Hi Elijah,

On Fri, 11 Oct 2019, Elijah Newren via GitGitGadget wrote:

> From: Elijah Newren <new...@gmail.com>
>
> 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 <new...@gmail.com>

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
>
>

Reply via email to