Merge branch 'fm/fetch-raw-sha1'
[gitweb.git] / git-rebase--interactive.sh
index 5822b2c59212fa5bd8dece9261d69e4fbc6a9719..bab0dccc04d85215223c30597659f23cf8edff32 100644 (file)
@@ -1,11 +1,8 @@
-#!/bin/sh
+# This shell script fragment is sourced by git-rebase to implement
+# its interactive mode.  "git rebase --interactive" makes it easy
+# to fix up commits in the middle of a series and rearrange commits.
 #
 # Copyright (c) 2006 Johannes E. Schindelin
-
-# SHORT DESCRIPTION
-#
-# This script makes it easy to fix up commits in the middle of a series,
-# and rearrange commits.
 #
 # The original idea comes from Eric W. Biederman, in
 # http://article.gmane.org/gmane.comp.version-control.git/22407
@@ -80,6 +77,18 @@ amend="$state_dir"/amend
 rewritten_list="$state_dir"/rewritten-list
 rewritten_pending="$state_dir"/rewritten-pending
 
+strategy_args=
+if test -n "$do_merge"
+then
+       strategy_args=${strategy:+--strategy=$strategy}
+       eval '
+               for strategy_opt in '"$strategy_opts"'
+               do
+                       strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")"
+               done
+       '
+fi
+
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
@@ -123,6 +132,16 @@ mark_action_done () {
        fi
 }
 
