Merge branch 'mb/rebase-i-no-ff'
authorJunio C Hamano <gitster@pobox.com>
Sat, 3 Apr 2010 19:28:44 +0000 (12:28 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 3 Apr 2010 19:28:44 +0000 (12:28 -0700)
* mb/rebase-i-no-ff:
Teach rebase the --no-ff option.

Conflicts:
git-rebase--interactive.sh
t/t3404-rebase-interactive.sh

1  2 
git-rebase--interactive.sh
git-rebase.sh
t/t3404-rebase-interactive.sh
index 2ff211cbaa928275857577535f02ae92cb407f2f,d5468b047854f9992517c1c4c7a293e4cc320ddf..b817c4a76e54c31f5a21d8196729f0cfb31997cf
@@@ -20,6 -20,7 +20,7 @@@ v,verbose          display a diffstat o
  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:
@@@ -96,13 -97,6 +97,13 @@@ AUTHOR_SCRIPT="$DOTEST"/author-scrip
  # command is processed, this file is deleted.
  AMEND="$DOTEST"/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=
@@@ -110,6 -104,7 +111,7 @@@ VERBOSE
  OK_TO_SKIP_PRE_REBASE=
  REBASE_ROOT=
  AUTOSQUASH=
+ NEVER_FF=
  
  GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
  mark the corrected paths with 'git add <paths>', and
@@@ -205,7 -200,6 +207,7 @@@ make_patch () 
  }
  
  die_with_patch () {
 +      echo "$1" > "$DOTEST"/stopped-sha
        make_patch "$1"
        git rerere
        die "$2"
@@@ -230,8 -224,8 +232,9 @@@ do_with_author () 
  }
  
  pick_one () {
 -      no_ff=$NEVER_FF
 -      case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
 +      ff=--ff
 +      case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
++      case "$NEVER_FF" in '') ;; ?*) ff= ;; esac
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
        test -d "$REWRITTEN" &&
                pick_one_preserving_merges "$@" && return
                output git cherry-pick "$@"
                return
        fi
 -      parent_sha1=$(git rev-parse --verify $sha1^) ||
 -              die "Could not get the parent of $sha1"
 -      current_sha1=$(git rev-parse --verify HEAD)
 -      if test -z "$no_ff" && test "$current_sha1" = "$parent_sha1"
 -      then
 -              output git reset --hard $sha1
 -              output warn Fast-forward to $(git rev-parse --short $sha1)
 -      else
 -              output git cherry-pick "$@"
 -      fi
 +      output git cherry-pick $ff "$@"
  }
  
  pick_one_preserving_merges () {
                                printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
                                die_with_patch $sha1 "Error redoing merge $sha1"
                        fi
 +                      echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
                        ;;
                *)
                        output git cherry-pick "$@" ||
@@@ -425,26 -427,6 +428,26 @@@ die_failed_squash() 
        die_with_patch $1 ""
  }
  
 +flush_rewritten_pending() {
 +      test -s "$REWRITTEN_PENDING" || return
 +      newsha1="$(git rev-parse HEAD^0)"
 +      sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
 +      rm -f "$REWRITTEN_PENDING"
 +}
 +
 +record_in_rewritten() {
 +      oldsha1="$(git rev-parse $1)"
 +      echo "$oldsha1" >> "$REWRITTEN_PENDING"
 +
 +      case "$(peek_next_command)" in
 +          squash|s|fixup|f)
 +              ;;
 +          *)
 +              flush_rewritten_pending
 +              ;;
 +      esac
 +}
 +
  do_next () {
        rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
        read command sha1 rest < "$TODO"
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $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"
 -              git commit --amend
 +              git commit --amend --no-post-rewrite
 +              record_in_rewritten $sha1
                ;;
        edit|e)
                comment_for_reflog edit
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
 +              echo "$sha1" > "$DOTEST"/stopped-sha
                make_patch $sha1
                git rev-parse --verify HEAD > "$AMEND"
                warn "Stopped at $sha1... $rest"
                        rm -f "$SQUASH_MSG" "$FIXUP_MSG"
                        ;;
                esac
 +              record_in_rewritten $sha1
                ;;
        *)
                warn "Unknown command: $command $sha1 $rest"
                test ! -f "$DOTEST"/verbose ||
                        git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
        } &&
 +      {
 +              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"
 +              true # we don't care if this hook failed
 +      fi &&
        rm -rf "$DOTEST" &&
        git gc --auto &&
        warn "Successfully rebased and updated $HEADNAME."
@@@ -604,12 -573,7 +607,12 @@@ skip_unnecessary_picks () 
                esac
                echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
        done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
 -      mv -f "$TODO".new "$TODO" ||
 +      mv -f "$TODO".new "$TODO" &&
 +      case "$(peek_next_command)" in
 +      squash|s|fixup|f)
 +              record_in_rewritten "$ONTO"
 +              ;;
 +      esac ||
        die "Could not skip unnecessary pick commands"
  }
  
@@@ -725,8 -689,6 +728,8 @@@ first and then run 'git rebase --contin
                        }
                fi
  
 +              record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
 +
                require_clean_work_tree
                do_rest
                ;;
        -i)
                # yeah, we know
                ;;
