Merge branch 'js/rebase-r-strategy'

"git rebase --rebase-merges" learned to drive different merge
strategies and pass strategy specific options to them.

* js/rebase-r-strategy:
  t3427: accelerate this test by using fast-export and fast-import
  rebase -r: do not (re-)generate root commits with `--root` *and* `--onto`
  t3418: test `rebase -r` with merge strategies
  t/lib-rebase: prepare for testing `git rebase --rebase-merges`
  rebase -r: support merge strategies other than `recursive`
  t3427: fix another incorrect assumption
  t3427: accommodate for the `rebase --merge` backend having been replaced
  t3427: fix erroneous assumption
  t3427: condense the unnecessarily repetitive test cases into three
  t3427: move the `filter-branch` invocation into the `setup` case
  t3427: simplify the `setup` test case significantly
  t3427: add a clarifying comment
  rebase: fold git-rebase--common into the -p backend
  sequencer: the `am` and `rebase--interactive` scripts are gone
  .gitignore: there is no longer a built-in `git-rebase--interactive`
  t3400: stop referring to the scripted rebase
  Drop unused git-rebase--am.sh
This commit is contained in:
Junio C Hamano 2019-09-18 11:50:07 -07:00
Родитель 7e1976e210 476998d05b
Коммит 917a319ea5
9 изменённых файлов: 139 добавлений и 117 удалений

Просмотреть файл

@ -543,8 +543,6 @@ In addition, the following pairs of options are incompatible:
* --preserve-merges and --interactive
* --preserve-merges and --signoff
* --preserve-merges and --rebase-merges
* --rebase-merges and --strategy
* --rebase-merges and --strategy-option
BEHAVIORAL DIFFERENCES
-----------------------

Просмотреть файл

@ -62,7 +62,7 @@ struct rebase_options {
const char *onto_name;
const char *revisions;
const char *switch_to;
int root;
int root, root_with_onto;
struct object_id *squash_onto;
struct commit *restrict_revision;
int dont_finish_rebase;
@ -374,6 +374,7 @@ static int run_rebase_interactive(struct rebase_options *opts,
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
switch (command) {
@ -1833,15 +1834,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
"'--reschedule-failed-exec'"));
}
if (options.rebase_merges) {
if (strategy_options.nr)
die(_("cannot combine '--rebase-merges' with "
"'--strategy-option'"));
if (options.strategy)
die(_("cannot combine '--rebase-merges' with "
"'--strategy'"));
}
if (!options.root) {
if (argc < 1) {
struct branch *branch;
@ -1872,7 +1864,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.squash_onto = &squash_onto;
options.onto_name = squash_onto_name =
xstrdup(oid_to_hex(&squash_onto));
}
} else
options.root_with_onto = 1;
options.upstream_name = NULL;
options.upstream = NULL;
if (argc > 1)

Просмотреть файл

@ -3364,6 +3364,9 @@ static int do_merge(struct repository *r,
struct commit *head_commit, *merge_commit, *i;
struct commit_list *bases, *j, *reversed = NULL;
struct commit_list *to_merge = NULL, **tail = &to_merge;
const char *strategy = !opts->xopts_nr &&
(!opts->strategy || !strcmp(opts->strategy, "recursive")) ?
NULL : opts->strategy;
struct merge_options o;
int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
static struct lock_file lock;
@ -3516,7 +3519,7 @@ static int do_merge(struct repository *r,
goto leave_merge;
}
if (to_merge->next) {
if (strategy || to_merge->next) {
/* Octopus merge */
struct child_process cmd = CHILD_PROCESS_INIT;
@ -3530,7 +3533,14 @@ static int do_merge(struct repository *r,
cmd.git_cmd = 1;
argv_array_push(&cmd.args, "merge");
argv_array_push(&cmd.args, "-s");
argv_array_push(&cmd.args, "octopus");
if (!strategy)
argv_array_push(&cmd.args, "octopus");
else {
argv_array_push(&cmd.args, strategy);
for (k = 0; k < opts->xopts_nr; k++)
argv_array_pushf(&cmd.args,
"-X%s", opts->xopts[k]);
}
argv_array_push(&cmd.args, "--no-edit");
argv_array_push(&cmd.args, "--no-ff");
argv_array_push(&cmd.args, "--no-log");
@ -4554,6 +4564,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
{
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
struct strbuf label = STRBUF_INIT;
struct commit_list *commits = NULL, **tail = &commits, *iter;
@ -4720,7 +4731,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
if (!commit)
strbuf_addf(out, "%s %s\n", cmd_reset,
rebase_cousins ? "onto" : "[new root]");
rebase_cousins || root_with_onto ?
"onto" : "[new root]");
else {
const char *to = NULL;

Просмотреть файл

@ -143,6 +143,12 @@ int sequencer_remove_state(struct replay_opts *opts);
*/
#define TODO_LIST_REBASE_COUSINS (1U << 4)
#define TODO_LIST_APPEND_TODO_HELP (1U << 5)
/*
* When generating a script that rebases merges with `--root` *and* with
* `--onto`, we do not want to re-generate the root commits.
*/
#define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
const char **argv, unsigned flags);

Просмотреть файл

@ -44,10 +44,10 @@ set_fake_editor () {
rm -f "$1"
echo 'rebase -i script before editing:'
cat "$1".tmp
action=pick
action=\&
for line in $FAKE_LINES; do
case $line in
pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d)
pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d|label|l|reset|r|merge|m)
action="$line";;
exec_*|x_*|break|b)
echo "$line" | sed 's/_/ /g' >> "$1";;
@ -58,11 +58,12 @@ set_fake_editor () {
bad)
action="badcmd";;
fakesha)
test \& != "$action" || action=pick
echo "$action XXXXXXX False commit" >> "$1"
action=pick;;
*)
sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
action=pick;;
sed -n "${line}s/^[a-z][a-z]*/$action/p" < "$1".tmp >> "$1"
action=\&;;
esac
done
echo 'rebase -i script after editing:'

