3 # git-debpush -- create & push a git tag with metadata for an ftp-master upload
5 # Copyright (C) 2019 Sean Whitton
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 set -e$DGIT_TEST_DEBPUSH_DEBUG
25 # - do not invoke dgit, do anything involving any tarballs, no network
26 # access except `git push` right at the end
28 # - do not look at the working tree, like `git push` `git tag`
30 # - we are always in split brain mode, because that fits this workflow,
31 # and avoids pushes failing just because dgit in the intermediary
32 # service wants to append commits
34 # - if there is no previous tag created by this script, require a quilt
35 # mode; if there is a previous tag, and no quilt mode provided, assume
36 # same quilt mode as in previous tag created by this script
38 # **** Helper functions and variables ****
41 git_playtree_setup=git-playtree-setup ###substituted###
42 git_playtree_setup=${DEBPUSH_GIT_PLAYTREE_SETUP-$git_playtree_setup}
45 if [ -d "$temp" ]; then
59 get_file_from_ref () {
62 if git ls-tree --name-only -r "$branch" \
63 | grep -Eq "^$path$"; then
64 git cat-file blob $branch:$path
71 local check_is_forced=false
74 *",$check,"*) check_is_forced=true ;;
76 if $force_all || $check_is_forced; then
77 echo >&2 "$us: warning: $* ('$check' check)"
79 echo >&2 "$us: $* ('$check' check)"
84 fail_check_upstream_nonidentical () {
85 fail_check upstream-nonidentical \
86 "the upstream source in tag $upstream_tag is not identical to the upstream source in $branch"
92 set +o pipefail # perl will SIGPIPE git-log(1) here
93 git log --pretty=format:'%D' --decorate=full "$branch" \
94 | perl -wne 'use Dpkg::Version;
95 @pieces = split /, /, $_;
96 @debian_tag_vs = sort { version_compare($b, $a) }
97 map { m|tag: refs/tags/'"$prefix"'(.+)| ? $1 : () } @pieces;
98 if (@debian_tag_vs) { print "'"$prefix"'$debian_tag_vs[0]\n"; exit }'
108 git diff --quiet --exit-code "$first".."$second" -- . "$@"
112 # show the user what the difference was
113 if [ $git_diff_rc = 1 ]; then
114 git diff --compact-summary "$first".."$second" -- . "$@"
117 if [ $git_diff_rc -le 1 ]; then
120 fail "'git diff' exited with unexpected code $git_diff_rc"
124 check_patches_apply () {
125 local should_match_branch="$1"
127 local playground="$(git rev-parse --git-dir)/gdp"
128 local playtree="$playground/apply-patches"
135 "$git_playtree_setup" .
137 # checking out the upstream source and then d/patches on top
138 # ensures this check will work for a variety of quilt modes
139 git checkout -b upstream "$upstream_committish"
140 git checkout "$branch_commit" -- debian
142 if [ -s "debian/patches/series" ]; then
144 shopt -s extglob; patch="${patch%%?( )#*}"; shopt -u extglob
145 if [ -z "$patch" ]; then continue; fi
147 git apply --index "debian/patches/$patch"
150 if ! [ $git_apply_rc = 0 ]; then
151 fail_check patches-applicable \
152 "'git apply' failed to apply patch $patch"
155 done <debian/patches/series
157 if $should_match_branch && [ $git_apply_rc = 0 ]; then
158 git commit -q -a -m"commit result of applying all patches"
159 check_treesame HEAD "$branch_commit" ':!debian' \
160 || fail_check patches-applicable \
161 "applying all patches does not yield $branch"
169 # **** Parse command line ****
171 getopt=$(getopt -s bash -o 'nfu:' \
172 -l 'no-push,force::,branch:,remote:,distro:,upstream:,quilt:,gbp,dpm,\
173 baredebian,baredebian+git,baredebian+tarball' \
176 set -e$DGIT_TEST_DEBPUSH_DEBUG
188 '-n'|'--no-push') pushing=false; shift; continue ;;
189 '-u') git_tag_opts+=(-u "$2"); shift 2; continue ;;
190 '-f') force_all=true; shift; continue ;;
191 '--gbp') quilt_mode='gbp'; shift; continue ;;
192 '--dpm') quilt_mode='dpm'; shift; continue ;;
193 '--branch') branch=$2; shift 2; continue ;;
194 '--remote') remote=$2; shift 2; continue ;;
195 '--distro') distro=$2; shift 2; continue ;;
196 '--quilt') quilt_mode=$2; shift 2; continue ;;
197 '--upstream') upstream_tag=$2; shift 2; continue ;;
199 '--baredebian'|'--baredebian+git')
200 quilt_mode=baredebian; shift; continue ;;
201 '--baredebian+tarball')
202 fail "--baredebian+tarball quilt mode not supported"
205 # we require the long form of the option to skip individual
206 # checks, not permitting `-f check`, to avoid problems if we
207 # later want to introduce positional args
217 '--') shift; break ;;
218 *) badusage "unknown option $1" ;;
223 badusage 'no positional arguments allowed'
226 case "$quilt_mode" in
227 linear|auto|smash|nofix|gbp|dpm|unapplied|baredebian|'') ;;
228 baredebian+git) quilt_mode="baredebian" ;;
229 baredebian+tarball) fail "--baredebian+tarball quilt mode not supported" ;;
230 *) badusage "invalid quilt mode: $quilt_mode" ;;
233 # **** Early sanity check ****
235 if [ "$branch" = "HEAD" ] \
236 && ! git symbolic-ref --quiet HEAD >/dev/null; then
237 fail_check detached \
238 "HEAD is detached; you probably don't want to debpush it"
241 # **** Gather git information ****
246 # Maybe $branch is a symbolic ref. If so, resolve it
247 branchref="$(git symbolic-ref -q $branch || test $? = 1)"
248 if [ "x$branchref" != "x" ]; then
251 # If $branch is the name of a branch but it does not start with
252 # 'refs/heads/', prepend 'refs/heads/', so that we can know later
253 # whether we are tagging a branch or some other kind of committish
257 branchref="$(git for-each-ref --format='%(objectname)' \
258 '[r]efs/heads/$branch')"
259 if [ "x$branchref" != "x" ]; then
260 branch="refs/heads/$branch"
265 # If our tag will point at a branch, push that branch, and add its
266 # pushRemote and remote to the things we'll check if the user didn't
270 b=${branch#refs/heads/}
272 remoteconfigs+=( branch.$b.pushRemote branch.$b.remote )
276 # resolve $branch to a commit
277 branch_commit="$(git rev-parse --verify ${branch}^{commit})"
279 # also check, if the branch does not have its own pushRemote or
280 # remote, whether there's a default push remote configured
281 remoteconfigs+=(remote.pushDefault)
283 if $pushing && [ "x$remote" = "x" ]; then
284 for c in "${remoteconfigs[@]}"; do
285 remote=$(git config "$c" || test $? = 1)
286 if [ "x$remote" != "x" ]; then break; fi
288 if [ "x$remote" = "x" ]; then
289 fail "pushing, but could not determine remote, so need --remote="
293 # **** Gather source package information ****
298 git cat-file blob "$branch":debian/changelog >"$temp/debian/changelog"
299 version=$(cd $temp; dpkg-parsechangelog -SVersion)
300 source=$(cd $temp; dpkg-parsechangelog -SSource)
301 target=$(cd $temp; dpkg-parsechangelog -SDistribution)
305 format="$(get_file_from_ref debian/source/format)"
307 '3.0 (quilt)') upstream=true ;;
308 '3.0 (native)') upstream=false ;;
310 if get_file_from_ref debian/source/options | grep -q '^-sn *$'; then
312 elif get_file_from_ref debian/source/options | grep -q '^-sk *$'; then
315 fail 'please see "SOURCE FORMAT 1.0" in git-debpush(1)'
319 fail "unsupported debian/source/format $format"
323 # **** Gather git history information ****
325 last_debian_tag=$(find_last_tag "debian/")
326 last_archive_tag=$(find_last_tag "archive/debian/")
330 if [ "x$upstream_tag" = x ]; then
333 git deborig --just-print --version="$version" \
335 ps="${PIPESTATUS[*]}"
338 "0 0"|"141 0") ;; # ok or SIGPIPE
341 "$us: git-deborig failed; maybe try $us --upstream=TAG"
344 *) exit 127; # presumably head will have complained
347 if [ "x$upstream_tag" = x ]; then exit 127; fi
349 upstream_committish=$(git rev-parse "refs/tags/${upstream_tag}"^{})
350 upstream_info=" upstream-tag=$upstream_tag upstream=$upstream_committish"
351 to_push+=("$upstream_tag")
354 # **** Useful sanity checks ****
356 # ---- UNRELEASED suite
358 if [ "$target" = "UNRELEASED" ]; then
359 fail_check unreleased "UNRELEASED changelog"
362 # ---- Pushing dgit view to maintainer view
364 if ! [ "x$last_debian_tag" = "x" ] && ! [ "x$last_archive_tag" = "x" ]; then
365 last_debian_tag_c=$(git rev-parse "$last_debian_tag"^{})
366 last_archive_tag_c=$(git rev-parse "$last_archive_tag"^{})
367 if ! [ "$last_debian_tag_c" = "$last_archive_tag_c" ] \
368 && git merge-base --is-ancestor \
369 "$last_debian_tag" "$last_archive_tag"; then
370 fail_check dgit-view \
371 "looks like you might be trying to push the dgit view to the maintainer branch?"
375 # ---- Targeting different suite
377 if ! [ "x$last_debian_tag" = "x" ]; then
381 git cat-file blob "$last_debian_tag":debian/changelog >"$temp/debian/changelog"
382 prev_target=$(cd $temp; dpkg-parsechangelog -SDistribution)
386 if ! [ "$prev_target" = "$target" ] && ! [ "$target" = "UNRELEASED" ]; then
388 "last upload targeted $prev_target, now targeting $target; might be a mistake?"
392 # ---- Upstream tag is not ancestor of $branch
394 if ! [ "x$upstream_tag" = "x" ] \
395 && ! git merge-base --is-ancestor "$upstream_tag" "$branch" \
396 && ! [ "$quilt_mode" = "baredebian" ]; then
397 fail_check upstream-nonancestor \
398 "upstream tag $upstream_tag is not an ancestor of $branch; probably a mistake"
401 # ---- Quilt mode-specific checks
403 case "$quilt_mode" in
405 check_treesame "$upstream_tag" "$branch" ':!debian' ':!**.gitignore' \
406 || fail_check_upstream_nonidentical
407 check_patches_apply false
410 check_treesame "$upstream_tag" "$branch" ':!debian' \
411 || fail_check_upstream_nonidentical
412 check_patches_apply false
415 check_patches_apply false
418 check_patches_apply true
422 # ---- git-debrebase branch format checks
424 # only check branches, since you can't run `git debrebase conclude` on
428 # see "STITCHING, PSEUDO-MERGES, FFQ RECORD" in git-debrebase(5)
429 ffq_prev_ref="refs/ffq-prev/${branch#refs/}"
430 if git show-ref --quiet --verify "$ffq_prev_ref"; then
431 fail_check unstitched \
432 "this looks like an unstitched git-debrebase branch, which should not be pushed"
438 if $failed_check; then
439 # We don't mention the --force=check options here as those are
440 # mainly for use by scripts, or when you already know what check
441 # is going to fail before you invoke git-debpush. Keep the
442 # script's terminal output as simple as possible. No "see the
444 fail "some check(s) failed; you can pass --force to ignore them"
447 # **** Create the git tag ****
449 # convert according to DEP-14 rules
450 git_version=$(echo $version | tr ':~' '%_' | sed 's/\.(?=\.|$|lock$)/.#/g')
452 debian_tag="$distro/$git_version"
453 to_push+=("$debian_tag")
455 # If the user didn't supply a quilt mode, look for it in a previous
456 # tag made by this script
457 if [ "x$quilt_mode" = "x" ] && [ "$format" = "3.0 (quilt)" ]; then
458 set +o pipefail # perl will SIGPIPE git-cat-file(1) here
459 if [ "x$last_debian_tag" != "x" ]; then
460 quilt_mode=$(git cat-file -p $(git rev-parse "$last_debian_tag") \
462 'm/^\[dgit.*--quilt=([a-z+]+).*\]$/;
463 if ($1) { print "$1\n"; exit }')
469 if [ "$format" = "3.0 (quilt)" ]; then
470 if [ "x$quilt_mode" = "x" ]; then
471 echo >&2 "$us: could not determine the git branch layout"
472 echo >&2 "$us: please supply a --quilt= argument"
475 quilt_mode_text=" --quilt=$quilt_mode"
479 tagmessage="$source release $version for $target
481 [dgit distro=$distro split$quilt_mode_text]
482 [dgit please-upload$upstream_info]
485 git tag "${git_tag_opts[@]}" -s -m "$tagmessage" "$debian_tag" "$branch"
487 # **** Do a git push ****
490 git push "$remote" "${to_push[@]}"