git p4 test: make sure P4CONFIG relative path works
[gitweb.git] / git-rebase--interactive.sh
index 2fed92fac1d73440c41f3fa57273d3000d8db3ce..048a140a6f6dd565a0d0d98b3b41439298626a96 100644 (file)
@@ -9,9 +9,7 @@
 #
 # The original idea comes from Eric W. Biederman, in
 # http://article.gmane.org/gmane.comp.version-control.git/22407
-
-. git-sh-setup
-
+#
 # The file containing rebase commands, comments, and empty lines.
 # This file is created by "git rebase -i" then edited by the user.  As
 # the lines are processed, they are removed from the front of this
@@ -82,6 +80,9 @@ rewritten_pending="$state_dir"/rewritten-pending
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
+comment_char=$(git config --get core.commentchar 2>/dev/null | cut -c1)
+: ${comment_char:=#}
+
 warn () {
        printf '%s\n' "$*" >&2
 }
@@ -107,8 +108,8 @@ mark_action_done () {
        sed -e 1q < "$todo" >> "$done"
        sed -e 1d < "$todo" >> "$todo".new
        mv -f "$todo".new "$todo"
-       new_count=$(sane_grep -c '^[^#]' < "$done")
-       total=$(($new_count+$(sane_grep -c '^[^#]' < "$todo")))
+       new_count=$(git stripspace --strip-comments <"$done" | wc -l)
+       total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l)))
        if test "$last_count" != "$new_count"
        then
                last_count=$new_count
@@ -117,6 +118,23 @@ mark_action_done () {
        fi
 }
 
+append_todo_help () {
+       git stripspace --comment-lines >>"$todo" <<\EOF
+
+Commands:
+ p, pick = use commit
+ r, reword = use commit, but edit the commit message
+ e, edit = use commit, but stop for amending
+ s, squash = use commit, but meld into previous commit
+ f, fixup = like "squash", but discard this commit's log message
+ x, exec = run command (the rest of the line) using shell
+
+These lines can be re-ordered; they are executed from top to bottom.
+
+If you remove a line here THAT COMMIT WILL BE LOST.
+EOF
+}
+
 make_patch () {
        sha1_and_parents="$(git rev-list --parents -1 "$1")"
        case "$sha1_and_parents" in
@@ -164,7 +182,7 @@ die_abort () {
 }
 
 has_action () {
-       sane_grep '^[^#]' "$1" >/dev/null
+       test -n "$(git stripspace --strip-comments <"$1")"
 }
 
 is_empty_commit() {
@@ -348,10 +366,10 @@ update_squash_messages () {
        if test -f "$squash_msg"; then
                mv "$squash_msg" "$squash_msg".bak || exit
                count=$(($(sed -n \
-                       -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \
+                       -e "1s/^. This is a combination of \(.*\) commits\./\1/p" \
                        -e "q" < "$squash_msg".bak)+1))
                {
-                       echo "# This is a combination of $count commits."
+                       printf '%s\n' "$comment_char This is a combination of $count commits."
                        sed -e 1d -e '2,/^./{
                                /^$/d
                        }' <"$squash_msg".bak
@@ -360,8 +378,8 @@ update_squash_messages () {
                commit_message HEAD > "$fixup_msg" || die "Cannot write $fixup_msg"
                count=2
                {
-                       echo "# This is a combination of 2 commits."
-                       echo "# The first commit's message is:"
+                       printf '%s\n' "$comment_char This is a combination of 2 commits."
+                       printf '%s\n' "$comment_char The first commit's message is:"
                        echo
                        cat "$fixup_msg"
                } >"$squash_msg"
@@ -370,21 +388,22 @@ update_squash_messages () {
        squash)
                rm -f "$fixup_msg"
                echo
-               echo "# This is the $(nth_string $count) commit message:"
+               printf '%s\n' "$comment_char This is the $(nth_string $count) commit message:"
                echo
                commit_message $2
                ;;
        fixup)
                echo
-               echo "# The $(nth_string $count) commit message will be skipped:"
+               printf '%s\n' "$comment_char The $(nth_string $count) commit message will be skipped:"
                echo
-               commit_message $2 | sed -e 's/^/#       /'
+               # Change the space after the comment character to TAB:
+               commit_message $2 | git stripspace --comment-lines | sed -e 's/ /       /'
                ;;
        esac >>"$squash_msg"
 }
 
 peek_next_command () {
-       sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$todo"
+       git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q
 }
 
 # A squash/fixup has failed.  Prepare the long version of the squash
@@ -422,27 +441,48 @@ record_in_rewritten() {
        esac
 }
 
+do_pick () {
+       if test "$(git rev-parse HEAD)" = "$squash_onto"
+       then
+               # Set the correct commit message and author info on the
+               # sentinel root before cherry-picking the original changes
+               # without committing (-n).  Finally, update the sentinel again
+               # to include these changes.  If the cherry-pick results in a
+               # conflict, this means our behaviour is similar to a standard
+               # failed cherry-pick during rebase, with a dirty index to
+               # resolve before manually running git commit --amend then git
+               # rebase --continue.
+               git commit --allow-empty --allow-empty-message --amend \
+                          --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 ||
+                       die_with_patch $1 "Could not apply $1... $2"
+       else
+               pick_one $1 ||
+                       die_with_patch $1 "Could not apply $1... $2"
+       fi
+}
+
 do_next () {
        rm -f "$msg" "$author_script" "$amend" || exit
        read -r command sha1 rest < "$todo"
        case "$command" in
-       '#'*|''|noop)
+       "$comment_char"*|''|noop)
                mark_action_done
                ;;
        pick|p)
                comment_for_reflog pick
 
                mark_action_done
