Skip to content

Commit eb7923b

Browse files
committed
Merge branch 'en/merge-ort-prepare-to-remove-recursive'
First step of deprecating and removing merge-recursive. * en/merge-ort-prepare-to-remove-recursive: am: switch from merge_recursive_generic() to merge_ort_generic() merge-ort: fix merge.directoryRenames=false t3650: document bug when directory renames are turned off merge-ort: support having merge verbosity be set to 0 merge-ort: allow rename detection to be disabled merge-ort: add new merge_ort_generic() function
2 parents 8d6413a + 947e219 commit eb7923b

11 files changed

+172
-20
lines changed

Documentation/merge-strategies.adoc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ find-renames[=<n>];;
8282
rename-threshold=<n>;;
8383
Deprecated synonym for `find-renames=<n>`.
8484

85+
no-renames;;
86+
Turn off rename detection. This overrides the `merge.renames`
87+
configuration variable.
88+
See also linkgit:git-diff[1] `--no-renames`.
89+
8590
subtree[=<path>];;
8691
This option is a more advanced form of 'subtree' strategy, where
8792
the strategy makes a guess on how two trees must be shifted to
@@ -107,7 +112,7 @@ For a path that is a submodule, the same caution as 'ort' applies to this
107112
strategy.
108113
+
109114
The 'recursive' strategy takes the same options as 'ort'. However,
110-
there are three additional options that 'ort' ignores (not documented
115+
there are two additional options that 'ort' ignores (not documented
111116
above) that are potentially useful with the 'recursive' strategy:
112117

113118
patience;;
@@ -121,11 +126,6 @@ diff-algorithm=[patience|minimal|histogram|myers];;
121126
specifically uses `diff-algorithm=histogram`, while `recursive`
122127
defaults to the `diff.algorithm` config setting.
123128

124-
no-renames;;
125-
Turn off rename detection. This overrides the `merge.renames`
126-
configuration variable.
127-
See also linkgit:git-diff[1] `--no-renames`.
128-
129129
resolve::
130130
This can only resolve two heads (i.e. the current branch
131131
and another branch you pulled from) using a 3-way merge

builtin/am.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#include "preload-index.h"
3232
#include "sequencer.h"
3333
#include "revision.h"
34-
#include "merge-recursive.h"
34+
#include "merge-ort-wrappers.h"
3535
#include "log-tree.h"
3636
#include "notes-utils.h"
3737
#include "rerere.h"
@@ -1638,12 +1638,13 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
16381638
o.branch1 = "HEAD";
16391639
their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
16401640
o.branch2 = their_tree_name;
1641+
o.ancestor = "constructed fake ancestor";
16411642
o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE;
16421643

16431644
if (state->quiet)
16441645
o.verbosity = 0;
16451646

1646-
if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
1647+
if (merge_ort_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
16471648
repo_rerere(the_repository, state->allow_rerere_autoupdate);
16481649
free(their_tree_name);
16491650
return error(_("Failed to merge in the changes."));

merge-ort-wrappers.c

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
#include "git-compat-util.h"
22
#include "gettext.h"
33
#include "hash.h"
4+
#include "hex.h"
5+
#include "lockfile.h"
46
#include "merge-ort.h"
57
#include "merge-ort-wrappers.h"
68
#include "read-cache-ll.h"
9+
#include "repository.h"
10+
#include "tag.h"
711
#include "tree.h"
812

913
#include "commit.h"
@@ -29,6 +33,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,
2933
struct tree *merge_base)
3034
{
3135
struct merge_result result;
36+
int show_msgs;
3237

3338
if (unclean(opt, head))
3439
return -1;
@@ -38,9 +43,10 @@ int merge_ort_nonrecursive(struct merge_options *opt,
3843
return 1;
3944
}
4045

46+
show_msgs = !!opt->verbosity;
4147
memset(&result, 0, sizeof(result));
4248
merge_incore_nonrecursive(opt, merge_base, head, merge, &result);
43-
merge_switch_to_result(opt, head, &result, 1, 1);
49+
merge_switch_to_result(opt, head, &result, 1, show_msgs);
4450

4551
return result.clean;
4652
}
@@ -53,14 +59,76 @@ int merge_ort_recursive(struct merge_options *opt,
5359
{
5460
struct tree *head = repo_get_commit_tree(opt->repo, side1);
5561
struct merge_result tmp;
62+
int show_msgs;
5663

5764
if (unclean(opt, head))
5865
return -1;
5966

67+
show_msgs = !!opt->verbosity;
6068
memset(&tmp, 0, sizeof(tmp));
6169
merge_incore_recursive(opt, merge_bases, side1, side2, &tmp);
62-
merge_switch_to_result(opt, head, &tmp, 1, 1);
70+
merge_switch_to_result(opt, head, &tmp, 1, show_msgs);
6371
*result = NULL;
6472

6573
return tmp.clean;
6674
}
75+
76+
static struct commit *get_ref(struct repository *repo,
77+
const struct object_id *oid,
78+
const char *name)
79+
{
80+
struct object *object;
81+
82+
object = deref_tag(repo, parse_object(repo, oid),
83+
name, strlen(name));
84+
if (!object)
85+
return NULL;
86+
if (object->type == OBJ_TREE)
87+
return make_virtual_commit(repo, (struct tree*)object, name);
88+
if (object->type != OBJ_COMMIT)
89+
return NULL;
90+
if (repo_parse_commit(repo, (struct commit *)object))
91+
return NULL;
92+
return (struct commit *)object;
93+
}
94+
95+
int merge_ort_generic(struct merge_options *opt,
96+
const struct object_id *head,
97+
const struct object_id *merge,
98+
int num_merge_bases,
99+
const struct object_id *merge_bases,
100+
struct commit **result)
101+
{
102+
int clean;
103+
struct lock_file lock = LOCK_INIT;
104+
struct commit *head_commit = get_ref(opt->repo, head, opt->branch1);
105+
struct commit *next_commit = get_ref(opt->repo, merge, opt->branch2);
106+
struct commit_list *ca = NULL;
107+
108+
if (merge_bases) {
109+
int i;
110+
for (i = 0; i < num_merge_bases; ++i) {
111+
struct commit *base;
112+
if (!(base = get_ref(opt->repo, &merge_bases[i],
113+
oid_to_hex(&merge_bases[i]))))
114+
return error(_("Could not parse object '%s'"),
115+
oid_to_hex(&merge_bases[i]));
116+
commit_list_insert(base, &ca);
117+
}
118+
}
119+
120+
repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);
121+
clean = merge_ort_recursive(opt, head_commit, next_commit, ca,
122+
result);
123+
free_commit_list(ca);
124+
if (clean < 0) {
125+
rollback_lock_file(&lock);
126+
return clean;
127+
}
128+
129+
if (write_locked_index(opt->repo->index, &lock,
130+
COMMIT_LOCK | SKIP_IF_UNCHANGED))
131+
return error(_("Unable to write index."));
132+
133+
return clean ? 0 : 1;
134+
}

merge-ort-wrappers.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,16 @@ int merge_ort_recursive(struct merge_options *opt,
2222
const struct commit_list *ancestors,
2323
struct commit **result);
2424

25+
/*
26+
* rename-detecting three-way merge. num_merge_bases must be at least 1.
27+
* Recursive ancestor consolidation will be performed if num_merge_bases > 1.
28+
* Wrapper mimicking the old merge_recursive_generic() function.
29+
*/
30+
int merge_ort_generic(struct merge_options *opt,
31+
const struct object_id *head,
32+
const struct object_id *merge,
33+
int num_merge_bases,
34+
const struct object_id *merge_bases,
35+
struct commit **result);
36+
2537
#endif

merge-ort.c

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3405,6 +3405,11 @@ static int collect_renames(struct merge_options *opt,
34053405
pool_diff_free_filepair(&opt->priv->pool, p);
34063406
continue;
34073407
}
3408+
if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE &&
3409+
p->status == 'R' && 1) {
3410+
possibly_cache_new_pair(renames, p, side_index, NULL);
3411+
goto skip_directory_renames;
3412+
}
34083413