+# Put the last action marked done at the beginning of the todo list
+# again. If there has not been an action marked done yet, leave the list of
+# items on the todo list unchanged.
+reschedule_last_action () {
+       tail -n 1 "$done" | cat - "$todo" >"$todo".new
+       sed -e \$d <"$done" >"$done".new
+       mv -f "$todo".new "$todo"
+       mv -f "$done".new "$done"
+}
+
 append_todo_help () {
        git stripspace --comment-lines >>"$todo" <<\EOF
 
@@ -170,9 +189,10 @@ exit_with_patch () {
        echo "$1" > "$state_dir"/stopped-sha
        make_patch $1
        git rev-parse --verify HEAD > "$amend"
+       gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
        warn "You can amend the commit now, with"
        warn
-       warn "  git commit --amend"
+       warn "  git commit --amend $gpg_sign_opt_quoted"
        warn
        warn "Once you are satisfied with your changes, run"
        warn
@@ -239,7 +259,15 @@ pick_one () {
 
        test -d "$rewritten" &&
                pick_one_preserving_merges "$@" && return
-       output git cherry-pick $empty_args $ff "$@"
+       output eval git cherry-pick \
+                       ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
+                       "$strategy_args" $empty_args $ff "$@"
+
+       # If cherry-pick dies it leaves the to-be-picked commit unrecorded. Reschedule
+       # previous task so this commit is not lost.
+       ret=$?
+       case "$ret" in [01]) ;; *) reschedule_last_action ;; esac
+       return $ret
 }
 
 pick_one_preserving_merges () {
@@ -340,9 +368,10 @@ pick_one_preserving_merges () {
                        msg_content="$(commit_message $sha1)"
                        # No point in merging the first parent, that's HEAD
                        new_parents=${new_parents# $first_parent}
-                       if ! do_with_author output \
-                               git merge --no-ff ${strategy:+-s $strategy} -m \
-                                       "$msg_content" $new_parents
+                       merge_args="--no-log --no-ff"
+                       if ! do_with_author output eval \
+                       'git merge ${gpg_sign_opt:+"$gpg_sign_opt"} \
+                               $merge_args $strategy_args -m "$msg_content" $new_parents'
                        then
                                printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
                                die_with_patch $sha1 "Error redoing merge $sha1"
@@ -350,7 +379,9 @@ pick_one_preserving_merges () {
                        echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
                        ;;
                *)
-                       output git cherry-pick "$@" ||
+                       output eval git cherry-pick \
+                               ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
+                               "$strategy_args" "$@" ||
                                die_with_patch $sha1 "Could not pick $sha1"
                        ;;
                esac
@@ -461,7 +492,8 @@ do_pick () {
                           --no-post-rewrite -n -q -C $1 &&
                        pick_one -n $1 &&
                        git commit --allow-empty --allow-empty-message \
-                                  --amend --no-post-rewrite -n -q -C $1 ||
+                                  --amend --no-post-rewrite -n -q -C $1 \
+                                  ${gpg_sign_opt:+"$gpg_sign_opt"} ||
                        die_with_patch $1 "Could not apply $1... $2"
        else
                pick_one $1 ||
@@ -488,7 +520,7 @@ do_next () {
 
                mark_action_done
                do_pick $sha1 "$rest"
-               git commit --amend --no-post-rewrite || {
+               git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || {
                        warn "Could not amend commit after successfully picking $sha1... $rest"
                        warn "This is most likely due to an empty commit message, or the pre-commit hook"
                        warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
@@ -533,19 +565,22 @@ do_next () {
                squash|s|fixup|f)
                        # This is an intermediate commit; its message will only be
                        # used in case of trouble.  So use the long version:
-                       do_with_author output git commit --amend --no-verify -F "$squash_msg" ||
+                       do_with_author output git commit --amend --no-verify -F "$squash_msg" \
+                               ${gpg_sign_opt:+"$gpg_sign_opt"} ||
                                die_failed_squash $sha1 "$rest"
                        ;;
                *)
                        # This is the final command of this squash/fixup group
                        if test -f "$fixup_msg"
                        then
-                               do_with_author git commit --amend --no-verify -F "$fixup_msg" ||
+                               do_with_author git commit --amend --no-verify -F "$fixup_msg" \
+                                       ${gpg_sign_opt:+"$gpg_sign_opt"} ||
                                        die_failed_squash $sha1 "$rest"
                        else
                                cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
                                rm -f "$GIT_DIR"/MERGE_MSG
-                               do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e ||
+                               do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
+                                       ${gpg_sign_opt:+"$gpg_sign_opt"} ||
                                        die_failed_squash $sha1 "$rest"
                        fi
                        rm -f "$squash_msg" "$fixup_msg"
@@ -623,22 +658,21 @@ do_next () {
                git notes copy --for-rewrite=rebase < "$rewritten_list" ||
                true # we don't care if this copying failed
        } &&
-       if test -x "$GIT_DIR"/hooks/post-rewrite &&
-               test -s "$rewritten_list"; then
-               "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
+       hook="$(git rev-parse --git-path hooks/post-rewrite)"
+       if test -x "$hook" && test -s "$rewritten_list"; then
+               "$hook" rebase < "$rewritten_list"
                true # we don't care if this hook failed
        fi &&
-       rm -rf "$state_dir" &&
-       git gc --auto &&
        warn "Successfully rebased and updated $head_name."
 
-       exit
+       return 1 # not failure; just to break the do_rest loop
 }
 
+# can only return 0, when the infinite loop breaks
 do_rest () {
        while :
        do
-               do_next
+               do_next || break
        done
 }
 
@@ -661,7 +695,7 @@ skip_unnecessary_picks () {
                                ;;
                        esac
                        ;;
-               3,#*|3,)
+               3,"$comment_char"*|3,)
                        # copy comments
                        ;;
                *)
@@ -679,6 +713,32 @@ skip_unnecessary_picks () {
        die "Could not skip unnecessary pick commands"
 }
 
+transform_todo_ids () {
+       while read -r command rest
+       do
+               case "$command" in
+               "$comment_char"* | exec)
+                       # Be careful for oddball commands like 'exec'
+                       # that do not have a SHA-1 at the beginning of $rest.
+                       ;;
+               *)
+                       sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) &&
+                       rest="$sha1 ${rest#* }"
+                       ;;
+               esac
+               printf '%s\n' "$command${rest:+ }$rest"
+       done <"$todo" >"$todo.new" &&
+       mv -f "$todo.new" "$todo"
+}
+
+expand_todo_ids() {
+       transform_todo_ids
+}
+
+collapse_todo_ids() {
+       transform_todo_ids --short
+}
+
 # Rearrange the todo list that has both "pick sha1 msg" and
 # "pick sha1 fixup!/squash! msg" appears in it so that the latter
 # comes immediately after the former, and change "pick" to
@@ -690,8 +750,22 @@ rearrange_squash () {
                case "$message" in
                "squash! "*|"fixup! "*)
                        action="${message%%!*}"
-                       rest="${message#*! }"
-                       echo "$sha1 $action $rest"
+                       rest=$message
+                       prefix=
+                       # skip all squash! or fixup! (but save for later)
+                       while :
+                       do
+                               case "$rest" in
+                               "squash! "*|"fixup! "*)
+                                       prefix="$prefix${rest%%!*},"
+                                       rest="${rest#*! }"
+                                       ;;
+                               *)
+                                       break
+                                       ;;
+                               esac
+                       done
+                       printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest"
                        # if it's a single word, try to resolve to a full sha1 and
                        # emit a second copy. This allows us to match on both message
                        # and on sha1 prefix