Просмотреть файл

@ -120,6 +120,20 @@ test_expect_success REBASE_P 'rebase passes merge strategy options correctly' '
git rebase --continue
'
test_expect_success 'rebase -r passes merge strategy options correctly' '
rm -fr .git/rebase-* &&
git reset --hard commit-new-file-F3-on-topic-branch &&
test_commit merge-theirs &&
git reset --hard HEAD^ &&
test_commit some-other-commit &&
test_tick &&
git merge --no-ff merge-theirs &&
FAKE_LINES="1 3 edit 4 5 7 8 9" git rebase -i -f -r -m \
-s recursive --strategy-option=theirs HEAD~2 &&
test_commit force-change-ours &&
git rebase --continue
'
test_expect_success '--skip after failed fixup cleans commit message' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout -b with-conflicting-fixup &&

Просмотреть файл

@ -76,14 +76,4 @@ test_expect_success REBASE_P \
test_must_fail git rebase --preserve-merges --rebase-merges A
'
test_expect_success '--rebase-merges incompatible with --strategy' '
git checkout B^0 &&
test_must_fail git rebase --rebase-merges -s resolve A
'
test_expect_success '--rebase-merges incompatible with --strategy-option' '
git checkout B^0 &&
test_must_fail git rebase --rebase-merges -Xignore-space-change A
'
test_done

Просмотреть файл

