Hi Elijah, On Fri, 11 Oct 2019, Elijah Newren via GitGitGadget wrote: > From: Elijah Newren <newren@xxxxxxxxx> > > Dscho noted a few things making this function hard to follow. > Restructure it a bit and add comments to make it easier to follow. The > restructurings include: > > * There was a special case if-check at the end of the function > checking whether someone just renamed a file within its original > directory, meaning that there could be no directory rename involved. > That check was slightly convoluted; it could be done in a more > straightforward fashion earlier in the function, and can be done > more cheaply too (no call to strncmp). > > * The conditions for advancing end_of_old and end_of_new before > calling strchr were both confusing and unnecessary. If either > points at a '/', then they need to be advanced in order to find the > next '/'. If either doesn't point at a '/', then advancing them one > char before calling strchr() doesn't hurt. So, just rip out the > if conditions and advance both before calling strchr(). > > Signed-off-by: Elijah Newren <newren@xxxxxxxxx> This commit message, as well as the patch, make a lot of sense to me. Thank you for doing this! Ciao, Dscho > --- > merge-recursive.c | 60 ++++++++++++++++++++++++++++------------------- > 1 file changed, 36 insertions(+), 24 deletions(-) > > diff --git a/merge-recursive.c b/merge-recursive.c > index 22a12cfeba..f80e48f623 100644 > --- a/merge-recursive.c > +++ b/merge-recursive.c > @@ -1943,8 +1943,8 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path, > char **old_dir, char **new_dir) > { > char *end_of_old, *end_of_new; > - int old_len, new_len; > > + /* Default return values: NULL, meaning no rename */ > *old_dir = NULL; > *new_dir = NULL; > > @@ -1955,43 +1955,55 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path, > * "a/b/c/d" was renamed to "a/b/some/thing/else" > * so, for this example, this function returns "a/b/c/d" in > * *old_dir and "a/b/some/thing/else" in *new_dir. > - * > - * Also, if the basename of the file changed, we don't care. We > - * want to know which portion of the directory, if any, changed. > + */ > + > + /* > + * If the basename of the file changed, we don't care. We want > + * to know which portion of the directory, if any, changed. > */ > end_of_old = strrchr(old_path, '/'); > end_of_new = strrchr(new_path, '/'); > - > if (end_of_old == NULL || end_of_new == NULL) > - return; > + return; /* We haven't modified *old_dir or *new_dir yet. */ > + > + /* Find the first non-matching character traversing backwards */ > while (*--end_of_new == *--end_of_old && > end_of_old != old_path && > end_of_new != new_path) > ; /* Do nothing; all in the while loop */ > + > /* > - * We've found the first non-matching character in the directory > - * paths. That means the current directory we were comparing > - * represents the rename. Move end_of_old and end_of_new back > - * to the full directory name. > + * If both got back to the beginning of their strings, then the > + * directory didn't change at all, only the basename did. > */ > - if (*end_of_old == '/') > - end_of_old++; > - if (*end_of_old != '/') > - end_of_new++; > - end_of_old = strchr(end_of_old, '/'); > - end_of_new = strchr(end_of_new, '/'); > + if (end_of_old == old_path && end_of_new == new_path && > + *end_of_old == *end_of_new) > + return; /* We haven't modified *old_dir or *new_dir yet. */ > > /* > - * It may have been the case that old_path and new_path were the same > - * directory all along. Don't claim a rename if they're the same. > + * We've found the first non-matching character in the directory > + * paths. That means the current characters we were looking at > + * were part of the first non-matching subdir name going back from > + * the end of the strings. Get the whole name by advancing both > + * end_of_old and end_of_new to the NEXT '/' character. That will > + * represent the entire directory rename. > + * > + * The reason for the increment is cases like > + * a/b/star/foo/whatever.c -> a/b/tar/foo/random.c > + * After dropping the basename and going back to the first > + * non-matching character, we're now comparing: > + * a/b/s and a/b/ > + * and we want to be comparing: > + * a/b/star/ and a/b/tar/ > + * but without the pre-increment, the one on the right would stay > + * a/b/. > */ > - old_len = end_of_old - old_path; > - new_len = end_of_new - new_path; > + end_of_old = strchr(++end_of_old, '/'); > + end_of_new = strchr(++end_of_new, '/'); > > - if (old_len != new_len || strncmp(old_path, new_path, old_len)) { > - *old_dir = xstrndup(old_path, old_len); > - *new_dir = xstrndup(new_path, new_len); > - } > + /* Copy the old and new directories into *old_dir and *new_dir. */ > + *old_dir = xstrndup(old_path, end_of_old - old_path); > + *new_dir = xstrndup(new_path, end_of_new - new_path); > } > > static void remove_hashmap_entries(struct hashmap *dir_renames, > -- > gitgitgadget > >