#!/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 . # 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 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.