@ -11,113 +11,99 @@ commit_message() {
git log --pretty=format:%s -1 "$1"
}
# There are a few bugs in the rebase with regards to the subtree strategy, and
# this test script tries to document them. First, the following commit history
# is generated (the onelines are shown, time flows from left to right):
#
# master1 - master2 - master3
# \
# README ---------------------- Add subproject master - master4 - files_subtree/master5
#
# Where the merge moves the files master[123].t into the subdirectory
# files_subtree/ and master4 as well as files_subtree/master5 add files to that
# directory directly.
#
# Then, in subsequent test cases, `git filter-branch` is used to distill just
# the commits that touch files_subtree/. To give it a final pre-rebase touch,
# an empty commit is added on top. The pre-rebase commit history looks like
# this:
#
# Add subproject master - master4 - files_subtree/master5 - Empty commit
#
# where the root commit adds three files: master1.t, master2.t and master3.t.
#
# This commit history is then rebased onto `master3` with the
# `-Xsubtree=files_subtree` option in three different ways:
#
# 1. using `--preserve-merges`
# 2. using `--preserve-merges` and --keep-empty
# 3. without specifying a rebase backend
test_expect_success 'setup' '
test_commit README &&
mkdir files &&
(
cd files &&
git init &&
test_commit master1 &&
test_commit master2 &&
test_commit master3
) &&
git fetch files master &&
git branch files-master FETCH_HEAD &&
git read-tree --prefix=files_subtree files-master &&
git checkout -- files_subtree &&
tree=$(git write-tree) &&
head=$(git rev-parse HEAD) &&
rev=$(git rev-parse --verify files-master^0) &&
commit=$(git commit-tree -p $head -p $rev -m "Add subproject master" $tree) &&
git update-ref HEAD $commit &&
(
cd files_subtree &&
test_commit master4
) &&
test_commit files_subtree/master5
git init files &&
test_commit -C files master1 &&
test_commit -C files master2 &&
test_commit -C files master3 &&
: perform subtree merge into files_subtree/ &&
git fetch files refs/heads/master:refs/heads/files-master &&
git merge -s ours --no-commit --allow-unrelated-histories \
files-master &&
git read-tree --prefix=files_subtree -u files-master &&
git commit -m "Add subproject master" &&
: add two extra commits to rebase &&
test_commit -C files_subtree master4 &&
test_commit files_subtree/master5 &&
git checkout -b to-rebase &&
git fast-export --no-data HEAD -- files_subtree/ |
sed -e "s%\([0-9a-f]\{40\} \)files_subtree/%\1%" |
git fast-import --force --quiet &&
git reset --hard &&
git commit -m "Empty commit" --allow-empty
'
# FAILURE: Does not preserve master4.
test_expect_failure REBASE_P \
'Rebase -Xsubtree --preserve-merges --onto commit 4' '
test_expect_failure REBASE_P 'Rebase -Xsubtree --preserve-merges --onto commit' '
reset_rebase &&
git checkout -b rebase-preserve-merges-4 master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master &&
verbose test "$(commit_message HEAD~)" = "files_subtree/master4"
'
# FAILURE: Does not preserve master5.
test_expect_failure REBASE_P \
'Rebase -Xsubtree --preserve-merges --onto commit 5' '
reset_rebase &&
git checkout -b rebase-preserve-merges-5 master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git checkout -b rebase-preserve-merges to-rebase &&
git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master &&
verbose test "$(commit_message HEAD~)" = "master4" &&
verbose test "$(commit_message HEAD)" = "files_subtree/master5"
'
# FAILURE: Does not preserve master4.
test_expect_failure REBASE_P \
'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 4' '
test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit' '
reset_rebase &&
git checkout -b rebase-keep-empty-4 master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
verbose test "$(commit_message HEAD~2)" = "files_subtree/master4"
'
# FAILURE: Does not preserve master5.
test_expect_failure REBASE_P \
'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 5' '
reset_rebase &&
git checkout -b rebase-keep-empty-5 master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
verbose test "$(commit_message HEAD~)" = "files_subtree/master5"
'
# FAILURE: Does not preserve Empty.
test_expect_failure REBASE_P \
'Rebase -Xsubtree --keep-empty --preserve-merges --onto empty commit' '
reset_rebase &&
git checkout -b rebase-keep-empty-empty master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git checkout -b rebase-keep-empty to-rebase &&
git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master &&
verbose test "$(commit_message HEAD~2)" = "master4" &&
verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
verbose test "$(commit_message HEAD)" = "Empty commit"
'
# FAILURE: fatal: Could not parse object
test_expect_failure 'Rebase -Xsubtree --onto commit 4' '
test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
reset_rebase &&
git checkout -b rebase-onto-4 master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git rebase -Xsubtree=files_subtree --onto files-master master &&
verbose test "$(commit_message HEAD~2)" = "files_subtree/master4"
git checkout -b rebase-onto to-rebase &&
test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
: first pick results in no changes &&
git rebase --continue &&
verbose test "$(commit_message HEAD~2)" = "master4" &&
verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
verbose test "$(commit_message HEAD)" = "Empty commit"
'
# FAILURE: fatal: Could not parse object
test_expect_failure 'Rebase -Xsubtree --onto commit 5' '
test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
reset_rebase &&
git checkout -b rebase-onto-5 master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git rebase -Xsubtree=files_subtree --onto files-master master &&
verbose test "$(commit_message HEAD~)" = "files_subtree/master5"
'
# FAILURE: fatal: Could not parse object
test_expect_failure 'Rebase -Xsubtree --onto empty commit' '
reset_rebase &&
git checkout -b rebase-onto-empty master &&
git filter-branch --prune-empty -f --subdirectory-filter files_subtree &&
git commit -m "Empty commit" --allow-empty &&
git rebase -Xsubtree=files_subtree --onto files-master master &&
git checkout -b rebase-merges-onto to-rebase &&
test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
: first pick results in no changes &&
git rebase --continue &&
verbose test "$(commit_message HEAD~2)" = "master4" &&
verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
verbose test "$(commit_message HEAD)" = "Empty commit"
'

Просмотреть файл

@ -441,4 +441,25 @@ test_expect_success '--continue after resolving conflicts after a merge' '
test_path_is_missing .git/MERGE_HEAD
'
test_expect_success '--rebase-merges with strategies' '
git checkout -b with-a-strategy F &&
test_tick &&
git merge -m "Merge conflicting-G" conflicting-G &&
: first, test with a merge strategy option &&
git rebase -ir -Xtheirs G &&
echo conflicting-G >expect &&
test_cmp expect G.t &&
: now, try with a merge strategy other than recursive &&
git reset --hard @{1} &&
write_script git-merge-override <<-\EOF &&
echo overridden$1 >>G.t
git add G.t
EOF
PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
test_write_lines G overridden--xopt >expect &&
test_cmp expect G.t
'
test_done