Merge branch 'en/rebase-consistency'
authorJunio C Hamano <gitster@pobox.com>
Tue, 24 Jul 2018 21:50:43 +0000 (14:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 24 Jul 2018 21:50:43 +0000 (14:50 -0700)
"git rebase" behaved slightly differently depending on which one of
the three backends gets used; this has been documented and an
effort to make them more uniform has begun.

* en/rebase-consistency:
git-rebase: make --allow-empty-message the default
t3401: add directory rename testcases for rebase and am
git-rebase.txt: document behavioral differences between modes
directory-rename-detection.txt: technical docs on abilities and limitations
git-rebase.txt: address confusion between --no-ff vs --force-rebase
git-rebase: error out when incompatible options passed
t3422: new testcases for checking when incompatible options passed
git-rebase.sh: update help messages a bit
git-rebase.txt: document incompatible options

1  2 
Documentation/git-rebase.txt
git-rebase.sh
t/t3404-rebase-interactive.sh
index 091eb53faa708da2015809b477ef3914a8ee249d,9e136ee16e1a11716c8379c7f6434f103100c535..a7850415b1a831eb3d1530dbc71e194b27bd8d59
@@@ -243,11 -243,15 +243,15 @@@ leave out at most one of A and B, in wh
  --keep-empty::
        Keep the commits that do not change anything from its
        parents in the result.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  --allow-empty-message::
        By default, rebasing commits with an empty message will fail.
        This option overrides that behavior, allowing commits with empty
        messages to be rebased.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  --skip::
        Restart the rebasing process by skipping the current patch.
@@@ -271,6 -275,8 +275,8 @@@ branch on top of the <upstream> branch
  conflict happens, the side reported as 'ours' is the so-far rebased
  series, starting with <upstream>, and 'theirs' is the working branch.  In
  other words, the sides are swapped.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  -s <strategy>::
  --strategy=<strategy>::
  +
  Because 'git rebase' replays each commit from the working branch
  on top of the <upstream> branch using the given strategy, using
- the 'ours' strategy simply discards all patches from the <branch>,
+ the 'ours' strategy simply empties all patches from the <branch>,
  which makes little sense.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  -X <strategy-option>::
  --strategy-option=<strategy-option>::
        This implies `--merge` and, if no strategy has been
        specified, `-s recursive`.  Note the reversal of 'ours' and
        'theirs' as noted above for the `-m` option.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  -S[<keyid>]::
  --gpg-sign[=<keyid>]::
        and after each change.  When fewer lines of surrounding
        context exist they all must match.  By default no context is
        ever ignored.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
- -f::
+ --no-ff::
  --force-rebase::
-       Force a rebase even if the current branch is up to date and
-       the command without `--force` would return without doing anything.
+ -f::
+       Individually replay all rebased commits instead of fast-forwarding
+       over the unchanged ones.  This ensures that the entire history of
+       the rebased branch is composed of new commits.
  +
- You may find this (or --no-ff with an interactive rebase) helpful after
- reverting a topic branch merge, as this option recreates the topic branch with
- fresh commits so it can be remerged successfully without needing to "revert
- the reversion" (see the
link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details).
+ You may find this helpful after reverting a topic branch merge, as this option
+ recreates the topic branch with fresh commits so it can be remerged
+ successfully without needing to "revert the reversion" (see the
+ link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for
+ details).
  
  --fork-point::
  --no-fork-point::
@@@ -355,19 -369,22 +369,22 @@@ default is `--no-fork-point`, otherwis
  --whitespace=<option>::
        These flag are passed to the 'git apply' program
        (see linkgit:git-apply[1]) that applies the patch.
-       Incompatible with the --interactive option.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  --committer-date-is-author-date::
  --ignore-date::
        These flags are passed to 'git am' to easily change the dates
        of the rebased commits (see linkgit:git-am[1]).
-       Incompatible with the --interactive option.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  --signoff::
        Add a Signed-off-by: trailer to all the rebased commits. Note
        that if `--interactive` is given then only commits marked to be
-       picked, edited or reworded will have the trailer added. Incompatible
-       with the `--preserve-merges` option.
+       picked, edited or reworded will have the trailer added.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  -i::
  --interactive::
  The commit list format can be changed by setting the configuration option
  rebase.instructionFormat.  A customized instruction format will automatically
  have the long commit hash prepended to the format.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  -r::
  --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
