Skip to content

Commit 49b8133

Browse files
newrengitster
authored andcommitted
merge-recursive: fix merging a subdirectory into the root directory
We allow renaming all entries in e.g. a directory named z/ into a directory named y/ to be detected as a z/ -> y/ rename, so that if the other side of history adds any files to the directory z/ in the mean time, we can provide the hint that they should be moved to y/. There is no reason to not allow 'y/' to be the root directory, but the code did not handle that case correctly. Add a testcase and the necessary special checks to support this case. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d3eebaa commit 49b8133

File tree

2 files changed

+163
-3
lines changed

2 files changed

+163
-3
lines changed

merge-recursive.c

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,6 +1931,16 @@ static char *apply_dir_rename(struct dir_rename_entry *entry,
19311931
return NULL;
19321932

19331933
oldlen = strlen(entry->dir);
1934+
if (entry->new_dir.len == 0)
1935+
/*
1936+
* If someone renamed/merged a subdirectory into the root
1937+
* directory (e.g. 'some/subdir' -> ''), then we want to
1938+
* avoid returning
1939+
* '' + '/filename'
1940+
* as the rename; we need to make old_path + oldlen advance
1941+
* past the '/' character.
1942+
*/
1943+
oldlen++;
19341944
newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
19351945
strbuf_grow(&new_path, newlen);
19361946
strbuf_addbuf(&new_path, &entry->new_dir);
@@ -1963,8 +1973,26 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
19631973
*/
19641974
end_of_old = strrchr(old_path, '/');
19651975
end_of_new = strrchr(new_path, '/');
1966-
if (end_of_old == NULL || end_of_new == NULL)
1967-
return; /* We haven't modified *old_dir or *new_dir yet. */
1976+
1977+
/*
1978+
* If end_of_old is NULL, old_path wasn't in a directory, so there
1979+
* could not be a directory rename (our rule elsewhere that a
1980+
* directory which still exists is not considered to have been
1981+
* renamed means the root directory can never be renamed -- because
1982+
* the root directory always exists).
1983+
*/
1984+
if (end_of_old == NULL)
1985+
return; /* Note: *old_dir and *new_dir are still NULL */
1986+
1987+
/*
1988+
* If new_path contains no directory (end_of_new is NULL), then we
1989+
* have a rename of old_path's directory to the root directory.
1990+
*/
1991+
if (end_of_new == NULL) {
1992+
*old_dir = xstrndup(old_path, end_of_old - old_path);
1993+
*new_dir = xstrdup("");
1994+
return;
1995+
}
19681996

19691997
/* Find the first non-matching character traversing backwards */
19701998
while (*--end_of_new == *--end_of_old &&
@@ -1978,7 +2006,25 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
19782006
*/
19792007
if (end_of_old == old_path && end_of_new == new_path &&
19802008
*end_of_old == *end_of_new)
1981-
return; /* We haven't modified *old_dir or *new_dir yet. */
2009+
return; /* Note: *old_dir and *new_dir are still NULL */
2010+
2011+
/*
2012+
* If end_of_new got back to the beginning of its string, and
2013+
* end_of_old got back to the beginning of some subdirectory, then
2014+
* we have a rename/merge of a subdirectory into the root, which
2015+
* needs slightly special handling.
2016+
*
2017+
* Note: There is no need to consider the opposite case, with a
2018+
* rename/merge of the root directory into some subdirectory
2019+
* because as noted above the root directory always exists so it
2020+
* cannot be considered to be renamed.
2021+
*/
2022+
if (end_of_new == new_path &&
2023+
end_of_old != old_path && end_of_old[-1] == '/') {
2024+
*old_dir = xstrndup(old_path, --end_of_old - old_path);
2025+
*new_dir = xstrdup("");
2026+
return;
2027+
}
19822028

19832029
/*
19842030
* We've found the first non-matching character in the directory

t/t6043-merge-rename-directories.sh

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4051,6 +4051,120 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c
40514051
)
40524052
'
40534053

4054+
# Testcase 12d, Rename/merge of subdirectory into the root
4055+
# Commit O: a/b/subdir/foo
4056+
# Commit A: subdir/foo
4057+
# Commit B: a/b/subdir/foo, a/b/bar
4058+
# Expected: subdir/foo, bar
4059+
4060+
test_expect_success '12d-setup: Rename/merge subdir into the root, variant 1' '
4061+
test_create_repo 12d &&
4062+
(
4063+
cd 12d &&
4064+
4065+
mkdir -p a/b/subdir &&
4066+
test_commit a/b/subdir/foo &&
4067+
4068+
git branch O &&
4069+
git branch A &&
4070+
git branch B &&
4071+
4072+
git checkout A &&
4073+
mkdir subdir &&
4074+
git mv a/b/subdir/foo.t subdir/foo.t &&
4075+
test_tick &&
4076+
git commit -m "A" &&
4077+
4078+
git checkout B &&
4079+
test_commit a/b/bar
4080+
)
4081+
'
4082+
4083+
test_expect_success '12d-check: Rename/merge subdir into the root, variant 1' '
4084+
(
4085+
cd 12d &&
4086+
4087+
git checkout A^0 &&
4088+
4089+
git -c merge.directoryRenames=true merge -s recursive B^0 &&
4090+
4091+
git ls-files -s >out &&
4092+
test_line_count = 2 out &&
4093+
4094+
git rev-parse >actual \
4095+
HEAD:subdir/foo.t HEAD:bar.t &&
4096+
git rev-parse >expect \
4097+
O:a/b/subdir/foo.t B:a/b/bar.t &&
4098+
test_cmp expect actual &&
4099+
4100+
git hash-object bar.t >actual &&
4101+
git rev-parse B:a/b/bar.t >expect &&
4102+
test_cmp expect actual &&
4103+
4104+
test_must_fail git rev-parse HEAD:a/b/subdir/foo.t &&
4105+
test_must_fail git rev-parse HEAD:a/b/bar.t &&
4106+
test_path_is_missing a/ &&
4107+
test_path_is_file bar.t
4108+
)
4109+
'
4110+
4111+
# Testcase 12e, Rename/merge of subdirectory into the root
4112+
# Commit O: a/b/foo
4113+
# Commit A: foo
4114+
# Commit B: a/b/foo, a/b/bar
4115+
# Expected: foo, bar
4116+
4117+
test_expect_success '12e-setup: Rename/merge subdir into the root, variant 2' '
4118+
test_create_repo 12e &&
4119+
(
4120+
cd 12e &&
4121+
4122+
mkdir -p a/b &&
4123+
test_commit a/b/foo &&
4124+
4125+
git branch O &&
4126+
git branch A &&
4127+
git branch B &&
4128+
4129+
git checkout A &&
4130+
mkdir subdir &&
4131+
git mv a/b/foo.t foo.t &&
4132+
test_tick &&
4133+
git commit -m "A" &&
4134+
4135+
git checkout B &&
4136+
test_commit a/b/bar
4137+
)
4138+
'
4139+
4140+
test_expect_success '12e-check: Rename/merge subdir into the root, variant 2' '
4141+
(
4142+
cd 12e &&
4143+
4144+
git checkout A^0 &&
4145+
4146+
git -c merge.directoryRenames=true merge -s recursive B^0 &&
4147+
4148+
git ls-files -s >out &&
4149+
test_line_count = 2 out &&
4150+
4151+
git rev-parse >actual \
4152+
HEAD:foo.t HEAD:bar.t &&
4153+
git rev-parse >expect \
4154+
O:a/b/foo.t B:a/b/bar.t &&
4155+
test_cmp expect actual &&
4156+
4157+
git hash-object bar.t >actual &&
4158+
git rev-parse B:a/b/bar.t >expect &&
4159+
test_cmp expect actual &&
4160+
4161+
test_must_fail git rev-parse HEAD:a/b/foo.t &&
4162+
test_must_fail git rev-parse HEAD:a/b/bar.t &&
4163+
test_path_is_missing a/ &&
4164+
test_path_is_file bar.t
4165+
)
4166+
'
4167+
40544168
###########################################################################
40554169
# SECTION 13: Checking informational and conflict messages
40564170
#

0 commit comments

Comments
 (0)