X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=git-debpush;h=1bfb0936c2e934087b50e4a2f3ec482e0af3cb37;hb=e82d9492c0a63d75c96e2ebdbc93300d57927d60;hp=4725eabbe17b4e321c9ff6ef3ee0e37a29546a19;hpb=b76d1994a63690251e5f521bdefb4a2d4a06a9cc;p=dgit.git diff --git a/git-debpush b/git-debpush index 4725eabb..1bfb0936 100755 --- a/git-debpush +++ b/git-debpush @@ -17,196 +17,231 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -set -e${DGIT_TEST_DEBPUSH_DEBUG-x} +set -e$DGIT_TEST_DEBPUSH_DEBUG set -o pipefail -# DEBUG +# DESIGN PRINCIPLES +# +# - do not invoke dgit, do anything involving any tarballs, no network +# access except `git push` right at the end +# +# - do not look at the working tree, like `git push` `git tag` +# +# - we are always in split brain mode, because that fits this workflow, +# and avoids pushes failing just because dgit in the intermediary +# service wants to append commits +# +# - if there is no previous tag created by this script, require a quilt +# mode; if there is a previous tag, and no quilt mode provided, assume +# same quilt mode as in previous tag created by this script -# Principles of operation +# **** Helper functions and variables **** -# - do not invoke dgit, anything involving any tarballs, no network -# except `git push` +us="$(basename $0)" -# - do not look at the working tree, like `git push` `git tag`, and so -# we can later add functionality to debpush any branch +cleanup() { + if [ -d "$temp" ]; then + rm -rf "$temp" + fi +} -# - we are always in split brain mode, because this means the push won't -# fail because dgit needs to append commits +fail () { + echo >&2 "$us: $*"; + exit 127; +} -# - if there is no previous tag created by this script, require a quilt -# mode; if there is a previous tag, and no quilt mode provided, assume -# same quilt mode +badusage () { + fail "bad usage: $*"; +} -# Other notes (which should be converted to a manpage/usage) +get_file_from_ref () { + local path=$1 -# - arguments after '--' passed to `git push` + if git ls-tree --name-only -r "$branch" \ + | grep -Eq "^$path$"; then + git cat-file blob $branch:$path + fi +} -# ---- Helper functions +failed_check=false +fail_check () { + local check=$1; shift + local check_is_forced=false -cleanup() { - if [ -d "$TEMP" ]; then - rm -rf "$TEMP" + case ",$force," in + *",$check,"*) check_is_forced=true ;; + esac + if $force_all || $check_is_forced; then + echo >&2 "$us: warning: $* ('$check' check)" + else + echo >&2 "$us: $* ('$check' check)" + failed_check=true fi } -# ---- Parse command line +fail_check_upstream_nonidentical () { + fail_check upstream-nonidentical \ + "the upstream source in tag $upstream_tag is not identical to the upstream source in $branch" +} -us="$(basename $0)" +find_last_tag () { + local prefix=$1 -fail () { echo >&2 "$us: $*"; exit 127; } -badusage () { fail "bad usage: $*"; } + set +o pipefail # perl will SIGPIPE git-log(1) here + git log --pretty=format:'%D' --decorate=full "$branch" \ + | perl -wne 'use Dpkg::Version; + @pieces = split /, /, $_; + @debian_tag_vs = sort { version_compare($b, $a) } + map { m|tag: refs/tags/'"$prefix"'(.+)| ? $1 : () } @pieces; + if (@debian_tag_vs) { print "'"$prefix"'$debian_tag_vs[0]\n"; exit }' + set -o pipefail +} + +check_treesame () { + local first=$1 + local second=$2 + shift 2 + + set +e + git diff --exit-code "$first".."$second" -- . "$@" + git_diff_rc=$? + set -e + + if [ $git_diff_rc -le 1 ]; then + return $git_diff_rc + else + fail "'git diff' exited with unexpected code $git_diff_rc" + fi +} + +# **** Parse command line **** getopt=$(getopt -s bash -o 'nfu:' \ - -l 'no-push,force,branch:,remote:,distro:,quilt:,gbp,dpm,baredebian,\ -baredebian+git,baredebian+tarball,linear' \ + -l 'no-push,force::,branch:,remote:,distro:,upstream:,quilt:,gbp,dpm,\ +baredebian,baredebian+git,baredebian+tarball' \ -n "$us" -- "$@") eval "set - $getopt" -set -e${DGIT_TEST_DEBPUSH_DEBUG-x} +set -e$DGIT_TEST_DEBPUSH_DEBUG git_tag_opts=() pushing=true +force_all=false +force="" distro=debian - quilt_mode="" +branch="HEAD" + while true; do case "$1" in - '-n'|'--no-push') - pushing=false - shift - continue - ;; - '-u') - git_tag_opts+=(-u "$2") - shift 2 - continue - ;; - '-f'|'--force') - force='yes' - shift - continue - ;; - '--gbp') - quilt_mode='gbp' - shift - continue - ;; - '--dpm') - quilt_mode='dpm' - shift - continue - ;; + '-n'|'--no-push') pushing=false; shift; continue ;; + '-u') git_tag_opts+=(-u "$2"); shift 2; continue ;; + '-f') force_all=true; shift; continue ;; + '--gbp') quilt_mode='gbp'; shift; continue ;; + '--dpm') quilt_mode='dpm'; shift; continue ;; + '--branch') branch=$2; shift 2; continue ;; + '--remote') remote=$2; shift 2; continue ;; + '--distro') distro=$2; shift 2; continue ;; + '--quilt') quilt_mode=$2; shift 2; continue ;; + '--upstream') upstream_tag=$2; shift 2; continue ;; + '--baredebian'|'--baredebian+git') - quilt_mode=baredebian - shift - continue - ;; + quilt_mode=baredebian; shift; continue ;; '--baredebian+tarball') - quilt_mode=baredebian+tarball - shift - continue + fail "--baredebian+tarball quilt mode not supported" ;; - '--branch') branch=$2; shift 2; continue ;; - '--remote') remote=$2; shift 2; continue ;; - '--distro') distro=$2; shift 2; continue ;; - '--quilt') quilt_mode=$2; shift 2; continue ;; - '--') - shift - break - ;; - *) - badusage "unknown option $1" - ;; + # we require the long form of the option to skip individual + # checks, not permitting `-f check`, to avoid problems if we + # later want to introduce positional args + '--force') + case "$2" in + '') + force_all=true ;; + *) + force="$force,$2" ;; + esac + shift 2; continue ;; + + '--') shift; break ;; + *) badusage "unknown option $1" ;; esac done -if [ $# != 0 ]; then badusage 'no positional arguments allowed'; fi +if [ $# != 0 ]; then + badusage 'no positional arguments allowed' +fi case "$quilt_mode" in - 'linear'|'auto'|'smash'|'nofix'|'gbp'|'dpm'|'unapplied'|'baredebian'|'baredebian+tarball') - ;; - 'baredebian+git') - quilt_mode="baredebian" - ;; - *) - badusage " invalid quilt mode: $quilt_mode" - ;; + linear|auto|smash|nofix|gbp|dpm|unapplied|baredebian|'') ;; + baredebian+git) quilt_mode="baredebian" ;; + baredebian+tarball) fail "--baredebian+tarball quilt mode not supported" ;; + *) badusage "invalid quilt mode: $quilt_mode" ;; esac +# **** Gather git information **** + remoteconfigs=() -push_branch=() - -if [ ! "$branch" ]; then - branch=HEAD - branchref="$(git symbolic-ref -q HEAD || test $? = 1)" - case "$branchref" in - refs/heads/*) - b=${branchref#refs/heads/} - remoteconfigs+=( branch.$b.pushRemote branch.$b.remote ) - push_branch+=("$b") - ;; - esac +to_push=() + +# Maybe $branch is a symbolic ref. If so, resolve it +branchref="$(git symbolic-ref -q $branch || test $? = 1)" +if [ "x$branchref" != "x" ]; then + branch="$branchref" fi +# If $branch is the name of a branch but it does not start with +# 'refs/heads/', prepend 'refs/heads/', so that we can know later +# whether we are tagging a branch or some other kind of committish +case "$branch" in + refs/heads/*) ;; + *) + branchref="$(git for-each-ref --format='%(objectname)' \ + '[r]efs/heads/$branch')" + if [ "x$branchref" != "x" ]; then + branch="refs/heads/$branch" + fi + ;; +esac +# If our tag will point at a branch, push that branch, and add its +# pushRemote and remote to the things we'll check if the user didn't +# supply a remote +case "$branch" in + refs/heads/*) + b=${branch#refs/heads/} + to_push+=("$b") + remoteconfigs+=( branch.$b.pushRemote branch.$b.remote ) + ;; +esac + +# also check, if the branch does not have its own pushRemote or +# remote, whether there's a default push remote configured remoteconfigs+=(remote.pushDefault) -if $pushing && [ ! "$remote" ]; then +if $pushing && [ "x$remote" = "x" ]; then for c in "${remoteconfigs[@]}"; do remote=$(git config "$c" || test $? = 1) - if [ "x$remote" ]; then break; fi + if [ "x$remote" != "x" ]; then break; fi done - if [ ! "$remote" ]; then + if [ "x$remote" = "x" ]; then fail "pushing, but could not determine remote, so need --remote=" fi fi -# ---- Gather source package information +# **** Gather source package information **** -TEMP=$(mktemp -d) +temp=$(mktemp -d) trap cleanup EXIT -mkdir "$TEMP/debian" -git cat-file blob HEAD:debian/changelog >"$TEMP/debian/changelog" -version=$(cd $TEMP; dpkg-parsechangelog -SVersion) -source=$(cd $TEMP; dpkg-parsechangelog -SSource) -target=$(cd $TEMP; dpkg-parsechangelog -SDistribution) -rm -rf "$TEMP" +mkdir "$temp/debian" +git cat-file blob "$branch":debian/changelog >"$temp/debian/changelog" +version=$(cd $temp; dpkg-parsechangelog -SVersion) +source=$(cd $temp; dpkg-parsechangelog -SSource) +target=$(cd $temp; dpkg-parsechangelog -SDistribution) +rm -rf "$temp" trap - EXIT -# ---- Useful sanity checks - -if [ "$force" != "yes" ]; then - - if [ "$target" = "UNRELEASED" ]; then - fail "UNRELEASED changelog" - fi - - # TODO additional checks we might do: - # - # - are we uploading to a different suite from the last tag - # (e.g. unstable after experimental)? user should pass option to - # confirm - # - # - walking backwards from $branch, if there is an archive/ strictly - # before we reach most recent debian/ tag, error, this might be a - # push of the dgit view to the maintainer branch - # - # - check for UNRELEASED changelog - -fi - -# ---- Create the git tag - -get_file_from_ref () { - local path=$1 - if git ls-tree --name-only -r "$branch" \ - | grep -Eq "^$path$"; then - git cat-file blob $branch:$path - fi -} - format="$(get_file_from_ref debian/source/format)" case "$format" in - '3.0 (quilt)') upstream=true ;; + '3.0 (quilt)') upstream=true ;; '3.0 (native)') upstream=false ;; '1.0'|'') if get_file_from_ref debian/source/options | grep '^-sn *$'; then @@ -214,7 +249,7 @@ case "$format" in elif get_file_from_ref debian/source/options | grep '^-sk *$'; then upstream=true else - fail 'xxxx see sn /sk docs' + fail 'please see "SOURCE FORMAT 1.0" in git-debpush(1)' fi ;; *) @@ -222,28 +257,122 @@ case "$format" in ;; esac +# **** Gather git history information **** + +last_debian_tag=$(find_last_tag "debian/") +last_archive_tag=$(find_last_tag "archive/debian/") + +upstream_info="" if $upstream; then - upstream_tag=$(git deborig --just-print --version="$version" \ - | head -n1) - upstream_committish=$(git rev-parse ${upstream_tag}^{}) + if [ "x$upstream_tag" = x ]; then + upstream_tag=$( + set +e + git deborig --just-print --version="$version" \ + | head -n1 + ps="${PIPESTATUS[*]}" + set -e + case "$ps" in + "0 0"|"141 0") ;; # ok or SIGPIPE + *" 0") + echo >&2 \ + "$us: git-deborig failed; maybe try $us --upstream=TAG" + exit 0 + ;; + *) exit 127; # presumably head will have complained + esac + ) + if [ "x$upstream_tag" = x ]; then exit 127; fi + fi + upstream_committish=$(git rev-parse "refs/tags/${upstream_tag}"^{}) upstream_info=" upstream-tag=$upstream_tag upstream=$upstream_committish" + to_push+=("$upstream_tag") fi +# **** Useful sanity checks **** + +# ---- UNRELEASED suite + +if [ "$target" = "UNRELEASED" ]; then + fail_check unreleased "UNRELEASED changelog" +fi + +# ---- Pushing dgit view to maintainer view + +if ! [ "x$last_debian_tag" = "x" ] && ! [ "x$last_archive_tag" = "x" ]; then + last_debian_tag_c=$(git rev-parse "$last_debian_tag"^{}) + last_archive_tag_c=$(git rev-parse "$last_archive_tag"^{}) + if ! [ "$last_debian_tag_c" = "$last_archive_tag_c" ] \ + && git merge-base --is-ancestor \ + "$last_debian_tag" "$last_archive_tag"; then + fail_check dgit-view \ +"looks like you might be trying to push the dgit view to the maintainer branch?" + fi +fi + +# ---- Targeting different suite + +if ! [ "x$last_debian_tag" = "x" ]; then + temp=$(mktemp -d) + trap cleanup EXIT + mkdir "$temp/debian" + git cat-file blob "$last_debian_tag":debian/changelog >"$temp/debian/changelog" + prev_target=$(cd $temp; dpkg-parsechangelog -SDistribution) + rm -rf "$temp" + trap - EXIT + + if ! [ "$prev_target" = "$target" ] && ! [ "$target" = "UNRELEASED" ]; then + fail_check suite \ +"last upload targeted $prev_target, now targeting $target; might be a mistake?" + fi +fi + +# ---- Upstream tag is not ancestor of $branch + +if ! [ "x$upstream_tag" = "x" ] \ + && ! git merge-base --is-ancestor "$upstream_tag" "$branch" \ + && ! [ "$quilt_mode" = "baredebian" ]; then + fail_check upstream-nonancestor \ + "upstream tag $upstream_tag is not an ancestor of $branch; probably a mistake" +fi + +# ---- Upstream tag tree nonidentical + +case "$quilt_mode" in + gbp) + check_treesame "$upstream_tag" "$branch" ':!debian' ':!**.gitignore' \ + || fail_check_upstream_nonidentical + ;; + unapplied) + check_treesame "$upstream_tag" "$branch" ':!debian' \ + || fail_check_upstream_nonidentical + ;; +esac + +# ---- Summary + +if $failed_check; then + # We don't mention the --force=check options here as those are + # mainly for use by scripts, or when you already know what check + # is going to fail before you invoke git-debpush. Keep the + # script's terminal output as simple as possible. No "see the + # manpage"! + fail "some check(s) failed; you can pass --force to ignore them" +fi + +# **** Create the git tag **** + # convert according to DEP-14 rules git_version=$(echo $version | tr ':~' '%_' | sed 's/\.(?=\.|$|lock$)/.#/g') debian_tag="$distro/$git_version" +to_push+=("$debian_tag") +# If the user didn't supply a quilt mode, look for it in a previous +# tag made by this script if [ "x$quilt_mode" = "x" ] && [ "$format" = "3.0 (quilt)" ]; then - set +o pipefail # perl will SIGPIPE git-log here - tag=$(git log --pretty=format:'%D' --decorate=full "$branch" \ - | perl -wne 'use Dpkg::Version; - @pieces = split /, /, $_; - @debian_tag_vs = sort {version_compare($b, $a)} - map { m|tag: refs/tags/debian/(.+)| ? $1 : () } @pieces; - if (@debian_tag_vs) { print "debian/$debian_tag_vs[0]\n"; exit }') - if [ "x$tag" != "x" ]; then - quilt_mode=$(git cat-file -p $(git rev-parse "$tag") \ + set +o pipefail # perl will SIGPIPE git-cat-file(1) here + if [ "x$last_debian_tag" != "x" ]; then + quilt_mode=$(git cat-file -p $(git rev-parse "$last_debian_tag") \ | perl -wne \ 'm/^\[dgit.*--quilt=([a-z+]+).*\]$/; if ($1) { print "$1\n"; exit }') @@ -269,8 +398,8 @@ $source release $version for $target [dgit please-upload$upstream_info] EOF -# ---- Do a git push +# **** Do a git push **** if $pushing; then - git push "$remote" "${push_branch[@]}" "$upstream_tag" "$debian_tag" + git push "$remote" "${to_push[@]}" fi