34093414
new_path = check_for_directory_rename(opt, p->two->path,
34103415
side_index,
@@ -3422,6 +3427,7 @@ static int collect_renames(struct merge_options *opt,
34223427
if (new_path)
34233428
apply_directory_rename_modifications(opt, p, new_path);
34243429

3430+
skip_directory_renames:
34253431
/*
34263432
* p->score comes back from diffcore_rename_extended() with
34273433
* the similarity of the renamed file. The similarity is
@@ -3449,6 +3455,11 @@ static int detect_and_process_renames(struct merge_options *opt)
34493455

34503456
if (!possible_renames(renames))
34513457
goto cleanup;
3458+
if (!opt->detect_renames) {
3459+
renames->redo_after_renames = 0;
3460+
renames->cached_pairs_valid_side = 0;
3461+
goto cleanup;
3462+
}
34523463

34533464
trace2_region_enter("merge", "regular renames", opt->repo);
34543465
detection_run |= detect_regular_renames(opt, MERGE_SIDE1);
@@ -4879,9 +4890,9 @@ static inline void set_commit_tree(struct commit *c, struct tree *t)
48794890
c->maybe_tree = t;
48804891
}
48814892

4882-
static struct commit *make_virtual_commit(struct repository *repo,
4883-
struct tree *tree,
4884-
const char *comment)
4893+
struct commit *make_virtual_commit(struct repository *repo,
4894+
struct tree *tree,
4895+
const char *comment)
48854896
{
48864897
struct commit *commit = alloc_commit_node(repo);
48874898

@@ -5021,7 +5032,8 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
50215032
trace2_region_leave("merge", "allocate/init", opt->repo);
50225033
}
50235034

5024-
static void merge_check_renames_reusable(struct merge_result *result,
5035+
static void merge_check_renames_reusable(struct merge_options *opt,
5036+
struct merge_result *result,
50255037
struct tree *merge_base,
50265038
struct tree *side1,
50275039
struct tree *side2)
@@ -5046,6 +5058,26 @@ static void merge_check_renames_reusable(struct merge_result *result,
50465058
return;
50475059
}
50485060

5061+
/*
5062+
* Avoid using cached renames when directory rename detection is
5063+
* turned off. Cached renames are far less important in that case,
5064+
* and they lead to testcases with an interesting intersection of
5065+
* effects from relevant renames optimization, trivial directory
5066+
* resolution optimization, and cached renames all converging when
5067+
* the target of a cached rename is in a directory that
5068+
* collect_merge_info() does not recurse into. To avoid such
5069+
* problems, simply disable cached renames for this case (similar
5070+
* to the rename/rename(1to1) case; see the "disabling the
5071+
* optimization" comment near that case).
5072+
*
5073+
* This could be revisited in the future; see the commit message
5074+
* where this comment was added for some possible pointers.
5075+
*/
5076+
if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE) {
5077+
renames->cached_pairs_valid_side = 0; /* neither side valid */
5078+
return;
5079+
}
5080+
50495081
/*
50505082
* Handle other cases; note that merge_trees[0..2] will only
50515083
* be NULL if opti is, or if all three were manually set to
@@ -5187,6 +5219,8 @@ static void merge_ort_internal(struct merge_options *opt,
51875219
ancestor_name = "empty tree";
51885220
} else if (merge_bases) {
51895221
ancestor_name = "merged common ancestors";
5222+
} else if (opt->ancestor) {
5223+
ancestor_name = opt->ancestor;
51905224
} else {
51915225
strbuf_add_unique_abbrev(&merge_base_abbrev,
51925226
&merged_merge_bases->object.oid,
@@ -5252,7 +5286,7 @@ void merge_incore_nonrecursive(struct merge_options *opt,
52525286

52535287
trace2_region_enter("merge", "merge_start", opt->repo);
52545288
assert(opt->ancestor != NULL);
5255-
merge_check_renames_reusable(result, merge_base, side1, side2);
5289+
merge_check_renames_reusable(opt, result, merge_base, side1, side2);
52565290
merge_start(opt, result);
52575291
/*
52585292
* Record the trees used in this merge, so if there's a next merge in
@@ -5276,8 +5310,13 @@ void merge_incore_recursive(struct merge_options *opt,
52765310
{
52775311
trace2_region_enter("merge", "incore_recursive", opt->repo);
52785312

5279-
/* We set the ancestor label based on the merge_bases */
5280-
assert(opt->ancestor == NULL);
5313+
/*
5314+
* We set the ancestor label based on the merge_bases...but we
5315+
* allow one exception through so that builtin/am can override
5316+
* with its constructed fake ancestor.
5317+
*/
5318+
assert(opt->ancestor == NULL ||
5319+
(merge_bases && !merge_bases->next));
52815320

52825321
trace2_region_enter("merge", "merge_start", opt->repo);
52835322
merge_start(opt, result);

merge-ort.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ struct merge_result {
4444
unsigned _properly_initialized;
4545
};
4646

47+
/* Mostly internal function also used by merge-ort-wrappers.c */
48+
struct commit *make_virtual_commit(struct repository *repo,
49+
struct tree *tree,
50+
const char *comment);
51+
4752
/*
4853
* rename-detecting three-way merge with recursive ancestor consolidation.
4954
* working tree and index are untouched.

t/t3650-replay-basics.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,26 @@ test_expect_success 'using replay on bare repo to rebase multiple divergent bran
195195
done
196196
'
197197

198+
test_expect_success 'merge.directoryRenames=false' '
199+
# create a test case that stress-tests the rename caching
200+
git switch -c rename-onto &&
201+
202+
mkdir -p to-rename &&
203+
test_commit to-rename/move &&
204+
205+
mkdir -p renamed-directory &&
206+
git mv to-rename/move* renamed-directory/ &&
207+
test_tick &&
208+
git commit -m renamed-directory &&
209+
210+
git switch -c rename-from HEAD^ &&
211+
test_commit to-rename/add-a-file &&
212+
echo modified >to-rename/add-a-file.t &&
213+
test_tick &&
214+
git commit -m modified to-rename/add-a-file.t &&
215+
216+
git -c merge.directoryRenames=false replay \
217+
--onto rename-onto rename-onto..rename-from
218+
'
219+
198220
test_done

t/t4151-am-abort.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ test_expect_success 'am --abort will keep dirty index intact' '
112112
test_expect_success 'am -3 stops on conflict on unborn branch' '
113113
git checkout -f --orphan orphan &&
114114
git reset &&
115-
rm -f otherfile-4 &&
115+
rm -f file-1 otherfile-4 &&
116116
test_must_fail git am -3 0003-*.patch &&
117117
test 2 -eq $(git ls-files -u | wc -l) &&
118118
test 4 = "$(cat otherfile-4)"

t/t4255-am-submodule.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ am_3way () {
1919
$2 git am --3way patch
2020
}
2121

22-
KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
2322
test_submodule_switch_func "am_3way"
2423

2524
test_expect_success 'setup diff.submodule' '

t/t4301-merge-tree-write-tree.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ test_expect_success 'Clean merge' '
7373
test_cmp expect actual
7474
'
7575

76+
# Repeat the previous test, but turn off rename detection
77+
test_expect_success 'Failed merge without rename detection' '
78+
test_must_fail git -c diff.renames=false merge-tree --write-tree side1 side3 >out &&
79+
grep "CONFLICT (modify/delete): numbers deleted" out
80+
'
81+
7682
test_expect_success 'Content merge and a few conflicts' '
7783
git checkout side1^0 &&
7884
test_must_fail git merge side2 &&

0 commit comments

Comments
 (0)