chiark / gitweb /
dgit-maint-pbo(7): Add to .gitignore
[dgit.git] / git-debpush
1 #!/bin/bash
2
3 # git-debpush -- create & push a git tag with metadata for an ftp-master upload
4 #
5 # Copyright (C) 2019 Sean Whitton
6 #
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.
11 #
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.
16 #
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/>.
19
20 set -e$DGIT_TEST_DEBPUSH_DEBUG
21 set -o pipefail
22
23 # DESIGN PRINCIPLES
24 #
25 # - do not invoke dgit, do anything involving any tarballs, no network
26 #   access except `git push` right at the end
27 #
28 # - do not look at the working tree, like `git push` `git tag`
29 #
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
33 #
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
37
38 # **** Helper functions and variables ****
39
40 us="$(basename $0)"
41
42 cleanup() {
43     if [ -d "$temp" ]; then
44         rm -rf "$temp"
45     fi
46 }
47
48 fail () {
49     echo >&2 "$us: $*";
50     exit 127;
51 }
52
53 badusage () {
54     fail "bad usage: $*";
55 }
56
57 get_file_from_ref () {
58     local path=$1
59
60     if git ls-tree --name-only -r "$branch" \
61             | grep -Eq "^$path$"; then
62         git cat-file blob $branch:$path
63     fi
64 }
65
66 failed_check=false
67 fail_check () {
68     local check=$1; shift
69     local check_is_forced=false
70
71     case ",$force," in
72         *",$check,"*) check_is_forced=true ;;
73     esac
74     if $force_all || $check_is_forced; then
75         echo >&2 "$us: warning: $* ('$check' check)"
76     else
77         echo >&2 "$us: $* ('$check' check)"
78         failed_check=true
79     fi
80 }
81
82 find_last_tag () {
83     local prefix=$1
84
85     set +o pipefail             # perl will SIGPIPE git-log(1) here
86     git log --pretty=format:'%D' --decorate=full "$branch" \
87         | perl -wne 'use Dpkg::Version;
88             @pieces = split /, /, $_;
89             @debian_tag_vs = sort { version_compare($b, $a) }
90                 map { m|tag: refs/tags/'"$prefix"'(.+)| ? $1 : () } @pieces;
91             if (@debian_tag_vs) { print "'"$prefix"'$debian_tag_vs[0]\n"; exit }'
92     set -o pipefail
93 }
94
95 # **** Parse command line ****
96
97 getopt=$(getopt -s bash -o 'nfu:' \
98               -l 'no-push,force::,branch:,remote:,distro:,upstream:,quilt:,gbp,dpm,\
99 baredebian,baredebian+git,baredebian+tarball' \
100               -n "$us" -- "$@")
101 eval "set - $getopt"
102 set -e$DGIT_TEST_DEBPUSH_DEBUG
103
104 git_tag_opts=()
105 pushing=true
106 force_all=false
107 force=""
108 distro=debian
109 quilt_mode=""
110 branch="HEAD"
111
112 while true; do
113     case "$1" in
114         '-n'|'--no-push') pushing=false;           shift;   continue ;;
115         '-u')             git_tag_opts+=(-u "$2"); shift 2; continue ;;
116         '-f')             force_all=true;          shift;   continue ;;
117         '--gbp')          quilt_mode='gbp';        shift;   continue ;;
118         '--dpm')          quilt_mode='dpm';        shift;   continue ;;
119         '--branch')       branch=$2;               shift 2; continue ;;
120         '--remote')       remote=$2;               shift 2; continue ;;
121         '--distro')       distro=$2;               shift 2; continue ;;
122         '--quilt')        quilt_mode=$2;           shift 2; continue ;;
123         '--upstream')     upstream_tag=$2;         shift 2; continue ;;
124
125         '--baredebian'|'--baredebian+git')
126             quilt_mode=baredebian;         shift; continue ;;
127         '--baredebian+tarball')
128             fail "--baredebian+tarball quilt mode not supported"
129             ;;
130
131         # we require the long form of the option to skip individual
132         # checks, not permitting `-f check`, to avoid problems if we
133         # later want to introduce positional args
134         '--force')
135             case "$2" in
136                 '')
137                     force_all=true                         ;;
138                 *)
139                     force="$force,$2"                      ;;
140             esac
141             shift 2; continue ;;
142
143         '--') shift; break ;;
144         *) badusage "unknown option $1" ;;
145     esac
146 done
147
148 if [ $# != 0 ]; then
149     badusage 'no positional arguments allowed'
150 fi
151
152 case "$quilt_mode" in
153     linear|auto|smash|nofix|gbp|dpm|unapplied|baredebian|'') ;;
154     baredebian+git) quilt_mode="baredebian" ;;
155     baredebian+tarball) fail "--baredebian+tarball quilt mode not supported" ;;
156     *) badusage "invalid quilt mode: $quilt_mode" ;;
157 esac
158
159 # **** Gather git information ****
160
161 remoteconfigs=()
162 to_push=()
163
164 # Maybe $branch is a symbolic ref.  If so, resolve it
165 branchref="$(git symbolic-ref -q $branch || test $? = 1)"
166 if [ "x$branchref" != "x" ]; then
167    branch="$branchref"
168 fi
169 # If $branch is the name of a branch but it does not start with
170 # 'refs/heads/', prepend 'refs/heads/', so that we can know later
171 # whether we are tagging a branch or some other kind of committish
172 case "$branch" in
173     refs/heads/*) ;;
174     *)
175         branchref="$(git for-each-ref --format='%(objectname)' \
176                          '[r]efs/heads/$branch')"
177         if [ "x$branchref" != "x" ]; then
178             branch="refs/heads/$branch"
179         fi
180         ;;
181 esac
182
183 # If our tag will point at a branch, push that branch, and add its
184 # pushRemote and remote to the things we'll check if the user didn't
185 # supply a remote
186 case "$branch" in
187     refs/heads/*)
188         b=${branch#refs/heads/}
189         to_push+=("$b")
190         remoteconfigs+=( branch.$b.pushRemote branch.$b.remote )
191         ;;
192 esac
193
194 # also check, if the branch does not have its own pushRemote or
195 # remote, whether there's a default push remote configured
196 remoteconfigs+=(remote.pushDefault)
197
198 if $pushing && [ "x$remote" = "x" ]; then
199     for c in "${remoteconfigs[@]}"; do
200         remote=$(git config "$c" || test $? = 1)
201         if [ "x$remote" != "x" ]; then break; fi
202     done
203     if [ "x$remote" = "x" ]; then
204         fail "pushing, but could not determine remote, so need --remote="
205     fi
206 fi
207
208 # **** Gather source package information ****
209
210 temp=$(mktemp -d)
211 trap cleanup EXIT
212 mkdir "$temp/debian"
213 git cat-file blob "$branch":debian/changelog >"$temp/debian/changelog"
214 version=$(cd $temp; dpkg-parsechangelog -SVersion)
215 source=$(cd $temp; dpkg-parsechangelog -SSource)
216 target=$(cd $temp; dpkg-parsechangelog -SDistribution)
217 rm -rf "$temp"
218 trap - EXIT
219
220 format="$(get_file_from_ref debian/source/format)"
221 case "$format" in
222     '3.0 (quilt)')  upstream=true ;;
223     '3.0 (native)') upstream=false ;;
224     '1.0'|'')
225         if get_file_from_ref debian/source/options | grep '^-sn *$'; then
226             upstream=false
227         elif get_file_from_ref debian/source/options | grep '^-sk *$'; then
228             upstream=true
229         else
230             fail 'please see "SOURCE FORMAT 1.0" in git-debpush(1)'
231         fi
232         ;;
233     *)
234         fail "unsupported debian/source/format $format"
235         ;;
236 esac
237
238 # **** Gather git history information ****
239
240 last_debian_tag=$(find_last_tag "debian/")
241 last_archive_tag=$(find_last_tag "archive/debian/")
242
243 upstream_info=""
244 if $upstream; then
245     if [ "x$upstream_tag" = x ]; then
246         upstream_tag=$(
247             set +e
248             git deborig --just-print --version="$version" \
249                            | head -n1
250             ps="${PIPESTATUS[*]}"
251             set -e
252             case "$ps" in
253                 "0 0"|"141 0") ;; # ok or SIGPIPE
254                 *" 0")
255                     echo >&2 \
256  "$us: git-deborig failed; maybe try $us --upstream=TAG"
257                     exit 0
258                     ;;
259                 *) exit 127; # presumably head will have complained
260             esac
261         )
262         if [ "x$upstream_tag" = x ]; then exit 127; fi
263     fi
264     upstream_committish=$(git rev-parse "refs/tags/${upstream_tag}"^{})
265     upstream_info=" upstream-tag=$upstream_tag upstream=$upstream_committish"
266     to_push+=("$upstream_tag")
267 fi
268
269 # **** Useful sanity checks ****
270
271 # ---- UNRELEASED suite
272
273 if [ "$target" = "UNRELEASED" ]; then
274     fail_check unreleased "UNRELEASED changelog"
275 fi
276
277 # ---- Pushing dgit view to maintainer view
278
279 if ! [ "x$last_debian_tag" = "x" ] && ! [ "x$last_archive_tag" = "x" ]; then
280     last_debian_tag_c=$(git rev-parse "$last_debian_tag"^{})
281     last_archive_tag_c=$(git rev-parse "$last_archive_tag"^{})
282     if ! [ "$last_debian_tag_c" = "$last_archive_tag_c" ] \
283             && git merge-base --is-ancestor \
284                    "$last_debian_tag" "$last_archive_tag"; then
285         fail_check dgit-view \
286 "looks like you might be trying to push the dgit view to the maintainer branch?"
287     fi
288 fi
289
290 # ---- Targeting different suite
291
292 if ! [ "x$last_debian_tag" = "x" ]; then
293     temp=$(mktemp -d)
294     trap cleanup EXIT
295     mkdir "$temp/debian"
296     git cat-file blob "$last_debian_tag":debian/changelog >"$temp/debian/changelog"
297     prev_target=$(cd $temp; dpkg-parsechangelog -SDistribution)
298     rm -rf "$temp"
299     trap - EXIT
300
301     if ! [ "$prev_target" = "$target" ] && ! [ "$target" = "UNRELEASED" ]; then
302         fail_check suite \
303 "last upload targeted $prev_target, now targeting $target; might be a mistake?"
304     fi
305 fi
306
307 # ---- Upstream tag is not ancestor of $branch
308
309 if ! [ "x$upstream_tag" = "x" ] \
310         && ! git merge-base --is-ancestor "$upstream_tag" "$branch" \
311         && ! [ "$quilt_mode" = "baredebian" ]; then
312     fail_check upstream-nonancestor \
313  "upstream tag $upstream_tag is not an ancestor of $branch; probably a mistake"
314 fi
315
316 # ---- Summary
317
318 if $failed_check; then
319     # We don't mention the --force=check options here as those are
320     # mainly for use by scripts, or when you already know what check
321     # is going to fail before you invoke git-debpush.  Keep the
322     # script's terminal output as simple as possible.  No "see the
323     # manpage"!
324     fail "some check(s) failed; you can pass --force to ignore them"
325 fi
326
327 # **** Create the git tag ****
328
329 # convert according to DEP-14 rules
330 git_version=$(echo $version | tr ':~' '%_' | sed 's/\.(?=\.|$|lock$)/.#/g')
331
332 debian_tag="$distro/$git_version"
333 to_push+=("$debian_tag")
334
335 # If the user didn't supply a quilt mode, look for it in a previous
336 # tag made by this script
337 if [ "x$quilt_mode" = "x" ] && [ "$format" = "3.0 (quilt)" ]; then
338     set +o pipefail             # perl will SIGPIPE git-cat-file(1) here
339     if [ "x$last_debian_tag" != "x" ]; then
340         quilt_mode=$(git cat-file -p $(git rev-parse "$last_debian_tag") \
341                          | perl -wne \
342                                 'm/^\[dgit.*--quilt=([a-z+]+).*\]$/;
343                                  if ($1) { print "$1\n"; exit }')
344     fi
345     set -o pipefail
346 fi
347
348 quilt_mode_text=""
349 if [ "$format" = "3.0 (quilt)" ]; then
350     if [ "x$quilt_mode" = "x" ]; then
351         echo >&2 "$us: could not determine the git branch layout"
352         echo >&2 "$us: please supply a --quilt= argument"
353         exit 1
354     else
355         quilt_mode_text=" --quilt=$quilt_mode"
356     fi
357 fi
358
359 git tag "${git_tag_opts[@]}" -s -F- "$debian_tag" "$branch" <<EOF
360 $source release $version for $target
361
362 [dgit distro=$distro split$quilt_mode_text]
363 [dgit please-upload$upstream_info]
364 EOF
365
366 # **** Do a git push ****
367
368 if $pushing; then
369     git push "$remote" "${to_push[@]}"
370 fi