Skip to content

Commit 4ce064d

Browse files
committed
Merge branch 'fa/merge-recursive-no-rename'
"git merge-recursive" learned "--no-renames" option to disable its rename detection logic. * fa/merge-recursive-no-rename: t3034: test deprecated interface t3034: test option to disable renames t3034: add rename threshold tests merge-recursive: find-renames resets threshold merge-strategies.txt: fix typo merge-recursive: more consistent interface merge-recursive: option to disable renames
2 parents 9671a76 + 44c74ec commit 4ce064d

File tree

5 files changed

+337
-4
lines changed

5 files changed

+337
-4
lines changed

Documentation/merge-strategies.txt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,17 @@ no-renormalize;;
8181
Disables the `renormalize` option. This overrides the
8282
`merge.renormalize` configuration variable.
8383

84+
no-renames;;
85+
Turn off rename detection.
86+
See also linkgit:git-diff[1] `--no-renames`.
87+
88+
find-renames[=<n>];;
89+
Turn on rename detection, optionally setting the similarity
90+
threshold. This is the default.
91+
See also linkgit:git-diff[1] `--find-renames`.
92+
8493
rename-threshold=<n>;;
85-
Controls the similarity threshold used for rename detection.
86-
See also linkgit:git-diff[1] `-M`.
94+
Deprecated synonym for `find-renames=<n>`.
8795

8896
subtree[=<path>];;
8997
This option is a more advanced form of 'subtree' strategy, where

