Merge branch 'aw/maint-rebase-i-p-no-ff'
authorJunio C Hamano <gitster@pobox.com>
Fri, 6 May 2011 17:50:00 +0000 (10:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 May 2011 17:50:00 +0000 (10:50 -0700)
* aw/maint-rebase-i-p-no-ff:
git-rebase--interactive.sh: preserve-merges fails on merges created with no-ff

Conflicts:
git-rebase--interactive.sh

1  2 
git-rebase--interactive.sh
index 6566d319aa121fc8f89fff28d0169fd9a1723537,c308529a9f1f668f02d63cfddcbac10770faa723..41ba96aeb7f8ee32f7c346d5d7934f4d6b305d2f
mode 100644,100755..100644
  # The original idea comes from Eric W. Biederman, in
  # http://article.gmane.org/gmane.comp.version-control.git/22407
  
 -OPTIONS_KEEPDASHDASH=
 -OPTIONS_SPEC="\
 -git-rebase [-i] [options] [--] <upstream> [<branch>]
 -git-rebase [-i] (--continue | --abort | --skip)
 ---
 - Available options are
 -v,verbose          display a diffstat of what changed upstream
 -onto=              rebase onto given branch instead of upstream
 -p,preserve-merges  try to recreate merges instead of ignoring them
 -s,strategy=        use the given merge strategy
 -no-ff              cherry-pick all commits, even if unchanged
 -m,merge            always used (no-op)
 -i,interactive      always used (no-op)
 - Actions:
 -continue           continue rebasing process
 -abort              abort rebasing process and restore original branch
 -skip               skip current patch and continue rebasing process
 -no-verify          override pre-rebase hook from stopping the operation
 -verify             allow pre-rebase hook to run
 -root               rebase all reachable commmits up to the root(s)
 -autosquash         move commits that begin with squash!/fixup! under -i
 -"
 -
  . git-sh-setup
 -require_work_tree
 -
 -DOTEST="$GIT_DIR/rebase-merge"
  
  # 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
 -# file and written to the tail of $DONE.
 -TODO="$DOTEST"/git-rebase-todo
 +# file and written to the tail of $done.
 +todo="$state_dir"/git-rebase-todo
  
  # The rebase command lines that have already been processed.  A line
  # is moved here when it is first handled, before any associated user
  # actions.
 -DONE="$DOTEST"/done
 +done="$state_dir"/done
  
  # The commit message that is planned to be used for any changes that
  # need to be committed following a user interaction.
 -MSG="$DOTEST"/message
 +msg="$state_dir"/message
  
  # The file into which is accumulated the suggested commit message for
  # squash/fixup commands.  When the first of a series of squash/fixups
  # is appended to the file as it is processed.
  #
  # The first line of the file is of the form
 -#     # This is a combination of $COUNT commits.
 -# where $COUNT is the number of commits whose messages have been
 +#     # This is a combination of $count commits.
 +# where $count is the number of commits whose messages have been
  # written to the file so far (including the initial "pick" commit).
  # Each time that a commit message is processed, this line is read and
  # updated.  It is deleted just before the combined commit is made.
 -SQUASH_MSG="$DOTEST"/message-squash
 +squash_msg="$state_dir"/message-squash
  
  # If the current series of squash/fixups has not yet included a squash
  # command, then this file exists and holds the commit message of the
  # original "pick" commit.  (If the series ends without a "squash"
  # command, then this can be used as the commit message of the combined
  # commit without opening the editor.)
 -FIXUP_MSG="$DOTEST"/message-fixup
 +fixup_msg="$state_dir"/message-fixup
  
 -# $REWRITTEN is the name of a directory containing files for each
 -# commit that is reachable by at least one merge base of $HEAD and
 -# $UPSTREAM. They are not necessarily rewritten, but their children
 +# $rewritten is the name of a directory containing files for each
 +# commit that is reachable by at least one merge base of $head and
 +# $upstream. They are not necessarily rewritten, but their children
  # might be.  This ensures that commits on merged, but otherwise
  # unrelated side branches are left alone. (Think "X" in the man page's
  # example.)
 -REWRITTEN="$DOTEST"/rewritten
 +rewritten="$state_dir"/rewritten
  
 -DROPPED="$DOTEST"/dropped
 +dropped="$state_dir"/dropped
  
  # A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
  # GIT_AUTHOR_DATE that will be used for the commit that is currently
  # being rebased.
 -AUTHOR_SCRIPT="$DOTEST"/author-script
 +author_script="$state_dir"/author-script
  
  # When an "edit" rebase command is being processed, the SHA1 of the
  # commit to be edited is recorded in this file.  When "git rebase
  # will be amended to the HEAD commit, but only provided the HEAD
  # commit is still the commit to be edited.  When any other rebase
  # command is processed, this file is deleted.
 -AMEND="$DOTEST"/amend
 +amend="$state_dir"/amend
  
  # For the post-rewrite hook, we make a list of rewritten commits and
  # their new sha1s.  The rewritten-pending list keeps the sha1s of
  # commits that have been processed, but not committed yet,
  # e.g. because they are waiting for a 'squash' command.
 -REWRITTEN_LIST="$DOTEST"/rewritten-list
 -REWRITTEN_PENDING="$DOTEST"/rewritten-pending
 -
 -PRESERVE_MERGES=
 -STRATEGY=
 -ONTO=
 -VERBOSE=
 -OK_TO_SKIP_PRE_REBASE=
 -REBASE_ROOT=
 -AUTOSQUASH=
 -test "$(git config --bool rebase.autosquash)" = "true" && AUTOSQUASH=t
 -NEVER_FF=
 -
 -GIT_CHERRY_PICK_HELP="\
 -hint: after resolving the conflicts, mark the corrected paths
 -hint: with 'git add <paths>' and run 'git rebase --continue'"
 +rewritten_list="$state_dir"/rewritten-list
 +rewritten_pending="$state_dir"/rewritten-pending
 +
 +GIT_CHERRY_PICK_HELP="$resolvemsg"
  export GIT_CHERRY_PICK_HELP
  
  warn () {
        printf '%s\n' "$*" >&2
  }
  
 -output () {
 -      case "$VERBOSE" in
 -      '')
 -              output=$("$@" 2>&1 )
 -              status=$?
 -              test $status != 0 && printf "%s\n" "$output"
 -              return $status
 -              ;;
 -      *)
 -              "$@"
 -              ;;
 -      esac
 -}
 -
  # Output the commit message for the specified commit.
  commit_message () {
        git cat-file commit "$1" | sed "1,/^$/d"
  }
  
 -run_pre_rebase_hook () {
 -      if test -z "$OK_TO_SKIP_PRE_REBASE" &&
 -         test -x "$GIT_DIR/hooks/pre-rebase"
 -      then
 -              "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
 -                      echo >&2 "The pre-rebase hook refused to rebase."
 -                      exit 1
 -              }
 -      fi
 -}
 -
 -
 -ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
 +orig_reflog_action="$GIT_REFLOG_ACTION"
  
  comment_for_reflog () {
 -      case "$ORIG_REFLOG_ACTION" in
 +      case "$orig_reflog_action" in
        ''|rebase*)
                GIT_REFLOG_ACTION="rebase -i ($1)"
                export GIT_REFLOG_ACTION
  
  last_count=
  mark_action_done () {
 -      sed -e 1q < "$TODO" >> "$DONE"
 -      sed -e 1d < "$TODO" >> "$TODO".new
 -      mv -f "$TODO".new "$TODO"
 -      count=$(sane_grep -c '^[^#]' < "$DONE")
 -      total=$(($count+$(sane_grep -c '^[^#]' < "$TODO")))
 -      if test "$last_count" != "$count"
 +      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")))
 +      if test "$last_count" != "$new_count"
        then
 -              last_count=$count
 -              printf "Rebasing (%d/%d)\r" $count $total
 -              test -z "$VERBOSE" || echo
 +              last_count=$new_count
 +              printf "Rebasing (%d/%d)\r" $new_count $total
 +              test -z "$verbose" || echo
        fi
  }
  
