File: dep-14-convert-git-branch-names.sh

package info (click to toggle)
devscripts 2.25.15
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 8,528 kB
  • sloc: perl: 26,530; sh: 11,698; python: 4,428; makefile: 363
file content (516 lines) | stat: -rwxr-xr-x 16,555 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
#!/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.