516 lines
16 KiB
Bash
Executable file
516 lines
16 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# This program is used to check that a git repository follows the DEP-14 branch
|
|
# naming scheme. If not, it suggests how to convert it.
|
|
|
|
# Debian dep14-convert. Copyright (C) 2024-2025 Otto Kekäläinen.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
# Abort if anything goes wrong
|
|
set -euo pipefail
|
|
|
|
readonly PROGNAME=$(basename "$0")
|
|
readonly REQUIRED_FILES=("debian/source/format" "debian/control")
|
|
|
|
# Global variables
|
|
declare -a COMMANDS=()
|
|
declare -x SALSA_PROJECT=""
|
|
declare debian_branch=""
|
|
declare upstream_branch=""
|
|
declare APPLY=""
|
|
declare DEBUG=""
|
|
|
|
declare dep14_debian_branch="debian/latest"
|
|
|
|
stderr() {
|
|
echo "$@" >&2
|
|
}
|
|
|
|
error() {
|
|
stderr "ERROR: $*"
|
|
}
|
|
|
|
debug() {
|
|
if [[ -n "$DEBUG" ]]
|
|
then
|
|
if [ -z "$*" ]
|
|
then
|
|
stderr
|
|
else
|
|
stderr "DEBUG: $*"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
die() {
|
|
error "$*"
|
|
exit 1
|
|
}
|
|
|
|
|
|
usage() {
|
|
printf "%s\n" \
|
|
"Usage: $PROGNAME [options]
|
|
|
|
This helper tool assists in renaming the branch names by printing the necessary
|
|
git commands for local repository and salsa commands remote repository to rename
|
|
the branches and to update the default git branch. It also prints commands to
|
|
create a gbp.conf with matching branch names.
|
|
|
|
As this script does not actually modify anything, so feel free to run this
|
|
script in any Debian packaging repository to see what it outputs.
|
|
|
|
For DEP-14 purpose and details, please see
|
|
https://dep-team.pages.debian.net/deps/dep14/
|
|
|
|
Options:
|
|
--packaging-branch <name> Branch for main packaging (e.g. '${dep14_debian_branch}')
|
|
|
|
--debug Display debug information while running
|
|
-h, --help Display this help message
|
|
--version Display version information"
|
|
}
|
|
|
|
version() {
|
|
printf "%s\n" \
|
|
"This is $PROGNAME, from the Debian devscripts package, version ###VERSION###
|
|
This code is copyright 2024-2025 by Otto Kekäläinen, all rights reserved.
|
|
This program comes with ABSOLUTELY NO WARRANTY.
|
|
You are free to redistribute this code under the terms of the
|
|
GNU General Public License, version 3 or later."
|
|
}
|
|
|
|
check_requirements() {
|
|
# Check if we're in a git repository
|
|
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1
|
|
then
|
|
die "Not in a git repository. Please run this script from within a git repository"
|
|
fi
|
|
|
|
# Check for required files
|
|
for file in "${REQUIRED_FILES[@]}"
|
|
do
|
|
if [[ ! -f "$file" ]]
|
|
then
|
|
die "Required file $file not found"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Given the output from 'git ls-remote --get-url', parse the Salsa project slug
|
|
# Examples:
|
|
# https://salsa.debian.org/games-team/vcmi.git => games-team/vcmi
|
|
# git@salsa.debian.org:games-team/vcmi.git => games-team/vcmi
|
|
find_salsa_remote() {
|
|
# Populate SALSA_PROJECT only if it contained a Salsa address, otherwise
|
|
# keep it empty to prevent garbage from being passed on
|
|
case "$1" in
|
|
"git@salsa.debian.org:"*)
|
|
SALSA_PROJECT="${1##git@salsa.debian.org:}"
|
|
;;
|
|
"https://salsa.debian.org/"*)
|
|
SALSA_PROJECT="${1##https://salsa.debian.org/}"
|
|
;;
|
|
esac
|
|
SALSA_PROJECT="${SALSA_PROJECT%%.git}"
|
|
echo $SALSA_PROJECT
|
|
}
|
|
|
|
# Find the most likely branch used for unstable uploads
|
|
find_debian_branch() {
|
|
local debian_branch=""
|
|
|
|
# if debian/gbp.conf exists, use value of debian-branch
|
|
if [[ -f debian/gbp.conf ]] && grep -q "^debian-branch" debian/gbp.conf
|
|
then
|
|
debian_branch=$(grep -oP "^debian-branch[[:space:]]*=[[:space:]]*\K.*" debian/gbp.conf)
|
|
debug "debian/gbp.conf exists and has debian-branch '$debian_branch'"
|
|
|
|
if git rev-parse --verify "$debian_branch" > /dev/null 2>&1
|
|
then
|
|
echo "$debian_branch"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# check debian/changelog on common branches
|
|
# and if the changelog targeted 'unstable'
|
|
local branches="debian debian/sid debian/unstable debian/master master main"
|
|
|
|
for branch in $branches
|
|
do
|
|
debug "Check debian/changelog on branch '$branch'"
|
|
|
|
# Check if the branch has debian/changelog
|
|
local git_contents=$(git ls-tree -r $branch 2>&1 | grep "debian/changelog")
|
|
if [[ -n "$git_contents" ]]
|
|
then
|
|
local changelog_content=$(git show "$branch:debian/changelog")
|
|
local distribution=$(echo "$changelog_content" | \
|
|
grep "^[a-z]" | \
|
|
cut -d ' ' -f 3 | \
|
|
grep -v UNRELEASED | \
|
|
grep -v experimental | \
|
|
grep -m 1 -o "[a-z-]*"
|
|
)
|
|
|
|
debug "Found distribution '$distribution'"
|
|
|
|
if [[ "$distribution" == "unstable" ]]
|
|
then
|
|
debian_branch="$branch"
|
|
echo "$debian_branch"
|
|
return
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Find the most likely branch used for upstream releases
|
|
# - if debian/gbp.conf exists, use value of upstream-branch
|
|
# - check if common branches (upstream, master, main) recently merged on the
|
|
# assumed debian branch
|
|
find_upstream_branch() {
|
|
local debian_branch="$1"
|
|
local branches=$(git branch --list --format="%(refname:short)")
|
|
local upstream_branch=""
|
|
|
|
# if debian/gbp.conf exists on debian branch, use value of upstream-branch
|
|
if [[ -n "$debian_branch" ]] && git show "$debian_branch:debian/gbp.conf" 2>/dev/null | grep "^upstream-branch" > /dev/null
|
|
then
|
|
upstream_branch=$(git show "$debian_branch:debian/gbp.conf" | grep -oP "^upstream-branch[[:space:]]*=[[:space:]]*\K.*")
|
|
if git rev-parse --verify "$upstream_branch" >/dev/null 2>&1
|
|
then
|
|
echo "$upstream_branch"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Check which branch that modified files outside of debian/ was most
|
|
# recently merged on the debian branch, but cap checks to 50 most recent
|
|
# merges
|
|
merge_commits=$(git log --merges --format="%H" -50 $debian_branch)
|
|
|
|
# Iterate through the merge commits
|
|
for commit in $merge_commits
|
|
do
|
|
# Get the two parent commits
|
|
parent1=$(git rev-parse $commit^1)
|
|
parent2=$(git rev-parse $commit^2)
|
|
|
|
if [[ -n "$DEBUG" ]]
|
|
then
|
|
debug
|
|
debug "commit $commit"
|
|
debug git log -1 --oneline $parent1
|
|
git log -1 --oneline $parent1 >&2
|
|
debug git log -1 --oneline $parent2
|
|
git log -1 --oneline $parent2 >&2
|
|
fi
|
|
|
|
# Check if any files outside debian/ were changed as a result of the merge
|
|
changed_files=$(git diff --name-only --diff-filter=ACMRTUXB $parent1...$commit | grep -v "^debian/")
|
|
|
|
# If there are changed files outside debian/, break the loop
|
|
if [[ -n "$changed_files" ]]
|
|
then
|
|
debug "First merge affecting files outside debian/: $commit"
|
|
|
|
# Get the branch names that decent from the merge commit
|
|
merge_branches=$(git branch --list --format="%(refname:short)" --contains $parent2)
|
|
#debug "merge_branches: $merge_branches"
|
|
|
|
for branch in $merge_branches
|
|
do
|
|
# If only one branch was found, it must be it
|
|
if [[ "$branch" == "$merge_branches" ]]
|
|
then
|
|
upstream_branch="$branch"
|
|
break
|
|
fi
|
|
|
|
# If branch has no debian/changelog, assume it was the upstream branch
|
|
local git_contents=$(git ls-tree -r $branch 2>&1 | grep "debian/changelog")
|
|
if [[ -z "$git_contents" ]]
|
|
then
|
|
debug "Found branch '$branch' with no 'debian/changelog'"
|
|
upstream_branch="$branch"
|
|
break
|
|
fi
|
|
done
|
|
|
|
echo "$upstream_branch"
|
|
return
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Parse command line arguments
|
|
while :
|
|
do
|
|
case "${1:-}" in
|
|
--apply)
|
|
# @TODO: Not implemented yet
|
|
APPLY=1
|
|
shift
|
|
;;
|
|
--debug)
|
|
DEBUG=1
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--version)
|
|
version
|
|
exit 0
|
|
;;
|
|
--packaging-branch)
|
|
shift
|
|
dep14_debian_branch="$1"
|
|
shift
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
-*)
|
|
die "Unknown option: $1"
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Main script execution starts here
|
|
check_requirements
|
|
|
|
# Check if we have a valid packaging branch name
|
|
git check-ref-format --branch "$dep14_debian_branch" >/dev/null
|
|
|
|
# Check if package is native
|
|
if grep -qF native debian/source/format 2>/dev/null
|
|
then
|
|
stderr "DEP-14 is not applicable to native Debian packages."
|
|
grep -HF native debian/source/format
|
|
exit 0
|
|
fi
|
|
|
|
# Check for problematic upstream remote
|
|
if git remote get-url upstream > /dev/null 2>&1
|
|
then
|
|
stderr "WARNING: There is a remote called 'upstream', which may interfere with branch names 'upstream/*'."
|
|
stderr "Please rename the remote by running: git remote rename upstream upstreamvcs"
|
|
stderr
|
|
fi
|
|
|
|
# Check branch count
|
|
local_branches=$(git branch --list --format="%(refname:short)")
|
|
branch_count=$(echo "$local_branches" | wc -l)
|
|
if [[ "$branch_count" -gt 1 ]]
|
|
then
|
|
stderr "The git repository has the following local branches:" $local_branches
|
|
stderr
|
|
else
|
|
error "To identify the correct debian and upstream branches, there needs to be at least two local branches."
|
|
stderr "Currently there are only: " $local_branches
|
|
exit 1
|
|
fi
|
|
|
|
# Print DEP-14 requirements
|
|
cat >/dev/stderr << 'EOF'
|
|
In DEP-14, these branches should exist in the Debian packaging repository:
|
|
|
|
* debian/latest Used to create the *.debian.tar.xz that contains the Debian
|
|
packaging code from the debian/ directory, and which is
|
|
uploaded to Debian unstable (or occasionally to experimental).
|
|
DEP-14 also allows using branch names debian/unstable
|
|
or debian/experimental.
|
|
* upstream/latest Used to create the *.orig.tar.gz that contains the unmodified
|
|
source code of the specific upstream release.
|
|
|
|
Optionally, DEP-14 suggests the following branch:
|
|
|
|
* pristine-tar Contains xdelta data for making the release tarball
|
|
bit-for-bit identical with the original one, so that the
|
|
upstream *.orig.tar.gz.asc signature can be validated.
|
|
|
|
Other branches may also exist, but are not required.
|
|
|
|
EOF
|
|
|
|
# Check debian/latest branch
|
|
stderr -n "-> Branch ${dep14_debian_branch}: "
|
|
if git show-ref --verify --quiet refs/heads/${dep14_debian_branch}
|
|
then
|
|
stderr "exists"
|
|
debian_branch="${dep14_debian_branch}"
|
|
else
|
|
stderr -n "missing"
|
|
debian_branch=$(find_debian_branch)
|
|
|
|
if [[ -n "$debian_branch" ]]
|
|
then
|
|
stderr ", presumably '$debian_branch' should be renamed"
|
|
COMMANDS+=("git branch -m $debian_branch ${dep14_debian_branch}")
|
|
|
|
# Get Salsa project name primarily from git remote
|
|
SALSA_PROJECT="$(find_salsa_remote "$(git ls-remote --get-url)")"
|
|
|
|
# If nothing matched, maybe there's another remote
|
|
if [[ -z "$SALSA_PROJECT" ]]
|
|
then
|
|
debug "Current git remote not on Salsa, check other remotes"
|
|
SALSA_PROJECT=$(
|
|
git remote show -n | while read -r remote
|
|
do
|
|
find_salsa_remote "$(git ls-remote --get-url $remote)" && break || true
|
|
done
|
|
)
|
|
fi
|
|
|
|
# If nothing matched, fall back to Vcs-Git field
|
|
if [[ -z "$SALSA_PROJECT" ]]
|
|
then
|
|
debug "No git remote on Salsa, using Vcs-Git for SALSA_PROJECT instead"
|
|
SALSA_PROJECT=$(find_salsa_remote "$(git show "$debian_branch:debian/control" | grep -oP 'Vcs-Git: \K(.+salsa\.debian\.org.+)')" || true)
|
|
fi
|
|
|
|
if [[ -n "$SALSA_PROJECT" ]]
|
|
then
|
|
# Unprotecting the branch is a bit ugly, but this is how 'salsa' in
|
|
# devscripts works
|
|
COMMANDS+=("salsa protect_branch $SALSA_PROJECT $debian_branch no # (intentionally fails with error 404 if branch wasn't protected)")
|
|
COMMANDS+=("salsa rename_branch $SALSA_PROJECT --source-branch=$debian_branch --dest-branch=${dep14_debian_branch}")
|
|
COMMANDS+=("salsa update_repo $SALSA_PROJECT --rename-head --source-branch=$debian_branch --dest-branch=${dep14_debian_branch}")
|
|
fi
|
|
else
|
|
stderr
|
|
die "Could not find the current debian branch"
|
|
fi
|
|
fi
|
|
|
|
# Check upstream/latest branch
|
|
stderr -n "-> Branch upstream/latest: "
|
|
if git show-ref --verify --quiet refs/heads/upstream/latest
|
|
then
|
|
stderr "exists"
|
|
else
|
|
stderr -n "missing"
|
|
upstream_branch=$(find_upstream_branch "$debian_branch")
|
|
|
|
if [[ -n "$upstream_branch" ]]
|
|
then
|
|
stderr ", presumably '$upstream_branch' should be renamed"
|
|
COMMANDS+=("git branch -m $upstream_branch upstream/latest")
|
|
|
|
if [[ -n "$SALSA_PROJECT" ]]
|
|
then
|
|
# Rename to temporary name before using final name to avoid API error:
|
|
# (HTTP 400): Bad Request {"message":"Failed to create branch 'upstream/latest'
|
|
COMMANDS+=("salsa rename_branch $SALSA_PROJECT --source-branch=$upstream_branch --dest-branch=temporary")
|
|
COMMANDS+=("salsa rename_branch $SALSA_PROJECT --source-branch=temporary --dest-branch=upstream/latest")
|
|
fi
|
|
else
|
|
stderr
|
|
die "Could not find the current upstream branch"
|
|
fi
|
|
fi
|
|
|
|
# Check gbp.conf configuration
|
|
stderr -n "-> Configuration file debian/gbp.conf: "
|
|
gbp_conf_defaultsection=false
|
|
if git ls-tree -r "$debian_branch" 2>&1 | grep "debian/gbp.conf" > /dev/null
|
|
then
|
|
stderr -n "exists "
|
|
if git show "$debian_branch:debian/gbp.conf" | grep -qP "^debian-branch[[:space:]]*=[[:space:]]*${dep14_debian_branch}" &&
|
|
git show "$debian_branch:debian/gbp.conf" | grep -qP "^upstream-branch[[:space:]]*=[[:space:]]*upstream/latest"
|
|
then
|
|
stderr "and 'debian-branch' and 'upstream-branch' are correctly configured"
|
|
else
|
|
stderr "but 'debian-branch' or 'upstream-branch' does not have correct values"
|
|
COMMANDS+=("git checkout ${dep14_debian_branch}")
|
|
|
|
if git show "$debian_branch:debian/gbp.conf" | grep -qP "^debian-branch[[:space:]]*="
|
|
then
|
|
COMMANDS+=("sed -i 's/^debian-branch[[:space:]]*=.*/debian-branch = debian\/latest/' debian/gbp.conf")
|
|
else
|
|
test "${gbp_conf_defaultsection}" == "true" || COMMANDS+=('echo "[DEFAULT]" >> debian/gbp.conf') && gbp_conf_defaultsection=true
|
|
COMMANDS+=("echo \"debian-branch = ${dep14_debian_branch}\" >> debian/gbp.conf")
|
|
fi
|
|
|
|
if git show "$debian_branch:debian/gbp.conf" | grep -qP "^upstream-branch[[:space:]]*="
|
|
then
|
|
COMMANDS+=("sed -i 's/^upstream-branch[[:space:]]*=.*/upstream-branch = upstream\/latest/' debian/gbp.conf")
|
|
else
|
|
test "${gbp_conf_defaultsection}" == "true" || COMMANDS+=('echo "[DEFAULT]" >> debian/gbp.conf') && gbp_conf_defaultsection=true
|
|
COMMANDS+=('echo "upstream-branch = upstream/latest" >> debian/gbp.conf')
|
|
fi
|
|
fi
|
|
else
|
|
stderr "missing"
|
|
COMMANDS+=("git checkout ${dep14_debian_branch}")
|
|
COMMANDS+=('echo "[DEFAULT]" > debian/gbp.conf')
|
|
COMMANDS+=("echo \"debian-branch = ${dep14_debian_branch}\" >> debian/gbp.conf")
|
|
COMMANDS+=('echo "upstream-branch = upstream/latest" >> debian/gbp.conf')
|
|
fi
|
|
|
|
# If any commands modified gbp.conf, ensure last command commits everything in git
|
|
if echo "${COMMANDS[@]}" | grep --quiet --fixed-strings gbp.conf
|
|
then
|
|
COMMANDS+=('git commit -a -m "Update git repository layout to follow DEP-14"')
|
|
fi
|
|
|
|
# If any commands ran 'salsa', ensure remote deletes propagate to local git
|
|
if echo "${COMMANDS[@]}" | grep --quiet --fixed-strings 'salsa '
|
|
then
|
|
COMMANDS+=('git pull --prune')
|
|
fi
|
|
|
|
# Blank newline to make output more readable
|
|
stderr
|
|
|
|
# Handle results
|
|
if [[ ${#COMMANDS[@]} -eq 0 ]]
|
|
then
|
|
stderr "Repository is DEP-14 compliant."
|
|
else
|
|
if [[ -z "$APPLY" ]]
|
|
then
|
|
stderr "Run the following commands to make the repository follow DEP-14:"
|
|
printf " %s\n" "${COMMANDS[@]}"
|
|
else
|
|
die "Using --apply has not yet been implemented"
|
|
# @TODO: Run commands automatically once we have enough confidence they
|
|
# always work
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$SALSA_PROJECT" ]]
|
|
then
|
|
stderr
|
|
stderr "For accurate results, ensure your local git checkout is in sync with Salsa project $SALSA_PROJECT."
|
|
fi
|
|
|
|
|
|
# Note the developers: When testing changes to this script, a good way to test
|
|
# the integration with Salsa is to fork the project
|
|
# https://salsa.debian.org/sudo-team/sudo, and in your
|
|
# `path-to-fork/-/settings/repository` add `master` as a protected branch. This
|
|
# way the salsa API calls will mimic the scenario a typical rename would run
|
|
# into. You can delete the fork and create fresh forks for every test as many
|
|
# times as needed.
|