@@@ -404,7 -423,7 +423,7 @@@ It is currently only possible to recrea
  `recursive` merge strategy; Different merge strategies can be used only via
  explicit `exec git merge -s <strategy> [...]` commands.
  +
- See also REBASING MERGES below.
+ See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
  
  -p::
  --preserve-merges::
  This uses the `--interactive` machinery internally, but combining it
  with the `--interactive` option explicitly is generally not a good
  idea unless you know what you are doing (see BUGS below).
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  -x <cmd>::
  --exec <cmd>::
@@@ -437,6 -458,8 +458,8 @@@ squash/fixup series
  +
  This uses the `--interactive` machinery internally, but it can be run
  without an explicit `--interactive`.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  --root::
        Rebase all commits reachable from <branch>, instead of
        When used together with both --onto and --preserve-merges,
        'all' root commits will be rewritten to have <newbase> as parent
        instead.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  --autosquash::
  --no-autosquash::
        too.  The recommended way to create fixup/squash commits is by using
        the `--fixup`/`--squash` options of linkgit:git-commit[1].
  +
- This option is only valid when the `--interactive` option is used.
- +
  If the `--autosquash` option is enabled by default using the
  configuration variable `rebase.autoSquash`, this option can be
  used to override and disable this setting.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  
  --autostash::
  --no-autostash::
        with care: the final stash application after a successful
        rebase might result in non-trivial conflicts.
  
- --no-ff::
-       With --interactive, cherry-pick all rebased commits instead of
-       fast-forwarding over the unchanged ones.  This ensures that the
-       entire history of the rebased branch is composed of new commits.
- +
- Without --interactive, this is a synonym for --force-rebase.
- +
- You may find this helpful after reverting a topic branch merge, as this option
- recreates the topic branch with fresh commits so it can be remerged
- successfully without needing to "revert the reversion" (see the
- link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details).
+ INCOMPATIBLE OPTIONS
+ --------------------
+ git-rebase has many flags that are incompatible with each other,
+ predominantly due to the fact that it has three different underlying
+ implementations:
+  * one based on linkgit:git-am[1] (the default)
+  * one based on git-merge-recursive (merge backend)
+  * one based on linkgit:git-cherry-pick[1] (interactive backend)
+ Flags only understood by the am backend:
+  * --committer-date-is-author-date
+  * --ignore-date
+  * --whitespace
+  * --ignore-whitespace
+  * -C
+ Flags understood by both merge and interactive backends:
+  * --merge
+  * --strategy
+  * --strategy-option
+  * --allow-empty-message
+ Flags only understood by the interactive backend:
+  * --[no-]autosquash
+  * --rebase-merges
+  * --preserve-merges
+  * --interactive
+  * --exec
+  * --keep-empty
+  * --autosquash
+  * --edit-todo
+  * --root when used in combination with --onto
+ Other incompatible flag pairs:
+  * --preserve-merges and --interactive
+  * --preserve-merges and --signoff
+  * --preserve-merges and --rebase-merges
+  * --rebase-merges and --strategy
+  * --rebase-merges and --strategy-option
+ BEHAVIORAL DIFFERENCES
+ -----------------------
+  * empty commits:
+     am-based rebase will drop any "empty" commits, whether the
+     commit started empty (had no changes relative to its parent to
+     start with) or ended empty (all changes were already applied
+     upstream in other commits).
+     merge-based rebase does the same.
+     interactive-based rebase will by default drop commits that
+     started empty and halt if it hits a commit that ended up empty.
+     The `--keep-empty` option exists for interactive rebases to allow
+     it to keep commits that started empty.
+   * directory rename detection:
+     merge-based and interactive-based rebases work fine with
+     directory rename detection.  am-based rebases sometimes do not.
  
  include::merge-strategies.txt[]
  
@@@ -804,7 -885,7 +885,7 @@@ The ripple effect of a "hard case" reco
  case" recovery too!
  
  REBASING MERGES
 ------------------
 +---------------
  
  The interactive rebase command was originally designed to handle
  individual patch series. As such, it makes sense to exclude merge
diff --combined git-rebase.sh
index f3b10c7f62d1aaed445bf2b08ff926f253a49925,031dbd2ec86cd7385c3b392743c18a81c631336f..797344764510e0a2b09bf91af08c528411a9025b
@@@ -20,23 -20,23 +20,23 @@@ onto=!             rebase onto given br
  r,rebase-merges?   try to rebase merges instead of skipping them
  p,preserve-merges! try to recreate merges instead of ignoring them
  s,strategy=!       use the given merge strategy
