Merge branch 'cw/rebase-i-root'
authorJunio C Hamano <gitster@pobox.com>
Mon, 16 Jul 2012 04:38:41 +0000 (21:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 16 Jul 2012 04:38:42 +0000 (21:38 -0700)
"git rebase [-i] --root $tip" can now be used to rewrite all the
history down to the root.

* cw/rebase-i-root:
t3404: make test 57 work with dash and others
Add tests for rebase -i --root without --onto
rebase -i: support --root without --onto

1  2 
Documentation/git-rebase.txt
git-rebase--interactive.sh
git-rebase.sh
t/t3404-rebase-interactive.sh
t/t3412-rebase-root.sh
index 2d71e4b975db3355c6de9005e77a701e18723539,85b5e4425cded0886712543f1d0453e0e859e3de..b30ed352e5a3f87d24119624966c33b4c194df5a
@@@ -8,9 -8,9 +8,9 @@@ git-rebase - Forward-port local commit
  SYNOPSIS
  --------
  [verse]
 -'git rebase' [-i | --interactive] [options] [--onto <newbase>]
 +'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        [<upstream>] [<branch>]
- 'git rebase' [-i | --interactive] [options] [--exec <cmd>] --onto <newbase>
 -'git rebase' [-i | --interactive] [options] [--onto <newbase>]
++'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
  'git rebase' --continue | --skip | --abort
  
@@@ -210,7 -210,7 +210,7 @@@ rebase.autosquash:
  
  OPTIONS
  -------
 -<newbase>::
 +--onto <newbase>::
        Starting point at which to create the new commits. If the
        --onto option is not specified, the starting point is
        <upstream>.  May be any valid commit, and not just an
@@@ -344,35 -344,15 +344,36 @@@ This uses the `--interactive` machiner
  with the `--interactive` option explicitly is generally not a good
  idea unless you know what you are doing (see BUGS below).
  
 +-x <cmd>::
 +--exec <cmd>::
 +      Append "exec <cmd>" after each line creating a commit in the
 +      final history. <cmd> will be interpreted as one or more shell
 +      commands.
 ++
 +This option can only be used with the `--interactive` option
 +(see INTERACTIVE MODE below).
 ++
 +You may execute several commands by either using one instance of `--exec`
 +with several commands:
 ++
 +      git rebase -i --exec "cmd1 && cmd2 && ..."
 ++
 +or by giving more than one `--exec`:
 ++
 +      git rebase -i --exec "cmd1" --exec "cmd2" --exec ...
 ++
 +If `--autosquash` is used, "exec" lines will not be appended for
 +the intermediate commits, and will only appear at the end of each
 +squash/fixup series.
  
  --root::
        Rebase all commits reachable from <branch>, instead of
        limiting them with an <upstream>.  This allows you to rebase
-       the root commit(s) on a branch.  Must be used with --onto, and
+       the root commit(s) on a branch.  When used with --onto, it
        will skip changes already contained in <newbase> (instead of
-       <upstream>).  When used together with --preserve-merges, 'all'
-       root commits will be rewritten to have <newbase> as parent
+       <upstream>) whereas without --onto it will operate on every change.
+       When used together with both --onto and --preserve-merges,
+       'all' root commits will be rewritten to have <newbase> as parent
        instead.
  
  --autosquash::
