Skip to content

Commit d980035

Browse files
committed
Merge branch 'en/merge-recursive-directory-rename-fixes'
When all files from some subdirectory were renamed to the root directory, the directory rename heuristics would fail to detect that as a rename/merge of the subdirectory to the root directory, which has been corrected. * en/merge-recursive-directory-rename-fixes: t604[236]: do not run setup in separate tests merge-recursive: fix merging a subdirectory into the root directory merge-recursive: clean up get_renamed_dir_portion()
2 parents 0c51181 + da1e295 commit d980035

4 files changed

+582
-336
lines changed

merge-recursive.c

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,6 +1951,16 @@ static char *apply_dir_rename(struct dir_rename_entry *entry,
19511951
return NULL;
19521952

19531953
oldlen = strlen(entry->dir);
1954+
if (entry->new_dir.len == 0)
1955+
/*
1956+
* If someone renamed/merged a subdirectory into the root
1957+
* directory (e.g. 'some/subdir' -> ''), then we want to
1958+
* avoid returning
1959+
* '' + '/filename'
1960+
* as the rename; we need to make old_path + oldlen advance
1961+
* past the '/' character.
1962+
*/
1963+
oldlen++;
19541964
newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
19551965
strbuf_grow(&new_path, newlen);
19561966
strbuf_addbuf(&new_path, &entry->new_dir);
@@ -1963,8 +1973,8 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
19631973
char **old_dir, char **new_dir)
19641974
{
19651975
char *end_of_old, *end_of_new;
1966-
int old_len, new_len;
19671976

1977+
/* Default return values: NULL, meaning no rename */
19681978
*old_dir = NULL;
19691979
*new_dir = NULL;
19701980

@@ -1975,43 +1985,91 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
19751985
* "a/b/c/d" was renamed to "a/b/some/thing/else"
19761986
* so, for this example, this function returns "a/b/c/d" in
19771987
* *old_dir and "a/b/some/thing/else" in *new_dir.
1978-
*
1979-
* Also, if the basename of the file changed, we don't care. We
1980-
* want to know which portion of the directory, if any, changed.
1988+
*/
1989+
1990+
/*
1991+
* If the basename of the file changed, we don't care. We want
1992+
* to know which portion of the directory, if any, changed.
19811993
*/
19821994
end_of_old = strrchr(old_path, '/');
19831995
end_of_new = strrchr(new_path, '/');
19841996

1985-
if (end_of_old == NULL || end_of_new == NULL)
1997+
/*
1998+
* If end_of_old is NULL, old_path wasn't in a directory, so there
1999+
* could not be a directory rename (our rule elsewhere that a
2000+
* directory which still exists is not considered to have been
2001+
* renamed means the root directory can never be renamed -- because
2002+
* the root directory always exists).
2003+
*/
2004+
if (end_of_old == NULL)
2005+
return; /* Note: *old_dir and *new_dir are still NULL */
2006+
2007+
/*
2008+
* If new_path contains no directory (end_of_new is NULL), then we
2009+
* have a rename of old_path's directory to the root directory.
2010+
*/
2011+
if (end_of_new == NULL) {
2012+
*old_dir = xstrndup(old_path, end_of_old - old_path);
2013+
*new_dir = xstrdup("");
19862014
return;
2015+
}
2016+
2017+
/* Find the first non-matching character traversing backwards */
19872018
while (*--end_of_new == *--end_of_old &&
19882019
end_of_old != old_path &&
19892020
end_of_new != new_path)
19902021
; /* Do nothing; all in the while loop */
2022+
19912023
/*
1992-
* We've found the first non-matching character in the directory
1993-
* paths. That means the current directory we were comparing
1994-
* represents the rename. Move end_of_old and end_of_new back
1995-
* to the full directory name.
2024+
* If both got back to the beginning of their strings, then the
2025+
* directory didn't change at all, only the basename did.
19962026
*/
1997-
if (*end_of_old == '/')
1998-
end_of_old++;
1999-
if (*end_of_old != '/')
2000-
end_of_new++;
2001-
end_of_old = strchr(end_of_old, '/');
2002-
end_of_new = strchr(end_of_new, '/');
2027+
if (end_of_old == old_path && end_of_new == new_path &&
2028+
*end_of_old == *end_of_new)
2029+
return; /* Note: *old_dir and *new_dir are still NULL */
20032030

20042031
/*
2005-
* It may have been the case that old_path and new_path were the same
2006-
* directory all along. Don't claim a rename if they're the same.
2032+
* If end_of_new got back to the beginning of its string, and
2033+
* end_of_old got back to the beginning of some subdirectory, then
2034+
* we have a rename/merge of a subdirectory into the root, which
2035+
* needs slightly special handling.
2036+
*
2037+
* Note: There is no need to consider the opposite case, with a
2038+
* rename/merge of the root directory into some subdirectory
2039+
* because as noted above the root directory always exists so it
2040+
* cannot be considered to be renamed.
20072041
*/
2008-
old_len = end_of_old - old_path;
2009-
new_len = end_of_new - new_path;
2010-
2011-
if (old_len != new_len || strncmp(old_path, new_path, old_len)) {
2012-
*old_dir = xstrndup(old_path, old_len);
2013-
*new_dir = xstrndup(new_path, new_len);
2042+
if (end_of_new == new_path &&
2043+
end_of_old != old_path && end_of_old[-1] == '/') {
2044+
*old_dir = xstrndup(old_path, --end_of_old - old_path);
2045+
*new_dir = xstrdup("");
2046+
return;
20142047
}
2048+
2049+
/*
2050+
* We've found the first non-matching character in the directory
2051+
* paths. That means the current characters we were looking at
2052+
* were part of the first non-matching subdir name going back from
2053+
* the end of the strings. Get the whole name by advancing both
2054+
* end_of_old and end_of_new to the NEXT '/' character. That will
2055+
* represent the entire directory rename.
2056+
*
2057+
* The reason for the increment is cases like
2058+
* a/b/star/foo/whatever.c -> a/b/tar/foo/random.c
2059+
* After dropping the basename and going back to the first
2060+
* non-matching character, we're now comparing:
2061+
* a/b/s and a/b/
2062+
* and we want to be comparing:
2063+
* a/b/star/ and a/b/tar/
2064+
* but without the pre-increment, the one on the right would stay
2065+
* a/b/.
2066+
*/
2067+
end_of_old = strchr(++end_of_old, '/');
2068+
end_of_new = strchr(++end_of_new, '/');
2069+
2070+
/* Copy the old and new directories into *old_dir and *new_dir. */
2071+
*old_dir = xstrndup(old_path, end_of_old - old_path);
2072+
*new_dir = xstrndup(new_path, end_of_new - new_path);
20152073
}
20162074

20172075
static void remove_hashmap_entries(struct hashmap *dir_renames,

0 commit comments

Comments
 (0)