@@@ -129,22 -193,22 +129,22 @@@ make_patch () 
        *)
                echo "Root commit"
                ;;
 -      esac > "$DOTEST"/patch
 -      test -f "$MSG" ||
 -              commit_message "$1" > "$MSG"
 -      test -f "$AUTHOR_SCRIPT" ||
 -              get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT"
 +      esac > "$state_dir"/patch
 +      test -f "$msg" ||
 +              commit_message "$1" > "$msg"
 +      test -f "$author_script" ||
 +              get_author_ident_from_commit "$1" > "$author_script"
  }
  
  die_with_patch () {
 -      echo "$1" > "$DOTEST"/stopped-sha
 +      echo "$1" > "$state_dir"/stopped-sha
        make_patch "$1"
        git rerere
        die "$2"
  }
  
  die_abort () {
 -      rm -rf "$DOTEST"
 +      rm -rf "$state_dir"
        die "$1"
  }
  
@@@ -164,10 -228,15 +164,10 @@@ do_with_author () 
  pick_one () {
        ff=--ff
        case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 -      case "$NEVER_FF" in '') ;; ?*) ff= ;; esac
 +      case "$force_rebase" in '') ;; ?*) ff= ;; esac
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
 -      test -d "$REWRITTEN" &&
 +      test -d "$rewritten" &&
                pick_one_preserving_merges "$@" && return
 -      if test -n "$REBASE_ROOT"
 -      then
 -              output git cherry-pick "$@"
 -              return
 -      fi
        output git cherry-pick $ff "$@"
  }
  