-               pick_one $sha1 ||
-                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               do_pick $sha1 "$rest"
                record_in_rewritten $sha1
                ;;
        reword|r)
                comment_for_reflog reword
 
                mark_action_done
-               pick_one $sha1 ||
-                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               do_pick $sha1 "$rest"
                git commit --amend --no-post-rewrite || {
                        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"
@@ -456,8 +496,7 @@ do_next () {
                comment_for_reflog edit
 
                mark_action_done
-               pick_one $sha1 ||
-                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               do_pick $sha1 "$rest"
                warn "Stopped at $sha1... $rest"
                exit_with_patch $sha1 0
                ;;
@@ -480,25 +519,28 @@ do_next () {
                author_script_content=$(get_author_ident_from_commit HEAD)
                echo "$author_script_content" > "$author_script"
                eval "$author_script_content"
-               output git reset --soft HEAD^
-               pick_one -n $sha1 || die_failed_squash $sha1 "$rest"
+               if ! pick_one -n $sha1
+               then
+                       git rev-parse --verify HEAD >"$amend"
+                       die_failed_squash $sha1 "$rest"
+               fi
                case "$(peek_next_command)" in
                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 --no-verify -F "$squash_msg" ||
+                       do_with_author output git commit --amend --no-verify -F "$squash_msg" ||
                                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 --no-verify -F "$fixup_msg" ||
+                               do_with_author git commit --amend --no-verify -F "$fixup_msg" ||
                                        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 --no-verify -e ||
+                               do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e ||
                                        die_failed_squash $sha1 "$rest"
                        fi
                        rm -f "$squash_msg" "$fixup_msg"
@@ -528,6 +570,10 @@ do_next () {
                        warn
                        warn "  git rebase --continue"
                        warn
+                       if test $status -eq 127         # command not found
+                       then
+                               status=1
+                       fi
                        exit "$status"
                elif test "$dirty" = t
                then
@@ -542,22 +588,22 @@ do_next () {
                ;;
        *)
                warn "Unknown command: $command $sha1 $rest"
+               fixtodo="Please fix this using 'git rebase --edit-todo'."
                if git rev-parse --verify -q "$sha1" >/dev/null
                then
-                       die_with_patch $sha1 "Please fix this in the file $todo."
+                       die_with_patch $sha1 "$fixtodo"
                else
-                       die "Please fix this in the file $todo."
+                       die "$fixtodo"
                fi
                ;;
        esac
        test -s "$todo" && return
 
        comment_for_reflog finish &&
-       shortonto=$(git rev-parse --short $onto) &&
        newhead=$(git rev-parse HEAD) &&
        case $head_name in
        refs/*)
-               message="$GIT_REFLOG_ACTION: $head_name onto $shortonto" &&
+               message="$GIT_REFLOG_ACTION: $head_name onto $onto" &&
                git update-ref -m "$message" $head_name $newhead $orig_head &&
                git symbolic-ref \
                  -m "$GIT_REFLOG_ACTION: returning to $head_name" \
@@ -689,6 +735,27 @@ rearrange_squash () {
        rm -f "$1.sq" "$1.rearranged"
 }
 
+# Add commands after a pick or after a squash/fixup serie
+# in the todo list.
+add_exec_commands () {
+       {
+               first=t
+               while read -r insn rest
+               do
+                       case $insn in
+                       pick)
+                               test -n "$first" ||
+                               printf "%s" "$cmd"
+                               ;;
+                       esac
+                       printf "%s %s\n" "$insn" "$rest"
+                       first=
+               done
+               printf "%s" "$cmd"
+       } <"$1" >"$1.new" &&
+       mv "$1.new" "$1"
+}
+
 case "$action" in
 continue)
        # do we have anything to commit?
@@ -714,7 +781,6 @@ In both case, once you're done, continue with:
                fi
                . "$author_script" ||
                        die "Error trying to find the author identity to amend commit"
-               current_head=
                if test -f "$amend"
                then
                        current_head=$(git rev-parse --verify HEAD)
@@ -722,13 +788,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."
-                       git reset --soft HEAD^ ||
-                       die "Cannot rewind the HEAD"
+                       do_with_author git commit --amend --no-verify -F "$msg" -e ||
+                               die "Could not commit staged changes."
+               else
+                       do_with_author git commit --no-verify -F "$msg" -e ||
+                               die "Could not commit staged changes."
                fi
-               do_with_author git commit --no-verify -F "$msg" -e || {
-                       test -n "$current_head" && git reset --soft $current_head
-                       die "Could not commit staged changes."
-               }
        fi
 
        record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
@@ -741,6 +806,23 @@ skip)
 
        do_rest
        ;;
+edit-todo)
+       git stripspace --strip-comments <"$todo" >"$todo".new
+       mv -f "$todo".new "$todo"
+       append_todo_help
+       git stripspace --comment-lines >>"$todo" <<\EOF
+
+You are editing the todo file of an ongoing interactive rebase.
+To continue rebase after editing, run:
+    git rebase --continue
+
+EOF
+
+       git_sequence_editor "$todo" ||
+               die "Could not execute editor"
+
+       exit
+       ;;
 esac
 
 git var GIT_COMMITTER_IDENT >/dev/null ||
@@ -803,7 +885,7 @@ do
 
        if test -z "$keep_empty" && is_empty_commit $shortsha1 && ! is_merge_commit $shortsha1
        then
-               comment_out="# "
+               comment_out="$comment_char "
        else
                comment_out=
        fi
@@ -862,28 +944,22 @@ fi
 
 test -s "$todo" || echo noop >> "$todo"
 test -n "$autosquash" && rearrange_squash "$todo"
-cat >> "$todo" << EOF
+test -n "$cmd" && add_exec_commands "$todo"
+
+cat >>"$todo" <<EOF
+
+$comment_char Rebase $shortrevisions onto $shortonto
+EOF
+append_todo_help
+git stripspace --comment-lines >>"$todo" <<\EOF
+
+However, if you remove everything, the rebase will be aborted.
 
-# Rebase $shortrevisions onto $shortonto
-#
-# Commands:
-#  p, pick = use commit
-#  r, reword = use commit, but edit the commit message
-#  e, edit = use commit, but stop for amending
-#  s, squash = use commit, but meld into previous commit
-#  f, fixup = like "squash", but discard this commit's log message
-#  x, exec = run command (the rest of the line) using shell
-#
-# These lines can be re-ordered; they are executed from top to bottom.
-#
-# If you remove a line here THAT COMMIT WILL BE LOST.
-# However, if you remove everything, the rebase will be aborted.
-#
 EOF
 
 if test -z "$keep_empty"
 then
-       echo "# Note that empty commits are commented out" >>"$todo"
+       printf '%s\n' "$comment_char Note that empty commits are commented out" >>"$todo"
 fi