chiark / gitweb /
dgit: update-vcs-git: Do not crash if url is unchanged
[dgit.git] / git-debpush
index e2ea2b64adf939a03686db14518ba2a11f12029d..c3b067dca4eff7feb0edab1812c33694f3e29e5e 100755 (executable)
@@ -38,6 +38,8 @@ set -o pipefail
 # **** Helper functions and variables ****
 
 us="$(basename $0)"
 # **** 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
 
 cleanup() {
     if [ -d "$temp" ]; then
@@ -65,14 +67,25 @@ get_file_from_ref () {
 
 failed_check=false
 fail_check () {
 
 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
     else
-        echo >&2 "$us: $*"
+        echo >&2 "$us: $* ('$check' check)"
         failed_check=true
     fi
 }
 
         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
 
 find_last_tag () {
     local prefix=$1
 
@@ -86,10 +99,77 @@ find_last_tag () {
     set -o pipefail
 }
 
     set -o pipefail
 }
 
+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:' \
 # **** 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"
 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
 
 git_tag_opts=()
 pushing=true
-force=false
+force_all=false
+force=""
 distro=debian
 quilt_mode=""
 branch="HEAD"
 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 ;;
     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 ;;
         '--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"
             ;;
 
             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
         '--') shift; break ;;
        *) badusage "unknown option $1" ;;
     esac
@@ -137,6 +230,14 @@ case "$quilt_mode" in
     *) badusage "invalid quilt mode: $quilt_mode" ;;
 esac
 
     *) badusage "invalid quilt mode: $quilt_mode" ;;
 esac
 
+# **** 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=()
 # **** Gather git information ****
 
 remoteconfigs=()
@@ -172,6 +273,9 @@ case "$branch" in
         ;;
 esac
 
         ;;
 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)
 # also check, if the branch does not have its own pushRemote or
 # remote, whether there's a default push remote configured
 remoteconfigs+=(remote.pushDefault)
@@ -203,9 +307,9 @@ case "$format" in
     '3.0 (quilt)')  upstream=true ;;
     '3.0 (native)') upstream=false ;;
     '1.0'|'')
     '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
            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)'
            upstream=true
        else
            fail 'please see "SOURCE FORMAT 1.0" in git-debpush(1)'
@@ -252,7 +356,7 @@ fi
 # ---- UNRELEASED suite
 
 if [ "$target" = "UNRELEASED" ]; then
 # ---- UNRELEASED suite
 
 if [ "$target" = "UNRELEASED" ]; then
-    fail_check "UNRELEASED changelog"
+    fail_check unreleased "UNRELEASED changelog"
 fi
 
 # ---- Pushing dgit view to maintainer view
 fi
 
 # ---- Pushing dgit view to maintainer view
@@ -263,7 +367,7 @@ if ! [ "x$last_debian_tag" = "x" ] && ! [ "x$last_archive_tag" = "x" ]; then
     if ! [ "$last_debian_tag_c" = "$last_archive_tag_c" ] \
             && git merge-base --is-ancestor \
                    "$last_debian_tag" "$last_archive_tag"; then
     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
 "looks like you might be trying to push the dgit view to the maintainer branch?"
     fi
 fi
@@ -280,7 +384,7 @@ if ! [ "x$last_debian_tag" = "x" ]; then
     trap - EXIT
 
     if ! [ "$prev_target" = "$target" ] && ! [ "$target" = "UNRELEASED" ]; 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
 "last upload targeted $prev_target, now targeting $target; might be a mistake?"
     fi
 fi
@@ -290,14 +394,54 @@ fi
 if ! [ "x$upstream_tag" = "x" ] \
         && ! git merge-base --is-ancestor "$upstream_tag" "$branch" \
         && ! [ "$quilt_mode" = "baredebian" ]; then
 if ! [ "x$upstream_tag" = "x" ] \
         && ! git merge-base --is-ancestor "$upstream_tag" "$branch" \
         && ! [ "$quilt_mode" = "baredebian" ]; then
-    fail_check \
+    fail_check upstream-nonancestor \
  "upstream tag $upstream_tag is not an ancestor of $branch; probably a mistake"
 fi
 
  "upstream tag $upstream_tag is not an ancestor of $branch; probably a mistake"
 fi
 
+# ---- 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
 
 # ---- Summary
 
-if ! $force && $failed_check; then
-    fail "some checks failed; you can override with --force"
+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 ****
 fi
 
 # **** Create the git tag ****
@@ -332,12 +476,13 @@ if [ "$format" = "3.0 (quilt)" ]; then
     fi
 fi
 
     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]
 
 [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 ****