]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Fix `GIT_CEILING_DIRECTORIES` with `C:\` and the likes
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Wed, 23 Mar 2022 22:00:41 +0000 (23:00 +0100)
committerJohannes Schindelin <johannes.schindelin@gmx.de>
Wed, 23 Mar 2022 23:21:08 +0000 (00:21 +0100)
When determining the length of the longest ancestor of a given path with
respect to to e.g. `GIT_CEILING_DIRECTORIES`, we special-case the root
directory by returning 0 (i.e. we pretend that the path `/` does not end
in a slash by virtually stripping it).

That is the correct behavior because when normalizing paths, the root
directory is special: all other directory paths have their trailing
slash stripped, but not the root directory's path (because it would
become the empty string, which is not a legal path).

However, this special-casing of the root directory in
`longest_ancestor_length()` completely forgets about Windows-style root
directories, e.g. `C:\`. These _also_ get normalized with a trailing
slash (because `C:` would actually refer to the current directory on
that drive, not necessarily to its root directory).

In fc56c7b34b (mingw: accomodate t0060-path-utils for MSYS2,
2016-01-27), we almost got it right. We noticed that
`longest_ancestor_length()` expects a slash _after_ the matched prefix,
and if the prefix already ends in a slash, the normalized path won't
ever match and -1 is returned.

But then that commit went astray: The correct fix is not to adjust the
_tests_ to expect an incorrect -1 when that function is fed a prefix
that ends in a slash, but instead to treat such a prefix as if the
trailing slash had been removed.

Likewise, that function needs to handle the case where it is fed a path
that ends in a slash (not only a prefix that ends in a slash): if it
matches the prefix (plus trailing slash), we still need to verify that
the path does not end there, otherwise the prefix is not actually an
ancestor of the path but identical to it (and we need to return -1 in
that case).

With these two adjustments, we no longer need to play games in t0060
where we only add `$rootoff` if the passed prefix is different from the
MSYS2 pseudo root, instead we also add it for the MSYS2 pseudo root
itself. We do have to be careful to skip that logic entirely for Windows
paths, though, because they do are not subject to that MSYS2 pseudo root
treatment.

This patch fixes the scenario where a user has set
`GIT_CEILING_DIRECTORIES=C:\`, which would be ignored otherwise.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
path.c
t/t0060-path-utils.sh

diff --git a/path.c b/path.c
index 7b385e5eb282276f5d2e0becde48d5e09434d6b1..853e7165c89fd094f1dca268d3b5ccb7b39857fd 100644 (file)
--- a/path.c
+++ b/path.c
@@ -1218,11 +1218,15 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes)
                const char *ceil = prefixes->items[i].string;
                int len = strlen(ceil);
 
-               if (len == 1 && ceil[0] == '/')
-                       len = 0; /* root matches anything, with length 0 */
-               else if (!strncmp(path, ceil, len) && path[len] == '/')
-                       ; /* match of length len */
-               else
+               /*
+                * For root directories (`/`, `C:/`, `//server/share/`)
+                * adjust the length to exclude the trailing slash.
+                */
+               if (len > 0 && ceil[len - 1] == '/')
+                       len--;
+
+               if (strncmp(path, ceil, len) ||
+                   path[len] != '/' || !path[len + 1])
                        continue; /* no match */
 
                if (len > max_len)
index 56db5c8abab62e7ae327bece57768f0f4ae6f0e4..f538264cdd33826ca1bad474256b438e5606fa56 100755 (executable)
@@ -55,12 +55,15 @@ fi
 ancestor() {
        # We do some math with the expected ancestor length.
        expected=$3
-       if test -n "$rootoff" && test "x$expected" != x-1; then
-               expected=$(($expected-$rootslash))
-               test $expected -lt 0 ||
-               expected=$(($expected+$rootoff))
-       fi
-       test_expect_success "longest ancestor: $1 $2 => $expected" \
+       case "$rootoff,$expected,$2" in
+       *,*,//*) ;; # leave UNC paths alone
+       [0-9]*,[0-9]*,/*)
+               # On Windows, expect MSYS2 pseudo root translation for
+               # Unix-style absolute paths
+               expected=$(($expected-$rootslash+$rootoff))
+               ;;
+       esac
+       test_expect_success $4 "longest ancestor: $1 $2 => $expected" \
        "actual=\$(test-tool path-utils longest_ancestor_length '$1' '$2') &&
         test \"\$actual\" = '$expected'"
 }
@@ -156,6 +159,11 @@ ancestor /foo/bar /foo 4
 ancestor /foo/bar /foo:/bar 4
 ancestor /foo/bar /bar -1
 
+# Windows-specific: DOS drives, network shares
+ancestor C:/Users/me C:/ 2 MINGW
+ancestor D:/Users/me C:/ -1 MINGW
+ancestor //server/share/my-directory //server/share/ 14 MINGW
+
 test_expect_success 'strip_path_suffix' '
        test c:/msysgit = $(test-tool path-utils strip_path_suffix \
                c:/msysgit/libexec//git-core libexec/git-core)