@@ -700,7 +774,7 @@ rearrange_squash () {
                                if test -n "$fullsha"; then
                                        # prefix the action to uniquely identify this line as
                                        # intended for full sha1 match
-                                       echo "$sha1 +$action $fullsha"
+                                       echo "$sha1 +$action $prefix $fullsha"
                                fi
                        fi
                esac
@@ -715,7 +789,7 @@ rearrange_squash () {
                esac
                printf '%s\n' "$pick $sha1 $message"
                used="$used$sha1 "
-               while read -r squash action msg_content
+               while read -r squash action msg_prefix msg_content
                do
                        case " $used" in
                        *" $squash "*) continue ;;
@@ -731,7 +805,8 @@ rearrange_squash () {
                                case "$message" in "$msg_content"*) emit=1;; esac ;;
                        esac
                        if test $emit = 1; then
-                               printf '%s\n' "$action $squash $action! $msg_content"
+                               real_prefix=$(echo "$msg_prefix" | sed "s/,/! /g")
+                               printf '%s\n' "$action $squash ${real_prefix}$msg_content"
                                used="$used$squash "
                        fi
                done <"$1.sq"
@@ -761,6 +836,17 @@ add_exec_commands () {
        mv "$1.new" "$1"
 }
 
+# The whole contents of this file is run by dot-sourcing it from
+# inside a shell function.  It used to be that "return"s we see
+# below were not inside any function, and expected to return
+# to the function that dot-sourced us.
+#
+# However, FreeBSD /bin/sh misbehaves on such a construct and
+# continues to run the statements that follow such a "return".
+# As a work-around, we introduce an extra layer of a function
+# here, and immediately call it after defining it.
+git_rebase__interactive () {
+
 case "$action" in
 continue)
        # do we have anything to commit?
@@ -770,14 +856,15 @@ continue)
        else
                if ! test -f "$author_script"
                then
+                       gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
                        die "You have staged changes in your working tree. If these changes are meant to be
 squashed into the previous commit, run:
 
-  git commit --amend
+  git commit --amend $gpg_sign_opt_quoted
 
 If they are meant to go into a new commit, run:
 
-  git commit
+  git commit $gpg_sign_opt_quoted
 
 In both case, once you're done, continue with:
 
@@ -793,10 +880,12 @@ In both case, once you're done, continue with:
                        die "\
 You have uncommitted changes in your working tree. Please, commit them
 first and then run 'git rebase --continue' again."
-                       do_with_author git commit --amend --no-verify -F "$msg" -e ||
+                       do_with_author git commit --amend --no-verify -F "$msg" -e \
+                               ${gpg_sign_opt:+"$gpg_sign_opt"} ||
                                die "Could not commit staged changes."
                else
-                       do_with_author git commit --no-verify -F "$msg" -e ||
+                       do_with_author git commit --no-verify -F "$msg" -e \
+                               ${gpg_sign_opt:+"$gpg_sign_opt"} ||
                                die "Could not commit staged changes."
                fi
        fi
@@ -805,15 +894,18 @@ first and then run 'git rebase --continue' again."
 
        require_clean_work_tree "rebase"
        do_rest
+       return 0
        ;;
 skip)
        git rerere clear
 
        do_rest