merge-recursive.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ static struct string_list *get_renames(struct merge_options *o,
482482
struct diff_options opts;
483483

484484
renames = xcalloc(1, sizeof(struct string_list));
485+
if (!o->detect_rename)
486+
return renames;
487+
485488
diff_setup(&opts);
486489
DIFF_OPT_SET(&opts, RECURSIVE);
487490
DIFF_OPT_CLR(&opts, RENAME_EMPTY);
@@ -2039,6 +2042,7 @@ void init_merge_options(struct merge_options *o)
20392042
o->diff_rename_limit = -1;
20402043
o->merge_rename_limit = -1;
20412044
o->renormalize = 0;
2045+
o->detect_rename = 1;
20422046
merge_recursive_config(o);
20432047
if (getenv("GIT_MERGE_VERBOSITY"))
20442048
o->verbosity =
@@ -2088,9 +2092,17 @@ int parse_merge_opt(struct merge_options *o, const char *s)
20882092
o->renormalize = 1;
20892093
else if (!strcmp(s, "no-renormalize"))
20902094
o->renormalize = 0;
2091-
else if (skip_prefix(s, "rename-threshold=", &arg)) {
2095+
else if (!strcmp(s, "no-renames"))
2096+
o->detect_rename = 0;
2097+
else if (!strcmp(s, "find-renames")) {
2098+
o->detect_rename = 1;
2099+
o->rename_score = 0;
2100+
}
2101+
else if (skip_prefix(s, "find-renames=", &arg) ||
2102+
skip_prefix(s, "rename-threshold=", &arg)) {
20922103
if ((o->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
20932104
return -1;
2105+
o->detect_rename = 1;
20942106
}
20952107
else
20962108
return -1;

merge-recursive.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ struct merge_options {
1717
unsigned renormalize : 1;
1818
long xdl_opts;
1919
int verbosity;
20+
int detect_rename;
2021
int diff_rename_limit;
2122
int merge_rename_limit;
2223
int rename_score;

t/t3032-merge-recursive-options.sh renamed to t/t3032-merge-recursive-space-options.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/sh
22

3-
test_description='merge-recursive options
3+
test_description='merge-recursive space options
44
55
* [master] Clarify
66
! [remote] Remove cruft
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
#!/bin/sh
2+
3+
test_description='merge-recursive rename options
4+
5+
Test rename detection by examining rename/delete conflicts.
6+
7+
* (HEAD -> rename) rename
8+
| * (master) delete
9+
|/
10+
* base
11+
12+
git diff --name-status base master
13+
D 0-old
14+
D 1-old
15+
D 2-old
16+
D 3-old
17+
18+
git diff --name-status -M01 base rename
19+
R025 0-old 0-new
20+
R050 1-old 1-new
21+
R075 2-old 2-new
22+
R100 3-old 3-new
23+
24+
Actual similarity indices are parsed from diff output. We rely on the fact that
25+
they are rounded down (see, e.g., Documentation/diff-generate-patch.txt, which
26+
mentions this in a different context).
27+
'
28+
29+
. ./test-lib.sh
30+
31+
get_expected_stages () {
32+
git checkout rename -- $1-new &&
33+
git ls-files --stage $1-new >expected-stages-undetected-$1 &&
34+
sed "s/ 0 / 2 /" <expected-stages-undetected-$1 \
35+
>expected-stages-detected-$1 &&
36+
git read-tree -u --reset HEAD
37+
}
38+
39+
rename_detected () {
40+
git ls-files --stage $1-old $1-new >stages-actual-$1 &&
41+
test_cmp expected-stages-detected-$1 stages-actual-$1
42+
}
43+
44+
rename_undetected () {
45+
git ls-files --stage $1-old $1-new >stages-actual-$1 &&
46+
test_cmp expected-stages-undetected-$1 stages-actual-$1
47+
}
48+
49+
check_common () {
50+
git ls-files --stage >stages-actual &&
51+
test_line_count = 4 stages-actual
52+
}
53+
54+
check_threshold_0 () {
55+
check_common &&
56+
rename_detected 0 &&
57+
rename_detected 1 &&
58+
rename_detected 2 &&
59+
rename_detected 3
60+
}
61+
62+
check_threshold_1 () {
63+
check_common &&
64+
rename_undetected 0 &&
65+
rename_detected 1 &&
66+
rename_detected 2 &&
67+
rename_detected 3
68+
}
69+
70+
check_threshold_2 () {
71+
check_common &&
72+
rename_undetected 0 &&
73+
rename_undetected 1 &&
74+
rename_detected 2 &&
75+
rename_detected 3
76+
}
77+
78+
check_exact_renames () {
79+
check_common &&
80+
rename_undetected 0 &&
81+
rename_undetected 1 &&
82+
rename_undetected 2 &&
83+
rename_detected 3
84+
}
85+
86+
check_no_renames () {
87+
check_common &&
88+
rename_undetected 0 &&
89+
rename_undetected 1 &&
90+
rename_undetected 2 &&
91+
rename_undetected 3
92+
}
93+
94+
test_expect_success 'setup repo' '
95+
cat <<-\EOF >3-old &&
96+
33a
97+
33b
98+
33c
99+
33d
100+
EOF
101+
sed s/33/22/ <3-old >2-old &&
102+
sed s/33/11/ <3-old >1-old &&
103+
sed s/33/00/ <3-old >0-old &&
104+
git add [0-3]-old &&
105+
git commit -m base &&
106+
git rm [0-3]-old &&
107+
git commit -m delete &&
108+
git checkout -b rename HEAD^ &&
109+
cp 3-old 3-new &&
110+
sed 1,1s/./x/ <2-old >2-new &&
111+
sed 1,2s/./x/ <1-old >1-new &&
112+
sed 1,3s/./x/ <0-old >0-new &&
113+
git add [0-3]-new &&
114+
git rm [0-3]-old &&
115+
git commit -m rename &&
116+
get_expected_stages 0 &&
117+
get_expected_stages 1 &&
118+
get_expected_stages 2 &&
119+
get_expected_stages 3 &&
120+
check_50="false" &&
121+
tail="HEAD^ -- HEAD master"
122+
'
123+
124+
test_expect_success 'setup thresholds' '
125+
git diff --name-status -M01 HEAD^ HEAD >diff-output &&
126+
test_debug "cat diff-output" &&
127+
test_line_count = 4 diff-output &&
128+
grep "R[0-9][0-9][0-9] \([0-3]\)-old \1-new" diff-output \
129+
>grep-output &&
130+
test_cmp diff-output grep-output &&
131+
th0=$(sed -n "s/R\(...\) 0-old 0-new/\1/p" <diff-output) &&
132+
th1=$(sed -n "s/R\(...\) 1-old 1-new/\1/p" <diff-output) &&
133+
th2=$(sed -n "s/R\(...\) 2-old 2-new/\1/p" <diff-output) &&
134+
th3=$(sed -n "s/R\(...\) 3-old 3-new/\1/p" <diff-output) &&
135+
test "$th0" -lt "$th1" &&
136+
test "$th1" -lt "$th2" &&
137+
test "$th2" -lt "$th3" &&
138+
test "$th3" = 100 &&
139+
if test 50 -le "$th0"
140+
then
141+
check_50=check_threshold_0
142+
elif test 50 -le "$th1"
143+
then
144+
check_50=check_threshold_1
145+
elif test 50 -le "$th2"
146+
then
147+
check_50=check_threshold_2
148+
fi &&
149+
th0="$th0%" &&
150+
th1="$th1%" &&
151+
th2="$th2%" &&
152+
th3="$th3%"
153+
'
154+
155+
test_expect_success 'assumption for tests: rename detection with diff' '
156+
git diff --name-status -M$th0 --diff-filter=R HEAD^ HEAD \
157+
>diff-output-0 &&
158+
git diff --name-status -M$th1 --diff-filter=R HEAD^ HEAD \
159+
>diff-output-1 &&
160+
git diff --name-status -M$th2 --diff-filter=R HEAD^ HEAD \
161+
>diff-output-2 &&
162+
git diff --name-status -M100% --diff-filter=R HEAD^ HEAD \
163+
>diff-output-3 &&
164+
test_line_count = 4 diff-output-0 &&
165+
test_line_count = 3 diff-output-1 &&
166+
test_line_count = 2 diff-output-2 &&
167+
test_line_count = 1 diff-output-3
168+
'
169+
170+
test_expect_success 'default similarity threshold is 50%' '
171+
git read-tree --reset -u HEAD &&
172+
test_must_fail git merge-recursive $tail &&
173+
$check_50
174+
'
175+
176+
test_expect_success 'low rename threshold' '
177+
git read-tree --reset -u HEAD &&
178+
test_must_fail git merge-recursive --find-renames=$th0 $tail &&
179+
check_threshold_0
180+
'
181+
182+
test_expect_success 'medium rename threshold' '
183+
git read-tree --reset -u HEAD &&
184+
test_must_fail git merge-recursive --find-renames=$th1 $tail &&
185+
check_threshold_1
186+
'
187+
188+
test_expect_success 'high rename threshold' '
189+
git read-tree --reset -u HEAD &&
190+
test_must_fail git merge-recursive --find-renames=$th2 $tail &&
191+
check_threshold_2
192+
'
193+
194+
test_expect_success 'exact renames only' '
195+
git read-tree --reset -u HEAD &&
196+
test_must_fail git merge-recursive --find-renames=100% $tail &&
197+
check_exact_renames
198+
'
199+
200+
test_expect_success 'rename threshold is truncated' '
201+
git read-tree --reset -u HEAD &&
202+
test_must_fail git merge-recursive --find-renames=200% $tail &&
203+
check_exact_renames
204+
'
205+
206+
test_expect_success 'disabled rename detection' '
207+
git read-tree --reset -u HEAD &&
208+
git merge-recursive --no-renames $tail &&
209+
check_no_renames
210+
'
211+
212+
test_expect_success 'last wins in --find-renames=<m> --find-renames=<n>' '
213+
git read-tree --reset -u HEAD &&
214+
test_must_fail git merge-recursive \
215+
--find-renames=$th0 --find-renames=$th2 $tail &&
216+
check_threshold_2
217+
'
218+
219+
test_expect_success '--find-renames resets threshold' '
220+
git read-tree --reset -u HEAD &&
221+
test_must_fail git merge-recursive \
222+
--find-renames=$th0 --find-renames $tail &&
223+
$check_50
224+
'
225+
226+
test_expect_success 'last wins in --no-renames --find-renames' '
227+
git read-tree --reset -u HEAD &&
228+
test_must_fail git merge-recursive --no-renames --find-renames $tail &&
229+
$check_50
230+
'
231+
232+
test_expect_success 'last wins in --find-renames --no-renames' '
233+
git read-tree --reset -u HEAD &&
234+
git merge-recursive --find-renames --no-renames $tail &&
235+
check_no_renames
236+
'
237+
238+
test_expect_success 'assumption for further tests: trivial merge succeeds' '
239+
git read-tree --reset -u HEAD &&
240+
git merge-recursive HEAD -- HEAD HEAD &&
241+
git diff --quiet --cached &&
242+
git merge-recursive --find-renames=$th0 HEAD -- HEAD HEAD &&
243+
git diff --quiet --cached &&
244+
git merge-recursive --find-renames=$th2 HEAD -- HEAD HEAD &&
245+
git diff --quiet --cached &&
246+
git merge-recursive --find-renames=100% HEAD -- HEAD HEAD &&
247+
git diff --quiet --cached &&
248+
git merge-recursive --no-renames HEAD -- HEAD HEAD &&
249+
git diff --quiet --cached
250+
'
251+
252+
test_expect_success '--find-renames rejects negative argument' '
253+
git read-tree --reset -u HEAD &&
254+
test_must_fail git merge-recursive --find-renames=-25 \
255+
HEAD -- HEAD HEAD &&
256+
git diff --quiet --cached
257+
'
258+
259+
test_expect_success '--find-renames rejects non-numbers' '
260+
git read-tree --reset -u HEAD &&
261+
test_must_fail git merge-recursive --find-renames=0xf \
262+
HEAD -- HEAD HEAD &&
263+
git diff --quiet --cached
264+
'
265+
266+
test_expect_success 'rename-threshold=<n> is a synonym for find-renames=<n>' '
267+
git read-tree --reset -u HEAD &&
268+
test_must_fail git merge-recursive --rename-threshold=$th0 $tail &&
269+
check_threshold_0
270+
'
271+
272+
test_expect_success 'last wins in --no-renames --rename-threshold=<n>' '
273+
git read-tree --reset -u HEAD &&
274+
test_must_fail git merge-recursive --no-renames --rename-threshold=$th0 $tail &&
275+
check_threshold_0
276+
'
277+
278+
test_expect_success 'last wins in --rename-threshold=<n> --no-renames' '
279+
git read-tree --reset -u HEAD &&
280+
git merge-recursive --rename-threshold=$th0 --no-renames $tail &&
281+
check_no_renames
282+
'
283+
284+
test_expect_success '--rename-threshold=<n> rejects negative argument' '
285+
git read-tree --reset -u HEAD &&
286+
test_must_fail git merge-recursive --rename-threshold=-25 \
287+
HEAD -- HEAD HEAD &&
288+
git diff --quiet --cached
289+
'
290+
291+
test_expect_success '--rename-threshold=<n> rejects non-numbers' '
292+
git read-tree --reset -u HEAD &&
293+
test_must_fail git merge-recursive --rename-threshold=0xf \
294+
HEAD -- HEAD HEAD &&
295+
git diff --quiet --cached
296+
'
297+
298+
test_expect_success 'last wins in --rename-threshold=<m> --find-renames=<n>' '
299+
git read-tree --reset -u HEAD &&
300+
test_must_fail git merge-recursive \
301+
--rename-threshold=$th0 --find-renames=$th2 $tail &&
302+
check_threshold_2
303+
'
304+
305+
test_expect_success 'last wins in --find-renames=<m> --rename-threshold=<n>' '
306+
git read-tree --reset -u HEAD &&
307+
test_must_fail git merge-recursive \
308+
--find-renames=$th2 --rename-threshold=$th0 $tail &&
309+
check_threshold_0
310+
'
311+
312+
test_done

0 commit comments

Comments
 (0)