+ X,strategy-option=! pass the argument through to the merge strategy
  no-ff!             cherry-pick all commits, even if unchanged
+ f,force-rebase!    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
  allow-empty-message allow rebasing commits with empty messages
- f,force-rebase!    force rebase even if branch is up to date
- X,strategy-option=! pass the argument through to the merge strategy
  stat!              display a diffstat of what changed upstream
  n,no-stat!         do not show diffstat of what changed upstream
  verify             allow pre-rebase hook to run
  rerere-autoupdate  allow rerere to update index with resolved conflicts
  root!              rebase all reachable commits up to the root(s)
  autosquash         move commits that begin with squash!/fixup! under -i
+ signoff            add a Signed-off-by: line to each commit
  committer-date-is-author-date! passed to 'git am'
  ignore-date!       passed to 'git am'
- signoff            passed to 'git am'
  whitespace=!       passed to 'git apply'
  ignore-whitespace! passed to 'git apply'
  C=!                passed to 'git apply'
@@@ -95,7 -95,7 +95,7 @@@ rebase_cousins
  preserve_merges=
  autosquash=
  keep_empty=
- allow_empty_message=
+ allow_empty_message=--allow-empty-message
  signoff=
  test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
  case "$(git config --bool commit.gpgsign)" in
@@@ -207,14 -207,7 +207,14 @@@ run_specific_rebase () 
                autosquash=
        fi
        . git-rebase--$type
 -      git_rebase__$type${preserve_merges:+__preserve_merges}
 +
 +      if test -z "$preserve_merges"
 +      then
 +              git_rebase__$type
 +      else
 +              git_rebase__preserve_merges
 +      fi
 +
        ret=$?
        if test $ret -eq 0
        then
@@@ -246,12 -239,7 +246,12 @@@ the
        state_dir="$apply_dir"
  elif test -d "$merge_dir"
  then
 -      if test -f "$merge_dir"/interactive
 +      if test -d "$merge_dir"/rewritten
 +      then
 +              type=preserve-merges
 +              interactive_rebase=explicit
 +              preserve_merges=t
 +      elif test -f "$merge_dir"/interactive
        then
                type=interactive
                interactive_rebase=explicit
@@@ -328,7 -316,7 +328,7 @@@ d
                do_merge=t
                ;;
        --strategy-option=*)
 -              strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}")"
 +              strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}" | sed -e s/^.//)"
                do_merge=t
                test -z "$strategy" && strategy=recursive
                ;;
@@@ -414,14 -402,14 +414,14 @@@ if test -n "$action
  then
        test -z "$in_progress" && die "$(gettext "No rebase in progress?")"
        # Only interactive rebase uses detailed reflog messages
 -      if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase
 +      if test -n "$interactive_rebase" && test "$GIT_REFLOG_ACTION" = rebase
        then
                GIT_REFLOG_ACTION="rebase -i ($action)"
                export GIT_REFLOG_ACTION
        fi
  fi
  
 -if test "$action" = "edit-todo" && test "$type" != "interactive"
 +if test "$action" = "edit-todo" && test -z "$interactive_rebase"
  then
        die "$(gettext "The --edit-todo action can only be used during interactive rebase.")"
  fi
  
  if test -n "$interactive_rebase"
  then
 -      type=interactive
 +      if test -z "$preserve_merges"
 +      then
 +              type=interactive
 +      else
 +              type=preserve-merges
 +      fi
 +
        state_dir="$merge_dir"
  elif test -n "$do_merge"
  then
@@@ -521,6 -503,24 +521,24 @@@ the
        git_format_patch_opt="$git_format_patch_opt --progress"
  fi
  