+       --no-ff)
+               NEVER_FF=t
+               ;;
        --root)
                REBASE_ROOT=t
                ;;
  
                if test ! -z "$1"
                then
 -                      output git show-ref --verify --quiet "refs/heads/$1" ||
 -                              die "Invalid branchname: $1"
                        output git checkout "$1" ||
                                die "Could not checkout $1"
                fi
@@@ -965,7 -932,7 +971,7 @@@ EO
                has_action "$TODO" ||
                        die_abort "Nothing to do"
  
-               test -d "$REWRITTEN" || skip_unnecessary_picks
+               test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks
  
                git update-ref ORIG_HEAD $HEAD
                output git checkout $ONTO && do_rest
diff --combined git-rebase.sh
index e0eb9568f3d12a068d46196d2dc896dde4d35fd3,8b23f8b7d2e6b75c8646aeeee0207f5d6500b9ef..44f5c65fdb5e81c4b660a666c2edd71aa75c5c1a
@@@ -3,7 -3,7 +3,7 @@@
  # Copyright (c) 2005 Junio C Hamano.
  #
  
- USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
+ USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
  LONG_USAGE='git-rebase replaces <branch> with a new branch of the
  same name.  When the --onto option is provided the new branch starts
  out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@@ -79,7 -79,6 +79,7 @@@ continue_merge () 
                then
                        printf "Committed: %0${prec}d " $msgnum
                fi
 +              echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
        else
                if test -z "$GIT_QUIET"
                then
@@@ -152,11 -151,6 +152,11 @@@ move_to_original_branch () 
  
  finish_rb_merge () {
        move_to_original_branch
 +      git notes copy --for-rewrite=rebase < "$dotest"/rewritten
 +      if test -x "$GIT_DIR"/hooks/post-rewrite &&
 +              test -s "$dotest"/rewritten; then
 +              "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
 +      fi
        rm -r "$dotest"
        say All done.
  }
@@@ -353,7 -347,7 +353,7 @@@ d
        --root)
                rebase_root=t
                ;;
-       -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
+       -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff)
                force_rebase=t
                ;;
        --rerere-autoupdate|--no-rerere-autoupdate)
index b0b43c6d329cacf24fd9ea6d69320e13d34b154c,624e78e982fd432ca9fe1f6482a0d27ef3adc685..f20ea38411d0ca67709dbde0bfd1108e28c0dd71
@@@ -22,12 -22,18 +22,18 @@@ set_fake_edito
  # | \
  # |   F - G - H                (branch1)
  # |     \
- #  \      I                    (branch2)
- #   \
- #     J - K - L - M            (no-conflict-branch)
+ # |\      I                    (branch2)
+ # | \
+ # |   J - K - L - M            (no-conflict-branch)
+ #  \
+ #    N - O - P                 (no-ff-branch)
  #
  # where A, B, D and G all touch file1, and one, two, three, four all
  # touch file "conflict".
+ #
+ # WARNING: Modifications to the initial repository can change the SHA ID used
+ # in the expect2 file for the 'stop on conflicting pick' test.
  
  test_expect_success 'setup' '
        test_commit A file1 &&
        done &&
        git checkout -b no-conflict-branch A &&
        for n in J K L M
+       do
+               test_commit $n file$n
+       done &&
+       git checkout -b no-ff-branch A &&
+       for n in N O P
        do
                test_commit $n file$n
        done
@@@ -113,7 -124,7 +124,7 @@@ cat > expect2 << EO
  D
  =======
  G
- >>>>>>> 51047de... G
+ >>>>>>> 5d18e54... G
  EOF
  
  test_expect_success 'stop on conflicting pick' '
@@@ -553,37 -564,21 +564,54 @@@ test_expect_success 'reword' 
        git show HEAD~2 | grep "C changed"
  '
  
 +test_expect_success 'rebase -i can copy notes' '
 +      git config notes.rewrite.rebase true &&
 +      git config notes.rewriteRef "refs/notes/*" &&
 +      test_commit n1 &&
 +      test_commit n2 &&
 +      test_commit n3 &&
 +      git notes add -m"a note" n3 &&
 +      git rebase --onto n1 n2 &&
 +      test "a note" = "$(git notes show HEAD)"
 +'
 +
 +cat >expect <<EOF
 +an earlier note
 +a note
 +EOF
 +
 +test_expect_success 'rebase -i can copy notes over a fixup' '
 +      git reset --hard n3 &&
 +      git notes add -m"an earlier note" n2 &&
 +      GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
 +      git notes show > output &&
 +      test_cmp expect output
 +'
 +
 +test_expect_success 'rebase while detaching HEAD' '
 +      git symbolic-ref HEAD &&
 +      grandparent=$(git rev-parse HEAD~2) &&
 +      test_tick &&
 +      FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0 &&
 +      test $grandparent = $(git rev-parse HEAD~2) &&
 +      test_must_fail git symbolic-ref HEAD
 +'
 +
+ test_tick # Ensure that the rebased commits get a different timestamp.
+ test_expect_success 'always cherry-pick with --no-ff' '
+       git checkout no-ff-branch &&
+       git tag original-no-ff-branch &&
+       git rebase -i --no-ff A &&
+       touch empty &&
+       for p in 0 1 2
+       do
+               test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) &&
+               git diff HEAD~$p original-no-ff-branch~$p > out &&
+               test_cmp empty out
+       done &&
+       test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
+       git diff HEAD~3 original-no-ff-branch~3 > out &&
+       test_cmp empty out
+ '
  test_done