@@@ -542,24 -522,6 +543,24 @@@ in `$SHELL`, or the default shell if `$
  use shell features (like "cd", ">", ";" ...). The command is run from
  the root of the working tree.
  
 +----------------------------------
 +$ git rebase -i --exec "make test"
 +----------------------------------
 +
 +This command lets you check that intermediate commits are compilable.
 +The todo list becomes like that:
 +
 +--------------------
 +pick 5928aea one
 +exec make test
 +pick 04d0fda two
 +exec make test
 +pick ba46169 three
 +exec make test
 +pick f4593f9 four
 +exec make test
 +--------------------
 +
  SPLITTING COMMITS
  -----------------
  
index 3a3c3823571162853e4e88876e7646287c7c1af5,fcb5f618da288f782cfab75ea628085594f3a4d1..bef7bc0444bab7b7e511c6e29417ce6db44b77ef
@@@ -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
@@@ -415,6 -417,29 +415,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
                ;;
@@@ -682,27 -704,6 +702,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?
@@@ -876,8 -877,6 +896,8 @@@ f
  
  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
diff --combined git-rebase.sh
index 5bddfa9690e8dfbedd07e31dc7e768b60f156213,bde2be88a0198819401d31224d35059ad167be68..1cd0633b80a8daf00dbc50657ccba228bd578a73
@@@ -3,8 -3,7 +3,8 @@@
  # Copyright (c) 2005 Junio C Hamano.
  #
  
 -USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
 +USAGE='[--interactive | -i] [--exec | -x <cmd>] [-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>
@@@ -31,8 -30,8 +31,8 @@@ Example:       git-rebase master~1 topi
  SUBDIRECTORY_OK=Yes
  OPTIONS_KEEPDASHDASH=
  OPTIONS_SPEC="\
 -git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>]
 -git rebase [-i] [options] [--onto <newbase>] --root [<branch>]
 +git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
- git rebase [-i] [options] [--exec <cmd>] --onto <newbase> --root [<branch>]
++git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
  git-rebase [-i] --continue | --abort | --skip
  --
   Available options are
@@@ -44,7 -43,6 +44,7 @@@ s,strategy=!       use the given merge 
  no-ff!             cherry-pick all commits, even if unchanged
  m,merge!           use merging strategies to rebase
  i,interactive!     let the user edit the list of commits to rebase
 +x,exec=!           add exec lines after each commit of the editable list
  k,keep-empty     preserve empty commits during rebase
  f,force-rebase!    force rebase even if branch is up to date
  X,strategy-option=! pass the argument through to the merge strategy
@@@ -78,7 -76,6 +78,7 @@@ If you would prefer to skip this patch
  To check out the original branch and stop rebasing run \"git rebase --abort\".
  "
  unset onto
 +cmd=
  strategy=
  strategy_opts=
  do_merge=
                onto="$2"
                shift
                ;;
 +      -x)
 +              test 2 -le "$#" || usage
 +              cmd="${cmd}exec $2${LF}"
 +              shift
 +              ;;
        -i)
                interactive_rebase=explicit
                ;;
  done
  test $# -gt 2 && usage
  
 +if test -n "$cmd" &&
 +   test "$interactive_rebase" != explicit
 +then
 +      die "--exec option must be used with --interactive option"
 +fi
 +
  if test -n "$action"
  then
        test -z "$in_progress" && die "No rebase in progress?"
@@@ -378,6 -364,11 +378,11 @@@ and run me again.  I am stopping in cas
  valuable there.'
  fi
  
+ if test -n "$rebase_root" && test -z "$onto"
+ then
+       test -z "$interactive_rebase" && interactive_rebase=implied
+ fi
  if test -n "$interactive_rebase"
  then
        type=interactive
@@@ -411,10 -402,14 +416,15 @@@ the
        die "invalid upstream $upstream_name"
        upstream_arg="$upstream_name"
  else
-       test -z "$onto" && die "You must specify --onto when using --root"
+       if test -z "$onto"
+       then
+               empty_tree=`git hash-object -t tree /dev/null`
+               onto=`git commit-tree $empty_tree </dev/null`
+               squash_onto="$onto"
+       fi
        unset upstream_name
        unset upstream
 +      test $# -gt 1 && usage
        upstream_arg=--root
  fi
  
@@@ -465,7 -460,7 +475,7 @@@ case "$#" i
                die "fatal: no such branch: $1"
        fi
        ;;
 -*)
 +0)
        # Do not need to switch branches, we are already on it.
        if branch_name=`git symbolic-ref -q HEAD`
        then
        fi
        orig_head=$(git rev-parse --verify "${branch_name}^0") || exit
        ;;
 +*)
 +      die "BUG: unexpected number of arguments left to parse"
 +      ;;
  esac
  
  require_clean_work_tree "rebase" "Please commit or stash them."
index 68d61480fbeee024ca463ca0d5771bdfdf980866,060f9d87d26b6c9285bee46d7dc514ea53b1723c..8078db6856ba4428e9d6b13f5e02076da7963b09
@@@ -755,121 -755,35 +755,152 @@@ test_expect_success 'rebase-i history w
        test_cmp expect actual
  '
  
 +
 +test_expect_success 'prepare for rebase -i --exec' '
 +      git checkout master &&
 +      git checkout -b execute &&
 +      test_commit one_exec main.txt one_exec &&
 +      test_commit two_exec main.txt two_exec &&
 +      test_commit three_exec main.txt three_exec
 +'
 +
 +
 +test_expect_success 'running "git rebase -i --exec git show HEAD"' '
 +      git rebase -i --exec "git show HEAD" HEAD~2 >actual &&
 +      (
 +              FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 +              export FAKE_LINES &&
 +              git rebase -i HEAD~2 >expect
 +      ) &&
 +      sed -e "1,9d" expect >expected &&
 +      test_cmp expected actual
 +'
 +
 +
 +test_expect_success 'running "git rebase --exec git show HEAD -i"' '
 +      git reset --hard execute &&
 +      git rebase --exec "git show HEAD" -i HEAD~2 >actual &&
 +      (
 +              FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 +              export FAKE_LINES &&
 +              git rebase -i HEAD~2 >expect
 +      ) &&
 +      sed -e "1,9d" expect >expected &&
 +      test_cmp expected actual
 +'
 +
 +
 +test_expect_success 'running "git rebase -ix git show HEAD"' '
 +      git reset --hard execute &&
 +      git rebase -ix "git show HEAD" HEAD~2 >actual &&
 +      (
 +              FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 +              export FAKE_LINES &&
 +              git rebase -i HEAD~2 >expect
 +      ) &&
 +      sed -e "1,9d" expect >expected &&
 +      test_cmp expected actual
 +'
 +
 +
 +test_expect_success 'rebase -ix with several <CMD>' '
 +      git reset --hard execute &&
 +      git rebase -ix "git show HEAD; pwd" HEAD~2 >actual &&
 +      (
 +              FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" &&
 +              export FAKE_LINES &&
 +              git rebase -i HEAD~2 >expect
 +      ) &&
 +      sed -e "1,9d" expect >expected &&
 +      test_cmp expected actual
 +'
 +
 +
 +test_expect_success 'rebase -ix with several instances of --exec' '
 +      git reset --hard execute &&
 +      git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual &&
 +      (
 +              FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2
 +                              exec_git_show_HEAD exec_pwd" &&
 +              export FAKE_LINES &&
 +              git rebase -i HEAD~2 >expect
 +      ) &&
 +      sed -e "1,11d" expect >expected &&
 +      test_cmp expected actual
 +'
 +
 +
 +test_expect_success 'rebase -ix with --autosquash' '
 +      git reset --hard execute &&
 +      git checkout -b autosquash &&
 +      echo second >second.txt &&
 +      git add second.txt &&
 +      git commit -m "fixup! two_exec" &&
 +      echo bis >bis.txt &&
 +      git add bis.txt &&
 +      git commit -m "fixup! two_exec" &&
 +      (
 +              git checkout -b autosquash_actual &&
 +              git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual
 +      ) &&
 +      git checkout autosquash &&
 +      (
 +              git checkout -b autosquash_expected &&
 +              FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
 +              export FAKE_LINES &&
 +              git rebase -i HEAD~4 >expect
 +      ) &&
 +      sed -e "1,13d" expect >expected &&
 +      test_cmp expected actual
 +'
 +
 +
 +test_expect_success 'rebase --exec without -i shows error message' '
 +      git reset --hard execute &&
 +      test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual &&
 +      echo "--exec option must be used with --interactive option" >expected &&
 +      test_i18ncmp expected actual
 +'
 +
 +
 +test_expect_success 'rebase -i --exec without <CMD>' '
 +      git reset --hard execute &&
 +      test_must_fail git rebase -i --exec 2>tmp &&
 +      sed -e "1d" tmp >actual &&
 +      test_must_fail git rebase -h >expected &&
 +      test_cmp expected actual &&
 +      git checkout master
 +'
 +
+ test_expect_success 'rebase -i --root re-order and drop commits' '
+       git checkout E &&
+       FAKE_LINES="3 1 2 5" git rebase -i --root &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test B = $(git cat-file commit HEAD^ | sed -ne \$p) &&
+       test A = $(git cat-file commit HEAD^^ | sed -ne \$p) &&
+       test C = $(git cat-file commit HEAD^^^ | sed -ne \$p) &&
+       test 0 = $(git cat-file commit HEAD^^^ | grep -c ^parent\ )
+ '
+ test_expect_success 'rebase -i --root retain root commit author and message' '
+       git checkout A &&
+       echo B >file7 &&
+       git add file7 &&
+       GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
+       FAKE_LINES="2" git rebase -i --root &&
+       git cat-file commit HEAD | grep -q "^author Twerp Snog" &&
+       git cat-file commit HEAD | grep -q "^different author$"
+ '
+ test_expect_success 'rebase -i --root temporary sentinel commit' '
+       git checkout B &&
+       (
+               FAKE_LINES="2" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i --root
+       ) &&
+       git cat-file commit HEAD | grep "^tree 4b825dc642cb" &&
+       git rebase --abort
+ '
  test_done
diff --combined t/t3412-rebase-root.sh
index 1e9d1a737c5369d1b41bfa75f5939ad41004b944,e4f9da8536556eb9b0711d85ba411877fbc31c7e..0b521057283bf106da8ce55f25f61c7ec7e3ad35
@@@ -22,16 -22,6 +22,11 @@@ test_expect_success 'prepare repository
        test_commit 4 B
  '
  
- test_expect_success 'rebase --root expects --onto' '
-       git checkout -B fail other &&
-       test_must_fail git rebase --root
- '
 +test_expect_success 'rebase --root fails with too many args' '
 +      git checkout -B fail other &&
 +      test_must_fail git rebase --onto master --root fail fail
 +'
 +
  test_expect_success 'setup pre-rebase hook' '
        mkdir -p .git/hooks &&
        cat >.git/hooks/pre-rebase <<EOF &&
@@@ -48,7 -38,7 +43,7 @@@ cat > expect <<EO
  EOF
  
  test_expect_success 'rebase --root --onto <newbase>' '
 -      git checkout -b work &&
 +      git checkout -b work other &&
        git rebase --root --onto master &&
        git log --pretty=tformat:"%s" > rebased &&
        test_cmp expect rebased