Switch default merge backend from recursive to ort
cover Switch default merge backend from recursive to ort
This is a reroll of my RFC series[1] to switch the default merge backend from recursive to ort. The consensus seems to be that we should do this immediately after the 2.33 release.
Note that folks who want to get the old merge backend after this series can simply set pull.twohead=recursive. (And, similarly, before this series, those who want to try out ort in Git 2.32 or Git 2.33 can set pull.twohead=ort.)
Changes since the RFC version of this series:
* Now depends on the reroll of en/merge-strategy-docs I just submitted[2].
* Made tweaks to code and documentation suggested by Stolee, Dscho, and Ævar
[1]
https://lore.kernel.org/git/[email protected]/
[2]
https://lore.kernel.org/git/[email protected]/
Elijah Newren (2):
Change default merge backend from recursive to ort Update docs for change of default merge backend
Documentation/git-rebase.txt | 28 ++++----- Documentation/gitfaq.txt | 2 +- Documentation/merge-options.txt | 2 +- Documentation/merge-strategies.txt | 93 ++++++++++++++++-------------- Documentation/user-manual.txt | 2 +- builtin/merge.c | 10 +++- builtin/rebase.c | 2 +- sequencer.c | 4 +- 8 files changed, 78 insertions(+), 65 deletions(-)
base-commit: 4a78ac53424525d7099867d5a4377b8afb9bf18f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1011%2Fnewren%2Fort-default-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1011/newren/ort-default-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1011
Signature · 1 line
gitgitgadget
PATCH 1/2 Change default merge backend from recursive to ort
From: Elijah Newren <[email protected]>
There are a few reasons to switch the default:
* Correctness
* Extensibility
* Performance
I'll provide some summaries about each.
=== Correctness ===
The original impetus for a new merge backend was to fix issues that were difficult to fix within recursive's design. The success with this goal is perhaps most easily demonstrated by running the following:
$ git grep -2 KNOWN_FAILURE t/ | grep -A 4 GIT_TEST_MERGE_ALGORITHM $ git grep test_expect_merge_algorithm.failure.success t/ $ git grep test_expect_merge_algorithm.success.failure t/
In order, these greps show:
* Seven sets of submodule tests (10 total tests) that fail with recursive but succeed with ort
* 22 other tests that fail with recursive, but succeed with ort
* 0 tests that pass with recursive, but fail with ort
=== Extensibility ===
Being able to perform merges without touching the working tree or index makes it possible to create new features that were difficult with the old backend:
* Merging, cherry-picking, rebasing, reverting in bare repositories... or just on branches that aren't checked out.
* git diff AUTO_MERGE -- ability to see what changes the user has made to resolve conflicts so far (see commit 5291828df8 ("merge-ort:
write $GIT_DIR/AUTO_MERGE whenever we hit a conflict", 2021-03-20)
* A --remerge-diff option for log/show, used to show diffs for merges that display the difference between what an automatic merge would have created and what was recorded in the merge. (This option will often result in an empty diff because many merges are clean, but for the non-clean ones it will show how conflicts were fixed including the removal of conflict markers, and also show additional changes made outside of conflict regions to e.g. fix semantic conflicts.)
* A --remerge-diff-only option for log/show, similar to --remerge-diff but also showing how cherry-picks or reverts differed from what an automatic cherry-pick or revert would provide.
The last three have been implemented already (though only one has been submitted upstream so far; the others were waiting for performance work to complete), and I still plan to implement the first one.
=== Performance ===
I'll quote from the summary of my final optimization for merge-ort (while fixing the testcase name from 'no-renames' to 'few-renames'):
Timings
Infinite
merge- merge- Parallelism
recursive recursive of rename merge-ort
v2.30.0 current detection current
---------- --------- ----------- ---------
few-renames: 18.912 s 18.030 s 11.699 s 198.3 ms mega-renames: 5964.031 s 361.281 s 203.886 s 661.8 ms just-one-mega: 149.583 s 11.009 s 7.553 s 264.6 ms
Speedup factors
Infinite
merge- merge- Parallelism
recursive recursive of rename
v2.30.0 current detection merge-ort
---------- --------- ----------- ---------
few-renames: 1 1.05 1.6 95 mega-renames: 1 16.5 29 9012 just-one-mega: 1 13.6 20 565
And, for partial clone users:
Factor reduction in number of objects needed
Infinite
merge- merge- Parallelism
recursive recursive of rename
v2.30.0 current detection merge-ort
---------- --------- ----------- ---------
mega-renames: 1 1 1 181.3
--- builtin/merge.c | 10 ++++++++-- builtin/rebase.c | 2 +- sequencer.c | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-)
builtin/merge.c +8 -2
@@ -88,9 +88,9 @@ static int autostash;static int no_verify;static struct strategy all_strategy[] = { - {"recursive" ,DEFAULT_TWOHEAD |NO_TRIVIAL }, + {"recursive" ,NO_TRIVIAL }, {"octopus" ,DEFAULT_OCTOPUS }, - {"ort" ,NO_TRIVIAL }, + {"ort" ,DEFAULT_TWOHEAD |NO_TRIVIAL }, {"resolve" ,0 }, {"ours" ,NO_FAST_FORWARD |NO_TRIVIAL }, {"subtree" ,NO_FAST_FORWARD |NO_TRIVIAL },
@@ -1484,6 +1484,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) fast_forward =FF_NO ; } +if (!use_strategies && !pull_twohead && + remoteheads && !remoteheads->next) { +char *default_strategy =getenv ("GIT_TEST_MERGE_ALGORITHM" ); +if (default_strategy) +append_strategy (get_strategy (default_strategy)); + }if (!use_strategies) {if (!remoteheads) ;/* already up-to-date */
builtin/rebase.c +1 -1
@@ -1713,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)int i;if (!options.strategy) - options.strategy ="recursive" ; + options.strategy ="ort" ;strbuf_reset (&buf);for (i =0 ; i < strategy_options.nr; i++)
sequencer.c +2 -2
@@ -636,7 +636,7 @@ static int do_recursive_merge(struct repository *r,for (i =0 ; i < opts->xopts_nr; i++)parse_merge_opt (&o, opts->xopts[i]); -if (opts->strategy && !strcmp (opts->strategy,"ort" )) { +if (!opts->strategy || !strcmp (opts->strategy,"ort" )) {memset (&result,0 ,sizeof (result));merge_incore_nonrecursive (&o, base_tree, head_tree, next_tree, &result);
@@ -3988,7 +3988,7 @@ static int do_merge(struct repository *r, o.branch2 = ref_name.buf; o.buffer_output =2 ; -if (opts->strategy && !strcmp (opts->strategy,"ort" )) { +if (!opts->strategy || !strcmp (opts->strategy,"ort" )) {/* * TODO: Should use merge_incore_recursive() and * merge_switch_to_result(), skipping the call to
Signature · 1 line
gitgitgadget
Re: [PATCH 1/2] Change default merge backend from recursive to ort
Hi Elijah
Quoted 19 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
There are a few reasons to switch the default:
* Correctness
* Extensibility
* PerformanceI'll provide some summaries about each.
=== Correctness ===
The original impetus for a new merge backend was to fix issues that were difficult to fix within recursive's design. The success with this goal is perhaps most easily demonstrated by running the following:
$ git grep -2 KNOWN_FAILURE t/ | grep -A 4 GIT_TEST_MERGE_ALGORITHM $ git grep test_expect_merge_algorithm.failure.success t/ $ git grep test_expect_merge_algorithm.success.failure t/
Quoted 6 lines
In order, these greps show:
* Seven sets of submodule tests (10 total tests) that fail with recursive but succeed with ort
* 22 other tests that fail with recursive, but succeed with ort
* 0 tests that pass with recursive, but fail with ort
I've not followed your patches too closely but I did notice you seem to have put a lot of effort into improving the test coverage which is fantastic.
Quoted 8 lines
=== Extensibility ===
Being able to perform merges without touching the working tree or index makes it possible to create new features that were difficult with the old backend:
* Merging, cherry-picking, rebasing, reverting in bare repositories... or just on branches that aren't checked out.
Rebasing without updating the worktree for each commit is exciting.
I agree with others that this should be merged into next sooner rather than later. I also agree with Peff's point about moving it into master to get more people using it rather than sitting in next for too long.
I think the sequencer changes below are easier to follow in this version. One thing I did wonder is whether there needs to be any change to the CI scripts to ensure we keep testing both merge implementations.
Best wishes and congratulations on an impressive achievement
Phillip
Quoted 130 lines
*
git diff AUTO_MERGE-- ability to see what changes the user has made to resolve conflicts so far (see commit5291828df8("merge-ort:
write $GIT_DIR/AUTO_MERGE whenever we hit a conflict", 2021-03-20)* A --remerge-diff option for log/show, used to show diffs for merges that display the difference between what an automatic merge would have created and what was recorded in the merge. (This option will often result in an empty diff because many merges are clean, but for the non-clean ones it will show how conflicts were fixed including the removal of conflict markers, and also show additional changes made outside of conflict regions to e.g. fix semantic conflicts.)
* A --remerge-diff-only option for log/show, similar to --remerge-diff but also showing how cherry-picks or reverts differed from what an automatic cherry-pick or revert would provide.
The last three have been implemented already (though only one has been submitted upstream so far; the others were waiting for performance work to complete), and I still plan to implement the first one.
=== Performance ===
I'll quote from the summary of my final optimization for merge-ort (while fixing the testcase name from 'no-renames' to 'few-renames'):
Timings
Infinite
merge- merge- Parallelism
recursive recursive of rename merge-ort
v2.30.0 current detection current
---------- --------- ----------- ---------few-renames: 18.912 s 18.030 s 11.699 s 198.3 ms mega-renames: 5964.031 s 361.281 s 203.886 s 661.8 ms just-one-mega: 149.583 s 11.009 s 7.553 s 264.6 msSpeedup factors
Infinite
merge- merge- Parallelism
recursive recursive of rename
v2.30.0 current detection merge-ort
---------- --------- ----------- ---------few-renames: 1 1.05 1.6 95 mega-renames: 1 16.5 29 9012 just-one-mega: 1 13.6 20 565And, for partial clone users:
Factor reduction in number of objects needed
Infinite
merge- merge- Parallelism
recursive recursive of rename
v2.30.0 current detection merge-ort
---------- --------- ----------- ---------
mega-renames: 1 1 1 181.3Signed-off-by Elijah Newren <[email protected]>--- builtin/merge.c | 10 ++++++++-- builtin/rebase.c | 2 +- sequencer.c | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-)builtin/merge.c +8
-2@@ -88,9 +88,9 @@ static int autostash;static int no_verify;static struct strategy all_strategy[] = { - {"recursive" ,DEFAULT_TWOHEAD |NO_TRIVIAL }, + {"recursive" ,NO_TRIVIAL }, {"octopus" ,DEFAULT_OCTOPUS }, - {"ort" ,NO_TRIVIAL }, + {"ort" ,DEFAULT_TWOHEAD |NO_TRIVIAL }, {"resolve" ,0 }, {"ours" ,NO_FAST_FORWARD |NO_TRIVIAL }, {"subtree" ,NO_FAST_FORWARD |NO_TRIVIAL },@@ -1484,6 +1484,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) fast_forward =FF_NO ; } +if (!use_strategies && !pull_twohead && + remoteheads && !remoteheads->next) { +char *default_strategy =getenv ("GIT_TEST_MERGE_ALGORITHM" ); +if (default_strategy) +append_strategy (get_strategy (default_strategy)); + }if (!use_strategies) {if (!remoteheads) ;/* already up-to-date */ builtin/rebase.c +1
-1@@ -1713,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)int i;if (!options.strategy) - options.strategy ="recursive" ; + options.strategy ="ort" ;strbuf_reset (&buf);for (i =0 ; i < strategy_options.nr; i++)sequencer.c +2
-2@@ -636,7 +636,7 @@ static int do_recursive_merge(struct repository *r,for (i =0 ; i < opts->xopts_nr; i++)parse_merge_opt (&o, opts->xopts[i]); -if (opts->strategy && !strcmp (opts->strategy,"ort" )) { +if (!opts->strategy || !strcmp (opts->strategy,"ort" )) {memset (&result,0 ,sizeof (result));merge_incore_nonrecursive (&o, base_tree, head_tree, next_tree, &result);@@ -3988,7 +3988,7 @@ static int do_merge(struct repository *r, o.branch2 = ref_name.buf; o.buffer_output =2 ; -if (opts->strategy && !strcmp (opts->strategy,"ort" )) { +if (!opts->strategy || !strcmp (opts->strategy,"ort" )) {/* * TODO: Should use merge_incore_recursive() and * merge_switch_to_result(), skipping the call to
Re: [PATCH 1/2] Change default merge backend from recursive to ort
On Mon, Aug 9, 2021 at 10:38 AM Phillip Wood <[email protected]> wrote:
...
Quoted 10 lines
Quoted 8 lines
=== Extensibility ===
Being able to perform merges without touching the working tree or index makes it possible to create new features that were difficult with the old backend:
* Merging, cherry-picking, rebasing, reverting in bare repositories... or just on branches that aren't checked out.
Rebasing without updating the worktree for each commit is exciting.
Yeah, there appear to be some interesting twists from my initial investigations so far. I may need to start some interesting discussions in the next cycle or two about what we should do here (and also for merges in bare repositories.)
Quoted 3 lines
I agree with others that this should be merged into next sooner rather than later. I also agree with Peff's point about moving it into master to get more people using it rather than sitting in next for too long.
:-)
Quoted 2 lines
I think the sequencer changes below are easier to follow in this version.
Thanks for taking a look.
Quoted 2 lines
One thing I did wonder is whether there needs to be any change to the CI scripts to ensure we keep testing both merge implementations.
There did need to be such a change, but it was made previously in commit f3b964a07e ("Add testing with merge-ort merge strategy", 2021-03-20). That commit changed the default merge backend *for testing* to be merge-ort, and modified the linux-gcc job to explicitly specify recursive. This commit doesn't change the testing story, so the recursive backend will still continue to be tested with the linux-gcc tests, or whenever someone requests it with GIT_TEST_MERGE_ALGORITHM=recursive, and otherwise merge-ort will be used.
Quoted 1 line
Best wishes and congratulations on an impressive achievement
Thanks!
PATCH 2/2 Update docs for change of default merge backend
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Make it clear that ort is the default merge strategy now rather than recursive, including moving ort to the front of the list of merge strategies.
--- Documentation/git-rebase.txt | 28 ++++----- Documentation/gitfaq.txt | 2 +- Documentation/merge-options.txt | 2 +- Documentation/merge-strategies.txt | 93 ++++++++++++++++-------------- Documentation/user-manual.txt | 2 +- 5 files changed, 67 insertions(+), 60 deletions(-)
Documentation/git-rebase.txt +14 -14
@@ -352,8 +352,8 @@ See also INCOMPATIBLE OPTIONS below. -s <strategy>:: --strategy=<strategy>:: - Use the given merge strategy, instead of the default - `recursive`. This implies `--merge`. + Use the given merge strategy, instead of the default `ort`. + This implies `--merge`. + Because 'git rebase' replays each commit from the working branch on top of the <upstream> branch using the given strategy, using
@@ -366,7 +366,7 @@ See also INCOMPATIBLE OPTIONS below. --strategy-option=<strategy-option>:: Pass the <strategy-option> through to the merge strategy. This implies `--merge` and, if no strategy has been - specified, `-s recursive`. Note the reversal of 'ours' and + specified, `-s ort`. Note the reversal of 'ours' and 'theirs' as noted above for the `-m` option. + See also INCOMPATIBLE OPTIONS below.
@@ -527,7 +527,7 @@ The `--rebase-merges` mode is similar in spirit to the deprecated where commits can be reordered, inserted and dropped at will. + It is currently only possible to recreate the merge commits using the -`recursive` merge strategy; different merge strategies can be used only via +`ort` merge strategy; different merge strategies can be used only via explicit `exec git merge -s <strategy> [...]` commands. + See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
@@ -1216,16 +1216,16 @@ successful merge so that the user can edit the message. If a `merge` command fails for any reason other than merge conflicts (i.e. when the merge operation did not even start), it is rescheduled immediately. -By default, the `merge` command will use the `recursive` merge -strategy for regular merges, and `octopus` for octopus merges. One -can specify a default strategy for all merges using the `--strategy` -argument when invoking rebase, or can override specific merges in the -interactive list of commands by using an `exec` command to call `git -merge` explicitly with a `--strategy` argument. Note that when -calling `git merge` explicitly like this, you can make use of the fact -that the labels are worktree-local refs (the ref `refs/rewritten/onto` -would correspond to the label `onto`, for example) in order to refer -to the branches you want to merge. +By default, the `merge` command will use the `ort` merge strategy for +regular merges, and `octopus` for octopus merges. One can specify a +default strategy for all merges using the `--strategy` argument when +invoking rebase, or can override specific merges in the interactive +list of commands by using an `exec` command to call `git merge` +explicitly with a `--strategy` argument. Note that when calling `git +merge` explicitly like this, you can make use of the fact that the +labels are worktree-local refs (the ref `refs/rewritten/onto` would +correspond to the label `onto`, for example) in order to refer to the +branches you want to merge. Note: the first command (`label onto`) labels the revision onto which the commits are rebased; The name `onto` is just a convention, as a nod
Documentation/gitfaq.txt +1 -1
@@ -275,7 +275,7 @@ best to always use a regular merge commit. [[merge-two-revert-one]] If I make a change on two branches but revert it on one, why does the merge of those branches include the change?:: - By default, when Git does a merge, it uses a strategy called the recursive + By default, when Git does a merge, it uses a strategy called the `ort` strategy, which does a fancy three-way merge. In such a case, when Git performs the merge, it considers exactly three points: the two heads and a third point, called the _merge base_, which is usually the common ancestor of
Documentation/merge-options.txt +1 -1
@@ -112,7 +112,7 @@ With --squash, --commit is not allowed, and will fail. Use the given merge strategy; can be supplied more than once to specify them in the order they should be tried. If there is no `-s` option, a built-in list of strategies - is used instead (`recursive` when merging a single head, + is used instead (`ort` when merging a single head, `octopus` otherwise). -X <option>::
Documentation/merge-strategies.txt +50 -43
130 lines not on this page · raw message
Documentation/user-manual.txt +1 -1
@@ -3190,7 +3190,7 @@ that *updated* thing--the old state that you added originally ends up not being pointed to by any commit or tree, so it's now a dangling blob object. -Similarly, when the "recursive" merge strategy runs, and finds that +Similarly, when the "ort" merge strategy runs, and finds that there are criss-cross merges and thus more than one merge base (which is fairly unusual, but it does happen), it will generate one temporary midway tree (or possibly even more, if you had lots of criss-crossing
Signature · 1 line
gitgitgadget
Re: [PATCH 2/2] Update docs for change of default merge backend
Quoted 1 line · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
These updates look good to me. Mostly whole paragraphs moving and some word replacements.
Documentation/merge-strategies.txt ours:: · line 141
Quoted 3 lines
subtree::
Documentation/merge-strategies.txt +1
-1- This is a modified recursive strategy. When merging trees A and + This is a modified ort strategy. When merging trees A and
Here is one more case where the old docs could have used quotes around "recursive" and the new ones could use quotes around "ort". Not worth a re-roll on its own.
Thanks,
-Stolee
Quoted 9 lines · Elijah Newren via GitGitGadget
This is a reroll of my RFC series[1] to switch the default merge backend from recursive to ort. The consensus seems to be that we should do this immediately after the 2.33 release.
Note that folks who want to get the old merge backend after this series can simply set pull.twohead=recursive. (And, similarly, before this series, those who want to try out ort in Git 2.32 or Git 2.33 can set pull.twohead=ort.)
Quoted 5 lines
Changes since the RFC version of this series:
* Now depends on the reroll of en/merge-strategy-docs I just submitted[2].
* Made tweaks to code and documentation suggested by Stolee, Dscho, and Ævar
I'm happy with this version (modulo a super tiny nitpick that doesn't merit a re-roll) and the docs that preceded it. I'm going to adapt my version for the microsoft/git fork to take this version and put it in our v2.33.0 release.
Thanks,
-Stolee