+ if test -n "$git_am_opt"; then
+       incompatible_opts=$(echo " $git_am_opt " | \
+                           sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/')
+       if test -n "$interactive_rebase"
+       then
+               if test -n "$incompatible_opts"
+               then
+                       die "$(gettext "error: cannot combine interactive options (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto) with am options ($incompatible_opts)")"
+               fi
+       fi
+       if test -n "$do_merge"; then
+               if test -n "$incompatible_opts"
+               then
+                       die "$(gettext "error: cannot combine merge options (--merge, --strategy, --strategy-option) with am options ($incompatible_opts)")"
+               fi
+       fi
+ fi
  if test -n "$signoff"
  then
        test -n "$preserve_merges" &&
        force_rebase=t
  fi
  
+ if test -n "$preserve_merges"
+ then
+       # Note: incompatibility with --signoff handled in signoff block above
+       # Note: incompatibility with --interactive is just a strong warning;
+       #       git-rebase.txt caveats with "unless you know what you are doing"
+       test -n "$rebase_merges" &&
+               die "$(gettext "error: cannot combine '--preserve_merges' with '--rebase-merges'")"
+ fi
+ if test -n "$rebase_merges"
+ then
+       test -n "$strategy_opts" &&
+               die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy-option'")"
+       test -n "$strategy" &&
+               die "$(gettext "error: cannot combine '--rebase_merges' with '--strategy'")"
+ fi
  if test -z "$rebase_root"
  then
        case "$#" in
@@@ -665,7 -682,7 +700,7 @@@ require_clean_work_tree "rebase" "$(get
  # but this should be done only when upstream and onto are the same
  # and if this is not an interactive rebase.
  mb=$(git merge-base "$onto" "$orig_head")
 -if test "$type" != interactive && test "$upstream" = "$onto" &&
 +if test -z "$interactive_rebase" && test "$upstream" = "$onto" &&
        test "$mb" = "$onto" && test -z "$restrict_revision" &&
        # linear history?
        ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
@@@ -709,7 -726,7 +744,7 @@@ the
        GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
  fi
  
 -test "$type" = interactive && run_specific_rebase
 +test -n "$interactive_rebase" && run_specific_rebase
  
  # Detach HEAD and reset the tree
  say "$(gettext "First, rewinding head to replay your work on top of it...")"
index d392160ba9957d9918f59cdfb031f66c624e90f3,81ce9fe7f9c64f98c658c82dfc2cc8312a71b23b..c5d39e2b23f0f257af1553a9f50efc3f6cf0ad5e
@@@ -553,15 -553,16 +553,16 @@@ test_expect_success '--continue tries t
  '
  
  test_expect_success 'aborted --continue does not squash commits after "edit"' '
+       test_when_finished "git rebase --abort" &&
        old=$(git rev-parse HEAD) &&
        test_tick &&
        set_fake_editor &&
        FAKE_LINES="edit 1" git rebase -i HEAD^ &&
        echo "edited again" > file7 &&
        git add file7 &&
-       test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue &&
-       test $old = $(git rev-parse HEAD) &&
-       git rebase --abort
+       echo all the things >>conflict &&
+       test_must_fail git rebase --continue &&
+       test $old = $(git rev-parse HEAD)
  '
  
  test_expect_success 'auto-amend only edited commits after "edit"' '
@@@ -981,35 -982,7 +982,35 @@@ test_expect_success 'rebase -i --root r
        test -z "$(git show -s --format=%p HEAD^)"
  '
  
 +test_expect_success 'rebase -i --root when root has untracked file confilct' '
 +      test_when_finished "reset_rebase" &&
 +      git checkout -b failing-root-pick A &&
 +      echo x >file2 &&
 +      git rm file1 &&
 +      git commit -m "remove file 1 add file 2" &&
 +      echo z >file1 &&
 +      set_fake_editor &&
 +      test_must_fail env FAKE_LINES="1 2" git rebase -i --root &&
 +      rm file1 &&
 +      git rebase --continue &&
 +      test "$(git log -1 --format=%B)" = "remove file 1 add file 2" &&
 +      test "$(git rev-list --count HEAD)" = 2
 +'
 +
 +test_expect_success 'rebase -i --root reword root when root has untracked file conflict' '
 +      test_when_finished "reset_rebase" &&
 +      echo z>file1 &&
 +      set_fake_editor &&
 +      test_must_fail env FAKE_LINES="reword 1 2" \
 +              FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root &&
 +      rm file1 &&
 +      FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue &&
 +      test "$(git log -1 --format=%B HEAD^)" = "Reworded A" &&
 +      test "$(git rev-list --count HEAD)" = 2
 +'
 +
  test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' '
 +      git checkout reword-root-branch &&
        git reset --hard &&
        git checkout conflict-branch &&
        set_fake_editor &&