diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2019-12-03 23:15:47 +1100 |
---|---|---|
committer | Patrick Steinhardt <ps@pks.im> | 2019-12-10 11:08:19 +0100 |
commit | a673ce14b814cfd785a80e3e54b52683e6427f70 (patch) | |
tree | 229a1afcbca4c15ee25ae96807f2e8d5885c8710 | |
parent | 6bd0740125e58d6eb1a250fd6628cd15e6a34174 (diff) | |
download | libgit2-a673ce14b814cfd785a80e3e54b52683e6427f70.tar.gz |
path: support non-ascii drive letters on dos
Windows/DOS only supports drive letters that are alpha characters A-Z.
However, you can `subst` any one-character as a drive letter, including
numbers or even emoji. Test that we can identify emoji as drive
letters.
-rw-r--r-- | src/path.c | 38 | ||||
-rw-r--r-- | tests/path/core.c | 11 |
2 files changed, 41 insertions, 8 deletions
diff --git a/src/path.c b/src/path.c index 7397bb465..fa94f0058 100644 --- a/src/path.c +++ b/src/path.c @@ -21,7 +21,29 @@ #include <stdio.h> #include <ctype.h> -#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':') +static int dos_drive_prefix_length(const char *path) +{ + int i; + + /* + * Does it start with an ASCII letter (i.e. highest bit not set), + * followed by a colon? + */ + if (!(0x80 & (unsigned char)*path)) + return *path && path[1] == ':' ? 2 : 0; + + /* + * While drive letters must be letters of the English alphabet, it is + * possible to assign virtually _any_ Unicode character via `subst` as + * a drive letter to "virtual drives". Even `1`, or `รค`. Or fun stuff + * like this: + * + * subst ึ: %USERPROFILE%\Desktop + */ + for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) + ; /* skip first UTF-8 character */ + return path[i] == ':' ? i + 1 : 0; +} #ifdef GIT_WIN32 static bool looks_like_network_computer_name(const char *path, int pos) @@ -123,11 +145,11 @@ static int win32_prefix_length(const char *path, int len) GIT_UNUSED(len); #else /* - * Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return - * 'C:/' here + * Mimic unix behavior where '/.git' returns '/': 'C:/.git' + * will return 'C:/' here */ - if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) - return 2; + if (dos_drive_prefix_length(path) == len) + return len; /* * Similarly checks if we're dealing with a network computer name @@ -260,11 +282,11 @@ const char *git_path_topdir(const char *path) int git_path_root(const char *path) { - int offset = 0; + int offset = 0, prefix_len; /* Does the root of the path look like a windows drive ? */ - if (LOOKS_LIKE_DRIVE_PREFIX(path)) - offset += 2; + if ((prefix_len = dos_drive_prefix_length(path))) + offset += prefix_len; #ifdef GIT_WIN32 /* Are we dealing with a windows network path? */ diff --git a/tests/path/core.c b/tests/path/core.c index bbcded083..95e5c8c94 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -352,3 +352,14 @@ void test_path_core__join_unrooted(void) git_buf_dispose(&out); } + +void test_path_core__join_unrooted_respects_funny_windows_roots(void) +{ + test_join_unrooted("๐ฉ:/foo/bar/foobar", 9, "bar/foobar", "๐ฉ:/foo"); + test_join_unrooted("๐ฉ:/foo/bar/foobar", 13, "foobar", "๐ฉ:/foo/bar"); + test_join_unrooted("๐ฉ:/foo", 5, "๐ฉ:/foo", "๐ฉ:/asdf"); + test_join_unrooted("๐ฉ:/foo/bar", 5, "๐ฉ:/foo/bar", "๐ฉ:/asdf"); + test_join_unrooted("๐ฉ:/foo/bar/foobar", 9, "๐ฉ:/foo/bar/foobar", "๐ฉ:/foo"); + test_join_unrooted("๐ฉ:/foo/bar/foobar", 13, "๐ฉ:/foo/bar/foobar", "๐ฉ:/foo/bar"); + test_join_unrooted("๐ฉ:/foo/bar/foobar", 9, "๐ฉ:/foo/bar/foobar", "๐ฉ:/foo/"); +} |