diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2019-12-03 23:15:47 +1100 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2019-12-10 18:11:45 +1000 |
commit | 14ff3516e5f4203838a0edb044c6622b8e3a3755 (patch) | |
tree | f4105016769743a7dcf987b2bbf10a405df18f95 | |
parent | 85d4ff77e2b539c525f36d0860f3faa8e68308b7 (diff) | |
download | libgit2-14ff3516e5f4203838a0edb044c6622b8e3a3755.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 7241a3500..625b95c0d 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 @@ -272,11 +294,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 3a68a9338..48b518c8d 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -362,3 +362,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/"); +} |