+       return 0
        ;;
 edit-todo)
        git stripspace --strip-comments <"$todo" >"$todo".new
        mv -f "$todo".new "$todo"
+       collapse_todo_ids
        append_todo_help
        git stripspace --comment-lines >>"$todo" <<\EOF
 
@@ -825,6 +917,7 @@ EOF
 
        git_sequence_editor "$todo" ||
                die "Could not execute editor"
+       expand_todo_ids
 
        exit
        ;;
@@ -837,12 +930,15 @@ comment_for_reflog start
 
 if test ! -z "$switch_to"
 then
+       GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
        output git checkout "$switch_to" -- ||
-               die "Could not checkout $switch_to"
+       die "Could not checkout $switch_to"
+
+       comment_for_reflog start
 fi
 
 orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?"
-mkdir "$state_dir" || die "Could not create temporary $state_dir"
+mkdir -p "$state_dir" || die "Could not create temporary $state_dir"
 
 : > "$state_dir"/interactive || die "Could not mark as interactive"
 write_basic_state
@@ -881,14 +977,13 @@ else
        revisions=$onto...$orig_head
        shortrevisions=$shorthead
 fi
-git rev-list $merges_option --pretty=oneline --abbrev-commit \
-       --abbrev=7 --reverse --left-right --topo-order \
-       $revisions | \
+git rev-list $merges_option --pretty=oneline --reverse --left-right --topo-order \
+       $revisions ${restrict_revision+^$restrict_revision} | \
        sed -n "s/^>//p" |
-while read -r shortsha1 rest
+while read -r sha1 rest
 do
 
-       if test -z "$keep_empty" && is_empty_commit $shortsha1 && ! is_merge_commit $shortsha1
+       if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
        then
                comment_out="$comment_char "
        else
@@ -897,9 +992,8 @@ do
 
        if test t != "$preserve_merges"
        then
-               printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
+               printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
        else
-               sha1=$(git rev-parse $shortsha1)
                if test -z "$rebase_root"
                then
                        preserve=t
@@ -916,7 +1010,7 @@ do
                if test f = "$preserve"
                then
                        touch "$rewritten"/$sha1
-                       printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
+                       printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
                fi
        fi
 done
@@ -933,15 +1027,15 @@ then
        git rev-list $revisions |
        while read rev
        do
-               if test -f "$rewritten"/$rev -a "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = ""
+               if test -f "$rewritten"/$rev && test "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = ""
                then
                        # Use -f2 because if rev-list is telling us this commit is
                        # not worthwhile, we don't want to track its multiple heads,
                        # just the history of its first-parent for others that will
                        # be rebasing on top of it
                        git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev
-                       short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
-                       sane_grep -v "^[a-z][a-z]* $short" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo"
+                       sha1=$(git rev-list -1 $rev)
+                       sane_grep -v "^[a-z][a-z]* $sha1" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo"
                        rm "$rewritten"/$rev
                fi
        done
@@ -951,9 +1045,12 @@ test -s "$todo" || echo noop >> "$todo"
 test -n "$autosquash" && rearrange_squash "$todo"
 test -n "$cmd" && add_exec_commands "$todo"
 
+todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
+todocount=${todocount##* }
+
 cat >>"$todo" <<EOF
 
-$comment_char Rebase $shortrevisions onto $shortonto
+$comment_char Rebase $shortrevisions onto $shortonto ($todocount command(s))
 EOF
 append_todo_help
 git stripspace --comment-lines >>"$todo" <<\EOF
@@ -969,17 +1066,25 @@ fi
 
 
 has_action "$todo" ||
-       die_abort "Nothing to do"
+       return 2
 
 cp "$todo" "$todo".backup
+collapse_todo_ids
 git_sequence_editor "$todo" ||
        die_abort "Could not execute editor"
 
 has_action "$todo" ||
-       die_abort "Nothing to do"
+       return 2
+
+expand_todo_ids
 
 test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
 
+GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
 output git checkout $onto || die_abort "could not detach HEAD"
 git update-ref ORIG_HEAD $orig_head
 do_rest
+
+}
+# ... and then we call the whole thing.
+git_rebase__interactive