Merge branch 'ph/rebase-preserve-all-merges'
authorJunio C Hamano <gitster@pobox.com>
Tue, 22 Jan 2013 04:15:15 +0000 (20:15 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 22 Jan 2013 04:15:15 +0000 (20:15 -0800)
An earlier change to add --keep-empty option broke "git rebase
--preserve-merges" and lost merge commits that end up being the
same as its parent.

* ph/rebase-preserve-all-merges:
rebase --preserve-merges: keep all merge commits including empty ones

1  2 
git-rebase--interactive.sh
index 44901d53c43d972e03a71bfbe0b769f2e8f22d7b,2fed92fac1d73440c41f3fa57273d3000d8db3ce..8ed7fccc18507b6f3e2508bb117ce056a764de77
@@@ -9,7 -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
@@@ -115,23 -117,6 +115,23 @@@ mark_action_done () 
        fi
  }
  
 +append_todo_help () {
 +      cat >> "$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
@@@ -190,6 -175,11 +190,11 @@@ is_empty_commit() 
        test "$tree" = "$ptree"
  }
  
+ is_merge_commit()
+ {
+       git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1
+ }
  # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
  # GIT_AUTHOR_DATE exported from the current environment.
  do_with_author () {
@@@ -432,29 -422,6 +437,29 @@@ 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"
                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"
                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
                ;;
                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"
                        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
                ;;
        *)
                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" \
@@@ -726,27 -689,6 +731,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?
@@@ -772,6 -714,7 +777,6 @@@ In both case, once you're done, continu
                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)
                        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)"
@@@ -797,23 -741,6 +802,23 @@@ skip
  
        do_rest
        ;;
 +edit-todo)
 +      sed -e '/^#/d' < "$todo" > "$todo".new
 +      mv -f "$todo".new "$todo"
 +      append_todo_help
 +      cat >> "$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 ||
@@@ -874,7 -801,7 +879,7 @@@ git rev-list $merges_option --pretty=on
  while read -r shortsha1 rest
  do
  
-       if test -z "$keep_empty" && is_empty_commit $shortsha1
+       if test -z "$keep_empty" && is_empty_commit $shortsha1 && ! is_merge_commit $shortsha1
        then
                comment_out="# "
        else
  
  test -s "$todo" || echo noop >> "$todo"
  test -n "$autosquash" && rearrange_squash "$todo"
 +test -n "$cmd" && add_exec_commands "$todo"
 +
  cat >> "$todo" << EOF
  
  # Rebase $shortrevisions onto $shortonto
 +EOF
 +append_todo_help
 +cat >> "$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.
  # However, if you remove everything, the rebase will be aborted.
  #
  EOF