chiark / gitweb /
changelog: Start 9.8
[dgit.git] / git-debpush
index 80594f5cd0c5f392521fa6c77a052792abb048f2..c3b067dca4eff7feb0edab1812c33694f3e29e5e 100755 (executable)
@@ -35,9 +35,11 @@ set -o pipefail
 #   mode; if there is a previous tag, and no quilt mode provided, assume
 #   same quilt mode as in previous tag created by this script
 
-# ---- Helper functions and variables
+# **** Helper functions and variables ****
 
 us="$(basename $0)"
+git_playtree_setup=git-playtree-setup ###substituted###
+git_playtree_setup=${DEBPUSH_GIT_PLAYTREE_SETUP-$git_playtree_setup}
 
 cleanup() {
     if [ -d "$temp" ]; then
@@ -65,14 +67,25 @@ get_file_from_ref () {
 
 failed_check=false
 fail_check () {
-    if $force; then
-        echo >&2 "$us: warning: $*"
+    local check=$1; shift
+    local check_is_forced=false
+
+    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: $*"
+        echo >&2 "$us: $* ('$check' check)"
         failed_check=true
     fi
 }
 
+fail_check_upstream_nonidentical () {
+    fail_check upstream-nonidentical \
+ "the upstream source in tag $upstream_tag is not identical to the upstream source in $branch"
+}
+
 find_last_tag () {
     local prefix=$1
 
@@ -86,10 +99,77 @@ find_last_tag () {
     set -o pipefail
 }
 
-# ---- Parse command line
+check_treesame () {
+    local first=$1
+    local second=$2
+    shift 2
+
+    set +e
+    git diff --quiet --exit-code "$first".."$second" -- . "$@"
+    git_diff_rc=$?
+    set -e
+
+    # show the user what the difference was
+    if [ $git_diff_rc = 1 ]; then
+        git diff --compact-summary "$first".."$second" -- . "$@"
+    fi
+
+    if [ $git_diff_rc -le 1 ]; then
+        return $git_diff_rc
+    else
+        fail "'git diff' exited with unexpected code $git_diff_rc"
+    fi
+}
+
+check_patches_apply () {
+    local should_match_branch="$1"
+
+    local playground="$(git rev-parse --git-dir)/gdp"
+    local playtree="$playground/apply-patches"
+    local git_apply_rc=0
+
+    rm -rf "$playground"
+    mkdir -p "$playtree"
+    local pwd="$(pwd)"
+    cd "$playtree"
+    "$git_playtree_setup" .
+
+    # checking out the upstream source and then d/patches on top
+    # ensures this check will work for a variety of quilt modes
+    git checkout -b upstream "$upstream_committish"
+    git checkout "$branch_commit" -- debian
+
+    if [ -s "debian/patches/series" ]; then
+        while read patch; do
+            shopt -s extglob; patch="${patch%%?( )#*}"; shopt -u extglob
+            if [ -z "$patch" ]; then continue; fi
+            set +e
+            git apply --index "debian/patches/$patch"
+            git_apply_rc=$?
+            set -e
+            if ! [ $git_apply_rc = 0 ]; then
+                fail_check patches-applicable \
+                           "'git apply' failed to apply patch $patch"
+                break
+            fi
+        done <debian/patches/series
+
+        if $should_match_branch && [ $git_apply_rc = 0 ]; then
+            git commit -q -a -m"commit result of applying all patches"
+            check_treesame HEAD "$branch_commit" ':!debian' \
+                || fail_check patches-applicable \
+                              "applying all patches does not yield $branch"
+        fi
+    fi
+
+    cd "$pwd"
+    rm -rf "$playground"
+}
+
+# **** Parse command line ****
 
 getopt=$(getopt -s bash -o 'nfu:' \
-              -l 'no-push,force,branch:,remote:,distro:,upstream:,quilt:,gbp,dpm,\
+              -l 'no-push,force::,branch:,remote:,distro:,upstream:,quilt:,gbp,dpm,\
 baredebian,baredebian+git,baredebian+tarball' \
               -n "$us" -- "$@")
 eval "set - $getopt"
@@ -97,7 +177,8 @@ set -e$DGIT_TEST_DEBPUSH_DEBUG
 
 git_tag_opts=()
 pushing=true
-force=false
+force_all=false
+force=""
 distro=debian
 quilt_mode=""
 branch="HEAD"
@@ -106,7 +187,7 @@ 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=true;              shift;   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 ;;
@@ -121,6 +202,18 @@ while true; do
             fail "--baredebian+tarball quilt mode not supported"
             ;;
 
+        # 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
@@ -137,10 +230,18 @@ case "$quilt_mode" in
     *) badusage "invalid quilt mode: $quilt_mode" ;;
 esac
 
-# ---- Gather git information
+# **** Early sanity check ****
+
+if [ "$branch" = "HEAD" ] \
+       && ! git symbolic-ref --quiet HEAD >/dev/null; then
+    fail_check detached \
+               "HEAD is detached; you probably don't want to debpush it"
+fi
+
+# **** Gather git information ****
 
 remoteconfigs=()
-push_branch=()
+to_push=()
 
 # Maybe $branch is a symbolic ref.  If so, resolve it
 branchref="$(git symbolic-ref -q $branch || test $? = 1)"
@@ -167,11 +268,14 @@ esac
 case "$branch" in
     refs/heads/*)
         b=${branch#refs/heads/}
-        push_branch+=("$b")
+        to_push+=("$b")
         remoteconfigs+=( branch.$b.pushRemote branch.$b.remote )
         ;;
 esac
 
+# resolve $branch to a commit
+branch_commit="$(git rev-parse --verify ${branch}^{commit})"
+
 # also check, if the branch does not have its own pushRemote or
 # remote, whether there's a default push remote configured
 remoteconfigs+=(remote.pushDefault)
@@ -186,7 +290,7 @@ if $pushing && [ "x$remote" = "x" ]; then
     fi
 fi
 
-# ---- Gather source package information
+# **** Gather source package information ****
 
 temp=$(mktemp -d)
 trap cleanup EXIT
@@ -203,9 +307,9 @@ case "$format" in
     '3.0 (quilt)')  upstream=true ;;
     '3.0 (native)') upstream=false ;;
     '1.0'|'')
-       if get_file_from_ref debian/source/options | grep '^-sn *$'; then
+       if get_file_from_ref debian/source/options | grep -q '^-sn *$'; then
            upstream=false
-        elif get_file_from_ref debian/source/options | grep '^-sk *$'; then
+        elif get_file_from_ref debian/source/options | grep -q '^-sk *$'; then
            upstream=true
        else
            fail 'please see "SOURCE FORMAT 1.0" in git-debpush(1)'
@@ -216,7 +320,7 @@ case "$format" in
        ;;
 esac
 
-# ---- Gather git history information
+# **** Gather git history information ****
 
 last_debian_tag=$(find_last_tag "debian/")
 last_archive_tag=$(find_last_tag "archive/debian/")
@@ -244,25 +348,32 @@ if $upstream; then
     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
+# **** Useful sanity checks ****
+
+# ---- UNRELEASED suite
 
 if [ "$target" = "UNRELEASED" ]; then
-    fail_check "UNRELEASED changelog"
+    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 \
+        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
@@ -273,21 +384,73 @@ if ! [ "x$last_debian_tag" = "x" ]; then
     trap - EXIT
 
     if ! [ "$prev_target" = "$target" ] && ! [ "$target" = "UNRELEASED" ]; then
-        fail_check \
+        fail_check suite \
 "last upload targeted $prev_target, now targeting $target; might be a mistake?"
     fi
 fi
 
-if ! $force && $failed_check; then
-    fail "some checks failed; you can override with --force"
+# ---- 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
 
-# ---- Create the git tag
+# ---- Quilt mode-specific checks
+
+case "$quilt_mode" in
+    gbp)
+        check_treesame "$upstream_tag" "$branch" ':!debian' ':!**.gitignore' \
+            || fail_check_upstream_nonidentical
+        check_patches_apply false
+        ;;
+    unapplied)
+        check_treesame "$upstream_tag" "$branch" ':!debian' \
+            || fail_check_upstream_nonidentical
+        check_patches_apply false
+        ;;
+    baredebian)
+        check_patches_apply false
+        ;;
+    dpm|nofix)
+        check_patches_apply true
+        ;;
+esac
+
+# ---- git-debrebase branch format checks
+
+# only check branches, since you can't run `git debrebase conclude` on
+# non-branches
+case "$branch" in
+    refs/heads/*)
+        # see "STITCHING, PSEUDO-MERGES, FFQ RECORD" in git-debrebase(5)
+        ffq_prev_ref="refs/ffq-prev/${branch#refs/}"
+        if git show-ref --quiet --verify "$ffq_prev_ref"; then
+            fail_check unstitched \
+ "this looks like an unstitched git-debrebase branch, which should not be pushed"
+        fi
+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
@@ -313,19 +476,16 @@ if [ "$format" = "3.0 (quilt)" ]; then
     fi
 fi
 
-git tag "${git_tag_opts[@]}" -s -F- "$debian_tag" "$branch" <<EOF
-$source release $version for $target
+tagmessage="$source release $version for $target
 
 [dgit distro=$distro split$quilt_mode_text]
 [dgit please-upload$upstream_info]
-EOF
+"
+
+git tag "${git_tag_opts[@]}" -s -m "$tagmessage" "$debian_tag" "$branch"
 
-# ---- Do a git push
+# **** Do a git push ****
 
 if $pushing; then
-    if [ "x$upstream_tag" = "x" ]; then
-        git push "$remote" "${push_branch[@]}" "$debian_tag"
-    else
-        git push "$remote" "${push_branch[@]}" "$debian_tag" "$upstream_tag"
-    fi
+    git push "$remote" "${to_push[@]}"
 fi