@@@ -184,20 -253,20 +184,20 @@@ pick_one_preserving_merges () 
        esac
        sha1=$(git rev-parse $sha1)
  
 -      if test -f "$DOTEST"/current-commit
 +      if test -f "$state_dir"/current-commit
        then
                if test "$fast_forward" = t
                then
                        while read current_commit
                        do
 -                              git rev-parse HEAD > "$REWRITTEN"/$current_commit
 -                      done <"$DOTEST"/current-commit
 -                      rm "$DOTEST"/current-commit ||
 +                              git rev-parse HEAD > "$rewritten"/$current_commit
 +                      done <"$state_dir"/current-commit
 +                      rm "$state_dir"/current-commit ||
                        die "Cannot write current commit's replacement sha1"
                fi
        fi
  
 -      echo $sha1 >> "$DOTEST"/current-commit
 +      echo $sha1 >> "$state_dir"/current-commit
  
        # rewrite parents; if none were rewritten, we can fast-forward.
        new_parents=
                p=$(expr "$pend" : ' \([^ ]*\)')
                pend="${pend# $p}"
  
 -              if test -f "$REWRITTEN"/$p
 +              if test -f "$rewritten"/$p
                then
 -                      new_p=$(cat "$REWRITTEN"/$p)
 +                      new_p=$(cat "$rewritten"/$p)
  
                        # If the todo reordered commits, and our parent is marked for
                        # rewriting, but hasn't been gotten to yet, assume the user meant to
                                ;;
                        esac
                else
 -                      if test -f "$DROPPED"/$p
 +                      if test -f "$dropped"/$p
                        then
                                fast_forward=f
 -                              replacement="$(cat "$DROPPED"/$p)"
 +                              replacement="$(cat "$dropped"/$p)"
                                test -z "$replacement" && replacement=root
                                pend=" $replacement$pend"
                        else
                        test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
  
                        # redo merge
 -                      author_script=$(get_author_ident_from_commit $sha1)
 -                      eval "$author_script"
 -                      msg="$(commit_message $sha1)"
 +                      author_script_content=$(get_author_ident_from_commit $sha1)
 +                      eval "$author_script_content"
 +                      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 ${strategy:+-s $strategy} -m \
 -                              git merge --no-ff $STRATEGY -m "$msg" $new_parents
++                              git merge --no-ff ${strategy:+-s $strategy} -m \
 +                                      "$msg_content" $new_parents
                        then
 -                              printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 +                              printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
                                die_with_patch $sha1 "Error redoing merge $sha1"
                        fi
 -                      echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 +                      echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
                        ;;
                *)
                        output git cherry-pick "$@" ||
