chiark / gitweb /
git-debpush: check for target suite change since last upload
[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     if $force; then
69         echo >&2 "$us: warning: $*"
70     else
71         echo >&2 "$us: $*"
72         failed_check=true
73     fi
74 }
75
76 find_last_tag () {
77     local prefix=$1
78
79     set +o pipefail             # perl will SIGPIPE git-log(1) here
80     git log --pretty=format:'%D' --decorate=full "$branch" \
81         | perl -wne 'use Dpkg::Version;
82             @pieces = split /, /, $_;
83             @debian_tag_vs = sort { version_compare($b, $a) }
84                 map { m|tag: refs/tags/'"$prefix"'(.+)| ? $1 : () } @pieces;
85             if (@debian_tag_vs) { print "'"$prefix"'$debian_tag_vs[0]\n"; exit }'
86     set -o pipefail
87 }
88
89 # ---- Parse command line
90
91 getopt=$(getopt -s bash -o 'nfu:' \
92               -l 'no-push,force,branch:,remote:,distro:,upstream:,quilt:,gbp,dpm,\
93 baredebian,baredebian+git,baredebian+tarball' \
94               -n "$us" -- "$@")
95 eval "set - $getopt"
96 set -e$DGIT_TEST_DEBPUSH_DEBUG
97
98 git_tag_opts=()
99 pushing=true
100 force=false
101 distro=debian
102 quilt_mode=""
103 branch="HEAD"
104
105 while true; do
106     case "$1" in
107         '-n'|'--no-push') pushing=false;           shift;   continue ;;
108         '-u')             git_tag_opts+=(-u "$2"); shift 2; continue ;;
109         '-f'|'--force')   force=true;              shift;   continue ;;
110         '--gbp')          quilt_mode='gbp';        shift;   continue ;;
111         '--dpm')          quilt_mode='dpm';        shift;   continue ;;
112         '--branch')       branch=$2;               shift 2; continue ;;
113         '--remote')       remote=$2;               shift 2; continue ;;
114         '--distro')       distro=$2;               shift 2; continue ;;
115         '--quilt')        quilt_mode=$2;           shift 2; continue ;;
116         '--upstream')     upstream_tag=$2;         shift 2; continue ;;
117
118         '--baredebian'|'--baredebian+git')
119             quilt_mode=baredebian;         shift; continue ;;
120         '--baredebian+tarball')
121             fail "--baredebian+tarball quilt mode not supported"
122             ;;
123
124         '--') shift; break ;;
125         *) badusage "unknown option $1" ;;
126     esac
127 done
128
129 if [ $# != 0 ]; then
130     badusage 'no positional arguments allowed'
131 fi
132
133 case "$quilt_mode" in
134     linear|auto|smash|nofix|gbp|dpm|unapplied|baredebian|'') ;;
135     baredebian+git) quilt_mode="baredebian" ;;
136     baredebian+tarball) fail "--baredebian+tarball quilt mode not supported" ;;
137     *) badusage "invalid quilt mode: $quilt_mode" ;;
138 esac
139
140 # ---- Gather git information
141
142 remoteconfigs=()
143 push_branch=()
144
145 # Maybe $branch is a symbolic ref.  If so, resolve it
146 branchref="$(git symbolic-ref -q $branch || test $? = 1)"
147 if [ "x$branchref" != "x" ]; then
148    branch="$branchref"
149 fi
150 # If $branch is the name of a branch but it does not start with
151 # 'refs/heads/', prepend 'refs/heads/', so that we can know later
152 # whether we are tagging a branch or some other kind of committish
153 case "$branch" in
154     refs/heads/*) ;;
155     *)
156         branchref="$(git for-each-ref --format='%(objectname)' \
157                          '[r]efs/heads/$branch')"
158         if [ "x$branchref" != "x" ]; then
159             branch="refs/heads/$branch"
160         fi
161         ;;
162 esac
163
164 # If our tag will point at a branch, push that branch, and add its
165 # pushRemote and remote to the things we'll check if the user didn't
166 # supply a remote
167 case "$branch" in
168     refs/heads/*)
169         b=${branch#refs/heads/}
170         push_branch+=("$b")
171         remoteconfigs+=( branch.$b.pushRemote branch.$b.remote )
172         ;;
173 esac
174
175 # also check, if the branch does not have its own pushRemote or
176 # remote, whether there's a default push remote configured
177 remoteconfigs+=(remote.pushDefault)
178
179 if $pushing && [ "x$remote" = "x" ]; then
180     for c in "${remoteconfigs[@]}"; do
181         remote=$(git config "$c" || test $? = 1)
182         if [ "x$remote" != "x" ]; then break; fi
183     done
184     if [ "x$remote" = "x" ]; then
185         fail "pushing, but could not determine remote, so need --remote="
186     fi
187 fi
188
189 # ---- Gather source package information
190
191 temp=$(mktemp -d)
192 trap cleanup EXIT
193 mkdir "$temp/debian"
194 git cat-file blob "$branch":debian/changelog >"$temp/debian/changelog"
195 version=$(cd $temp; dpkg-parsechangelog -SVersion)
196 source=$(cd $temp; dpkg-parsechangelog -SSource)
197 target=$(cd $temp; dpkg-parsechangelog -SDistribution)
198 rm -rf "$temp"
199 trap - EXIT
200
201 # ---- Gather git history information
202
203 last_debian_tag=$(find_last_tag "debian/")
204 last_archive_tag=$(find_last_tag "archive/debian/")
205
206 # ---- Useful sanity checks
207
208 if [ "$target" = "UNRELEASED" ]; then
209     fail_check "UNRELEASED changelog"
210 fi
211
212 if ! [ "x$last_debian_tag" = "x" ] && ! [ "x$last_archive_tag" = "x" ]; then
213     last_debian_tag_c=$(git rev-parse "$last_debian_tag"^{})
214     last_archive_tag_c=$(git rev-parse "$last_archive_tag"^{})
215     if ! [ "$last_debian_tag_c" = "$last_archive_tag_c" ] \
216             && git merge-base --is-ancestor \
217                    "$last_debian_tag" "$last_archive_tag"; then
218         fail_check \
219 "looks like you might be trying to push the dgit view to the maintainer branch?"
220     fi
221 fi
222
223 if ! [ "x$last_debian_tag" = "x" ]; then
224     temp=$(mktemp -d)
225     trap cleanup EXIT
226     mkdir "$temp/debian"
227     git cat-file blob "$last_debian_tag":debian/changelog >"$temp/debian/changelog"
228     prev_target=$(cd $temp; dpkg-parsechangelog -SDistribution)
229     rm -rf "$temp"
230     trap - EXIT
231
232     if ! [ "$prev_target" = "$target" ] && ! [ "$target" = "UNRELEASED" ]; then
233         fail_check \
234 "last upload targeted $prev_target, now targeting $target; might be a mistake?"
235     fi
236 fi
237
238 if ! $force && $failed_check; then
239     fail "some checks failed; you can override with --force"
240 fi
241
242 # ---- Create the git tag
243
244 format="$(get_file_from_ref debian/source/format)"
245 case "$format" in
246     '3.0 (quilt)')  upstream=true ;;
247     '3.0 (native)') upstream=false ;;
248     '1.0'|'')
249         if get_file_from_ref debian/source/options | grep '^-sn *$'; then
250             upstream=false
251         elif get_file_from_ref debian/source/options | grep '^-sk *$'; then
252             upstream=true
253         else
254             fail 'please see "SOURCE FORMAT 1.0" in git-debpush(1)'
255         fi
256         ;;
257     *)
258         fail "unsupported debian/source/format $format"
259         ;;
260 esac
261
262 upstream_info=""
263 if $upstream; then
264     if [ "x$upstream_tag" = x ]; then
265         upstream_tag=$(
266             set +e
267             git deborig --just-print --version="$version" \
268                            | head -n1
269             ps="${PIPESTATUS[*]}"
270             set -e
271             case "$ps" in
272                 "0 0"|"141 0") ;; # ok or SIGPIPE
273                 *" 0")
274                     echo >&2 \
275  "$us: git-deborig failed; maybe try $us --upstream=TAG"
276                     exit 0
277                     ;;
278                 *) exit 127; # presumably head will have complained
279             esac
280         )
281         if [ "x$upstream_tag" = x ]; then exit 127; fi
282     fi
283     upstream_committish=$(git rev-parse "refs/tags/${upstream_tag}"^{})
284     upstream_info=" upstream-tag=$upstream_tag upstream=$upstream_committish"
285 fi
286
287 # convert according to DEP-14 rules
288 git_version=$(echo $version | tr ':~' '%_' | sed 's/\.(?=\.|$|lock$)/.#/g')
289
290 debian_tag="$distro/$git_version"
291
292 # If the user didn't supply a quilt mode, look for it in a previous
293 # tag made by this script
294 if [ "x$quilt_mode" = "x" ] && [ "$format" = "3.0 (quilt)" ]; then
295     set +o pipefail             # perl will SIGPIPE git-cat-file(1) here
296     if [ "x$last_debian_tag" != "x" ]; then
297         quilt_mode=$(git cat-file -p $(git rev-parse "$last_debian_tag") \
298                          | perl -wne \
299                                 'm/^\[dgit.*--quilt=([a-z+]+).*\]$/;
300                                  if ($1) { print "$1\n"; exit }')
301     fi
302     set -o pipefail
303 fi
304
305 quilt_mode_text=""
306 if [ "$format" = "3.0 (quilt)" ]; then
307     if [ "x$quilt_mode" = "x" ]; then
308         echo >&2 "$us: could not determine the git branch layout"
309         echo >&2 "$us: please supply a --quilt= argument"
310         exit 1
311     else
312         quilt_mode_text=" --quilt=$quilt_mode"
313     fi
314 fi
315
316 git tag "${git_tag_opts[@]}" -s -F- "$debian_tag" "$branch" <<EOF
317 $source release $version for $target
318
319 [dgit distro=$distro split$quilt_mode_text]
320 [dgit please-upload$upstream_info]
321 EOF
322
323 # ---- Do a git push
324
325 if $pushing; then
326     # xxx when user can specify upstream_tag, must cope with spaces
327     git push "$remote" "${push_branch[@]}" $upstream_tag "$debian_tag"
328 fi