@@@ -297,46 -365,46 +297,46 @@@ nth_string () 
  }
  
  update_squash_messages () {
 -      if test -f "$SQUASH_MSG"; then
 -              mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit
 -              COUNT=$(($(sed -n \
 +      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 "q" < "$SQUASH_MSG".bak)+1))
 +                      -e "q" < "$squash_msg".bak)+1))
                {
 -                      echo "# This is a combination of $COUNT commits."
 +                      echo "# This is a combination of $count commits."
                        sed -e 1d -e '2,/^./{
                                /^$/d
 -                      }' <"$SQUASH_MSG".bak
 -              } >"$SQUASH_MSG"
 +                      }' <"$squash_msg".bak
 +              } >"$squash_msg"
        else
 -              commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG"
 -              COUNT=2
 +              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:"
                        echo
 -                      cat "$FIXUP_MSG"
 -              } >"$SQUASH_MSG"
 +                      cat "$fixup_msg"
 +              } >"$squash_msg"
        fi
        case $1 in
        squash)
 -              rm -f "$FIXUP_MSG"
 +              rm -f "$fixup_msg"
                echo
 -              echo "# This is the $(nth_string $COUNT) commit message:"
 +              echo "# This is the $(nth_string $count) commit message:"
                echo
                commit_message $2
                ;;
        fixup)
                echo
 -              echo "# The $(nth_string $COUNT) commit message will be skipped:"
 +              echo "# The $(nth_string $count) commit message will be skipped:"
                echo
                commit_message $2 | sed -e 's/^/#       /'
                ;;
 -      esac >>"$SQUASH_MSG"
 +      esac >>"$squash_msg"
  }
  
  peek_next_command () {
 -      sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO"
 +      sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$todo"
  }
  
  # A squash/fixup has failed.  Prepare the long version of the squash
  # messages, effectively causing the combined commit to be used as the
  # new basis for any further squash/fixups.  Args: sha1 rest
  die_failed_squash() {
 -      mv "$SQUASH_MSG" "$MSG" || exit
 -      rm -f "$FIXUP_MSG"
 -      cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit
 +      mv "$squash_msg" "$msg" || exit
 +      rm -f "$fixup_msg"
 +      cp "$msg" "$GIT_DIR"/MERGE_MSG || exit
        warn
        warn "Could not apply $1... $2"
        die_with_patch $1 ""
  }
  
  flush_rewritten_pending() {
 -      test -s "$REWRITTEN_PENDING" || return
 +      test -s "$rewritten_pending" || return
        newsha1="$(git rev-parse HEAD^0)"
 -      sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
 -      rm -f "$REWRITTEN_PENDING"
 +      sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list"
 +      rm -f "$rewritten_pending"
  }
  
  record_in_rewritten() {
        oldsha1="$(git rev-parse $1)"
 -      echo "$oldsha1" >> "$REWRITTEN_PENDING"
 +      echo "$oldsha1" >> "$rewritten_pending"
  
        case "$(peek_next_command)" in
        squash|s|fixup|f)
  }
  
  do_next () {
 -      rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 -      read -r command sha1 rest < "$TODO"
 +      rm -f "$msg" "$author_script" "$amend" || exit
 +      read -r command sha1 rest < "$todo"
        case "$command" in
        '#'*|''|noop)
                mark_action_done
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
 -              echo "$sha1" > "$DOTEST"/stopped-sha
 +              echo "$sha1" > "$state_dir"/stopped-sha
                make_patch $sha1
 -              git rev-parse --verify HEAD > "$AMEND"
 +              git rev-parse --verify HEAD > "$amend"
                warn "Stopped at $sha1... $rest"
                warn "You can amend the commit now, with"
                warn
                esac
                comment_for_reflog $squash_style
  
 -              test -f "$DONE" && has_action "$DONE" ||
 +              test -f "$done" && has_action "$done" ||
                        die "Cannot '$squash_style' without a previous commit"
  
                mark_action_done
                update_squash_messages $squash_style $sha1
 -              author_script=$(get_author_ident_from_commit HEAD)
 -              echo "$author_script" > "$AUTHOR_SCRIPT"
 -              eval "$author_script"
 +              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"
                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 --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"
 +                      if test -f "$fixup_msg"
                        then
 -                              do_with_author git commit --no-verify -F "$FIXUP_MSG" ||
 +                              do_with_author git commit --no-verify -F "$fixup_msg" ||
                                        die_failed_squash $sha1 "$rest"
                        else
 -                              cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit
 +                              cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
                                rm -f "$GIT_DIR"/MERGE_MSG
                                do_with_author git commit --no-verify -e ||
                                        die_failed_squash $sha1 "$rest"
                        fi
 -                      rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 +                      rm -f "$squash_msg" "$fixup_msg"
                        ;;
                esac
                record_in_rewritten $sha1
                ;;
        x|"exec")
 -              read -r command rest < "$TODO"
 +              read -r command rest < "$todo"
                mark_action_done
                printf 'Executing: %s\n' "$rest"
                # "exec" command doesn't take a sha1 in the todo-list.
                # => can't just use $sha1 here.
 -              git rev-parse --verify HEAD > "$DOTEST"/stopped-sha
 +              git rev-parse --verify HEAD > "$state_dir"/stopped-sha
                ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
                status=$?
                if test "$status" -ne 0
                warn "Unknown command: $command $sha1 $rest"
                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 "Please fix this in the file $todo."
                else
 -                      die "Please fix this in the file $TODO."
 +                      die "Please fix this in the file $todo."
                fi
                ;;
        esac
 -      test -s "$TODO" && return
 +      test -s "$todo" && return
  
        comment_for_reflog finish &&
 -      HEADNAME=$(cat "$DOTEST"/head-name) &&
 -      OLDHEAD=$(cat "$DOTEST"/head) &&
 -      SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
 -      NEWHEAD=$(git rev-parse HEAD) &&
 -      case $HEADNAME in
 +      shortonto=$(git rev-parse --short $onto) &&
 +      newhead=$(git rev-parse HEAD) &&
 +      case $head_name in
        refs/*)
 -              message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO" &&
 -              git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
 -              git symbolic-ref HEAD $HEADNAME
 +              message="$GIT_REFLOG_ACTION: $head_name onto $shortonto" &&
 +              git update-ref -m "$message" $head_name $newhead $orig_head &&
 +              git symbolic-ref HEAD $head_name
                ;;
        esac && {
 -              test ! -f "$DOTEST"/verbose ||
 -                      git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 +              test ! -f "$state_dir"/verbose ||
 +                      git diff-tree --stat $orig_head..HEAD
        } &&
        {
 -              test -s "$REWRITTEN_LIST" &&
 -              git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
 +              test -s "$rewritten_list" &&
 +              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"
 +              test -s "$rewritten_list"; then
 +              "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
                true # we don't care if this hook failed
        fi &&
 -      rm -rf "$DOTEST" &&
 +      rm -rf "$state_dir" &&
        git gc --auto &&
 -      warn "Successfully rebased and updated $HEADNAME."
 +      warn "Successfully rebased and updated $head_name."
  
        exit
  }
@@@ -548,11 -618,11 +548,11 @@@ skip_unnecessary_picks () 
                # fd=3 means we skip the command
                case "$fd,$command" in
                3,pick|3,p)
 -                      # pick a commit whose parent is current $ONTO -> skip
 +                      # pick a commit whose parent is current $onto -> skip
                        sha1=${rest%% *}
                        case "$(git rev-parse --verify --quiet "$sha1"^)" in
 -                      "$ONTO"*)
 -                              ONTO=$sha1
 +                      "$onto"*)
 +                              onto=$sha1
                                ;;
                        *)
                                fd=1
                        ;;
                esac
                printf '%s\n' "$command${rest:+ }$rest" >&$fd
 -      done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
 -      mv -f "$TODO".new "$TODO" &&
 +      done <"$todo" >"$todo.new" 3>>"$done" &&
 +      mv -f "$todo".new "$todo" &&
        case "$(peek_next_command)" in
        squash|s|fixup|f)
 -              record_in_rewritten "$ONTO"
 +              record_in_rewritten "$onto"
                ;;
        esac ||
        die "Could not skip unnecessary pick commands"
  }
  
 -# check if no other options are set
 -is_standalone () {
 -      test $# -eq 2 -a "$2" = '--' &&
 -      test -z "$ONTO" &&
 -      test -z "$PRESERVE_MERGES" &&
 -      test -z "$STRATEGY" &&
 -      test -z "$VERBOSE"
 -}
 -
 -get_saved_options () {
 -      test -d "$REWRITTEN" && PRESERVE_MERGES=t
 -      test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
 -      test -f "$DOTEST"/verbose && VERBOSE=t
 -      test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
 -}
 -
  # 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
@@@ -613,7 -699,7 +613,7 @@@ rearrange_squash () 
                esac
                printf '%s\n' "$pick $sha1 $message"
                used="$used$sha1 "
 -              while read -r squash action msg
 +              while read -r squash action msg_content
                do
                        case " $used" in
                        *" $squash "*) continue ;;
                        +*)
                                action="${action#+}"
                                # full sha1 prefix test
 -                              case "$msg" in "$sha1"*) emit=1;; esac ;;
 +                              case "$msg_content" in "$sha1"*) emit=1;; esac ;;
                        *)
                                # message prefix test
 -                              case "$message" in "$msg"*) emit=1;; esac ;;
 +                              case "$message" in "$msg_content"*) emit=1;; esac ;;
                        esac
                        if test $emit = 1; then
 -                              printf '%s\n' "$action $squash $action! $msg"
 +                              printf '%s\n' "$action $squash $action! $msg_content"
                                used="$used$squash "
                        fi
                done <"$1.sq"
        rm -f "$1.sq" "$1.rearranged"
  }
  
 -LF='
 -'
 -parse_onto () {
 -      case "$1" in
 -      *...*)
 -              if      left=${1%...*} right=${1#*...} &&
 -                      onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
 -              then
 -                      case "$onto" in
 -                      ?*"$LF"?* | '')
 -                              exit 1 ;;
 -                      esac
 -                      echo "$onto"
 -                      exit 0
 -              fi
 -      esac
 -      git rev-parse --verify "$1^0"
 -}
 -
 -while test $# != 0
 -do
 -      case "$1" in
 -      --no-verify)
 -              OK_TO_SKIP_PRE_REBASE=yes
 -              ;;
 -      --verify)
 -              OK_TO_SKIP_PRE_REBASE=
 -              ;;
 -      --continue)
 -              is_standalone "$@" || usage
 -              get_saved_options
 -              comment_for_reflog continue
 -
 -              test -d "$DOTEST" || die "No interactive rebase running"
 -
 -              # Sanity check
 -              git rev-parse --verify HEAD >/dev/null ||
 -                      die "Cannot read HEAD"
 -              git update-index --ignore-submodules --refresh &&
 -                      git diff-files --quiet --ignore-submodules ||
 -                      die "Working tree is dirty"
 -
 -              # do we have anything to commit?
 -              if git diff-index --cached --quiet --ignore-submodules HEAD --
 +case "$action" in
 +continue)
 +      # do we have anything to commit?
 +      if git diff-index --cached --quiet --ignore-submodules HEAD --
 +      then
 +              : Nothing to commit -- skip this
 +      else
 +              . "$author_script" ||
 +                      die "Cannot find the author identity"
 +              current_head=
 +              if test -f "$amend"
                then
 -                      : Nothing to commit -- skip this
 -              else
 -                      . "$AUTHOR_SCRIPT" ||
 -                              die "Cannot find the author identity"
 -                      amend=
 -                      if test -f "$AMEND"
 -                      then
 -                              amend=$(git rev-parse --verify HEAD)
 -                              test "$amend" = $(cat "$AMEND") ||
 -                              die "\
 +                      current_head=$(git rev-parse --verify HEAD)
 +                      test "$current_head" = $(cat "$amend") ||
 +                      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"
 -                      fi
 -                      do_with_author git commit --no-verify -F "$MSG" -e || {
 -                              test -n "$amend" && git reset --soft $amend
 -                              die "Could not commit staged changes."
 -                      }
 +                      git reset --soft HEAD^ ||
 +                      die "Cannot rewind the HEAD"
                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 "$DOTEST"/stopped-sha)"
 -
 -              require_clean_work_tree "rebase"
 -              do_rest
 -              ;;
 -      --abort)
 -              is_standalone "$@" || usage
 -              get_saved_options
 -              comment_for_reflog abort
 -
 -              git rerere clear
 -              test -d "$DOTEST" || die "No interactive rebase running"
 -
 -              HEADNAME=$(cat "$DOTEST"/head-name)
 -              HEAD=$(cat "$DOTEST"/head)
 -              case $HEADNAME in
 -              refs/*)
 -                      git symbolic-ref HEAD $HEADNAME
 -                      ;;
 -              esac &&
 -              output git reset --hard $HEAD &&
 -              rm -rf "$DOTEST"
 -              exit
 -              ;;
 -      --skip)
 -              is_standalone "$@" || usage
 -              get_saved_options
 -              comment_for_reflog skip
 -
 -              git rerere clear
 -              test -d "$DOTEST" || die "No interactive rebase running"
 -
 -              output git reset --hard && do_rest
 -              ;;
 -      -s)
 -              case "$#,$1" in
 -              *,*=*)
 -                      STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
 -              1,*)
 -                      usage ;;
 -              *)
 -                      STRATEGY="-s $2"
 -                      shift ;;
 -              esac
 -              ;;
 -      -m)
 -              # we use merge anyway
 -              ;;
 -      -v)
 -              VERBOSE=t
 -              ;;
 -      -p)
 -              PRESERVE_MERGES=t
 -              ;;
 -      -i)
 -              # yeah, we know
 -              ;;
 -      --no-ff)
 -              NEVER_FF=t
 -              ;;
 -      --root)
 -              REBASE_ROOT=t
 -              ;;
 -      --autosquash)
 -              AUTOSQUASH=t
 -              ;;
 -      --no-autosquash)
 -              AUTOSQUASH=
 -              ;;
 -      --onto)
 -              shift
 -              ONTO=$(parse_onto "$1") ||
 -                      die "Does not point to a valid commit: $1"
 -              ;;
 -      --)
 -              shift
 -              test -z "$REBASE_ROOT" -a $# -ge 1 -a $# -le 2 ||
 -              test ! -z "$REBASE_ROOT" -a $# -le 1 || usage
 -              test -d "$DOTEST" &&
 -                      die "Interactive rebase already started"
 -
 -              git var GIT_COMMITTER_IDENT >/dev/null ||
 -                      die "You need to set your committer info first"
 +      record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
  
 -              if test -z "$REBASE_ROOT"
 -              then
 -                      UPSTREAM_ARG="$1"
 -                      UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
 -                      test -z "$ONTO" && ONTO=$UPSTREAM
 -                      shift
 -              else
 -                      UPSTREAM=
 -                      UPSTREAM_ARG=--root
 -                      test -z "$ONTO" &&
 -                              die "You must specify --onto when using --root"
 -              fi
 -              run_pre_rebase_hook "$UPSTREAM_ARG" "$@"
 +      require_clean_work_tree "rebase"
 +      do_rest
 +      ;;
 +skip)
 +      git rerere clear
  
 -              comment_for_reflog start
 +      do_rest
 +      ;;
 +esac
  
 -              require_clean_work_tree "rebase" "Please commit or stash them."
 +git var GIT_COMMITTER_IDENT >/dev/null ||
 +      die "You need to set your committer info first"
  
 -              if test ! -z "$1"
 -              then
 -                      output git checkout "$1" -- ||
 -                              die "Could not checkout $1"
 -              fi
 +comment_for_reflog start
  
 -              HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
 -              mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
 +if test ! -z "$switch_to"
 +then
 +      output git checkout "$switch_to" -- ||
 +              die "Could not checkout $switch_to"
 +fi
  
 -              : > "$DOTEST"/interactive || die "Could not mark as interactive"
 -              git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
 -                      echo "detached HEAD" > "$DOTEST"/head-name
 +orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?"
 +mkdir "$state_dir" || die "Could not create temporary $state_dir"
  
 -              echo $HEAD > "$DOTEST"/head
 -              case "$REBASE_ROOT" in
 -              '')
 -                      rm -f "$DOTEST"/rebase-root ;;
 -              *)
 -                      : >"$DOTEST"/rebase-root ;;
 -              esac
 -              echo $ONTO > "$DOTEST"/onto
 -              test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
 -              test t = "$VERBOSE" && : > "$DOTEST"/verbose
 -              if test t = "$PRESERVE_MERGES"
 -              then
 -                      if test -z "$REBASE_ROOT"
 -                      then
 -                              mkdir "$REWRITTEN" &&
 -                              for c in $(git merge-base --all $HEAD $UPSTREAM)
 -                              do
 -                                      echo $ONTO > "$REWRITTEN"/$c ||
 -                                              die "Could not init rewritten commits"
 -                              done
 -                      else
 -                              mkdir "$REWRITTEN" &&
 -                              echo $ONTO > "$REWRITTEN"/root ||
 -                                      die "Could not init rewritten commits"
 -                      fi
 -                      # No cherry-pick because our first pass is to determine
 -                      # parents to rewrite and skipping dropped commits would
 -                      # prematurely end our probe
 -                      MERGES_OPTION=
 -                      first_after_upstream="$(git rev-list --reverse --first-parent $UPSTREAM..$HEAD | head -n 1)"
 -              else
 -                      MERGES_OPTION="--no-merges --cherry-pick"
 -              fi
 -
 -              SHORTHEAD=$(git rev-parse --short $HEAD)
 -              SHORTONTO=$(git rev-parse --short $ONTO)
 -              if test -z "$REBASE_ROOT"
 -                      # this is now equivalent to ! -z "$UPSTREAM"
 -              then
 -                      SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
 -                      REVISIONS=$UPSTREAM...$HEAD
 -                      SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD
 -              else
 -                      REVISIONS=$ONTO...$HEAD
 -                      SHORTREVISIONS=$SHORTHEAD
 -              fi
 -              git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
 -                      --abbrev=7 --reverse --left-right --topo-order \
 -                      $REVISIONS | \
 -                      sed -n "s/^>//p" |
 -              while read -r shortsha1 rest
 +: > "$state_dir"/interactive || die "Could not mark as interactive"
 +write_basic_state
 +if test t = "$preserve_merges"
 +then
 +      if test -z "$rebase_root"
 +      then
 +              mkdir "$rewritten" &&
 +              for c in $(git merge-base --all $orig_head $upstream)
                do
 -                      if test t != "$PRESERVE_MERGES"
 -                      then
 -                              printf '%s\n' "pick $shortsha1 $rest" >> "$TODO"
 -                      else
 -                              sha1=$(git rev-parse $shortsha1)
 -                              if test -z "$REBASE_ROOT"
 -                              then
 -                                      preserve=t
 -                                      for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
 -                                      do
 -                                              if test -f "$REWRITTEN"/$p -a \( $p != $ONTO -o $sha1 = $first_after_upstream \)
 -                                              then
 -                                                      preserve=f
 -                                              fi
 -                                      done
 -                              else
 -                                      preserve=f
 -                              fi
 -                              if test f = "$preserve"
 -                              then
 -                                      touch "$REWRITTEN"/$sha1
 -                                      printf '%s\n' "pick $shortsha1 $rest" >> "$TODO"
 -                              fi
 -                      fi
 +                      echo $onto > "$rewritten"/$c ||
 +                              die "Could not init rewritten commits"
                done
 -
 -              # Watch for commits that been dropped by --cherry-pick
 -              if test t = "$PRESERVE_MERGES"
 +      else
 +              mkdir "$rewritten" &&
 +              echo $onto > "$rewritten"/root ||
 +                      die "Could not init rewritten commits"
 +      fi
 +      # No cherry-pick because our first pass is to determine
 +      # parents to rewrite and skipping dropped commits would
 +      # prematurely end our probe
 +      merges_option=
 +      first_after_upstream="$(git rev-list --reverse --first-parent $upstream..$orig_head | head -n 1)"
 +else
 +      merges_option="--no-merges --cherry-pick"
 +fi
 +
 +shorthead=$(git rev-parse --short $orig_head)
 +shortonto=$(git rev-parse --short $onto)
 +if test -z "$rebase_root"
 +      # this is now equivalent to ! -z "$upstream"
 +then
 +      shortupstream=$(git rev-parse --short $upstream)
 +      revisions=$upstream...$orig_head
 +      shortrevisions=$shortupstream..$shorthead
 +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 | \
 +      sed -n "s/^>//p" |
 +while read -r shortsha1 rest
 +do
 +      if test t != "$preserve_merges"
 +      then
 +              printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
 +      else
 +              sha1=$(git rev-parse $shortsha1)
 +              if test -z "$rebase_root"
                then
 -                      mkdir "$DROPPED"
 -                      # Save all non-cherry-picked changes
 -                      git rev-list $REVISIONS --left-right --cherry-pick | \
 -                              sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
 -                      # Now all commits and note which ones are missing in
 -                      # not-cherry-picks and hence being dropped
 -                      git rev-list $REVISIONS |
 -                      while read rev
 +                      preserve=t
 +                      for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
                        do
 -                              if test -f "$REWRITTEN"/$rev -a "$(sane_grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
 +                              if test -f "$rewritten"/$p -a \( $p != $onto -o $sha1 = $first_after_upstream \)
                                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"
 -                                      rm "$REWRITTEN"/$rev
 +                                      preserve=f
                                fi
                        done
 +              else
 +                      preserve=f
 +              fi
 +              if test f = "$preserve"
 +              then
 +                      touch "$rewritten"/$sha1
 +                      printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
                fi
 +      fi
 +done
  
 -              test -s "$TODO" || echo noop >> "$TODO"
 -              test -n "$AUTOSQUASH" && rearrange_squash "$TODO"
 -              cat >> "$TODO" << EOF
 +# Watch for commits that been dropped by --cherry-pick
 +if test t = "$preserve_merges"
 +then
 +      mkdir "$dropped"
 +      # Save all non-cherry-picked changes
 +      git rev-list $revisions --left-right --cherry-pick | \
 +              sed -n "s/^>//p" > "$state_dir"/not-cherry-picks
 +      # Now all commits and note which ones are missing in
 +      # not-cherry-picks and hence being dropped
 +      git rev-list $revisions |
 +      while read rev
 +      do
 +              if test -f "$rewritten"/$rev -a "$(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"
 +                      rm "$rewritten"/$rev
 +              fi
 +      done
 +fi
 +
 +test -s "$todo" || echo noop >> "$todo"
 +test -n "$autosquash" && rearrange_squash "$todo"
 +cat >> "$todo" << EOF
  
 -# Rebase $SHORTREVISIONS onto $SHORTONTO
 +# Rebase $shortrevisions onto $shortonto
  #
  # Commands:
  #  p, pick = use commit
  #
  EOF
  
 -              has_action "$TODO" ||
 -                      die_abort "Nothing to do"
 +has_action "$todo" ||
 +      die_abort "Nothing to do"
  
 -              cp "$TODO" "$TODO".backup
 -              git_editor "$TODO" ||
 -                      die_abort "Could not execute editor"
 +cp "$todo" "$todo".backup
 +git_editor "$todo" ||
 +      die_abort "Could not execute editor"
  
 -              has_action "$TODO" ||
 -                      die_abort "Nothing to do"
 +has_action "$todo" ||
 +      die_abort "Nothing to do"
  
 -              test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks
 +test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
  
 -              output git checkout $ONTO || die_abort "could not detach HEAD"
 -              git update-ref ORIG_HEAD $HEAD
 -              do_rest
 -              ;;
 -      esac
 -      shift
 -done
 +output git checkout $onto || die_abort "could not detach HEAD"
 +git update-ref ORIG_HEAD $orig_head
 +do_rest