Skip to content

Commit 14ff351

Browse files
committed
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.
1 parent 85d4ff7 commit 14ff351

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

src/path.c

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,29 @@
2121
#include <stdio.h>
2222
#include <ctype.h>
2323

24-
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
24+
static int dos_drive_prefix_length(const char *path)
25+
{
26+
int i;
27+
28+
/*
29+
* Does it start with an ASCII letter (i.e. highest bit not set),
30+
* followed by a colon?
31+
*/
32+
if (!(0x80 & (unsigned char)*path))
33+
return *path && path[1] == ':' ? 2 : 0;
34+
35+
/*
36+
* While drive letters must be letters of the English alphabet, it is
37+
* possible to assign virtually _any_ Unicode character via `subst` as
38+
* a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
39+
* like this:
40+
*
41+
* subst ֍: %USERPROFILE%\Desktop
42+
*/
43+
for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
44+
; /* skip first UTF-8 character */
45+
return path[i] == ':' ? i + 1 : 0;
46+
}
2547

2648
#ifdef GIT_WIN32
2749
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)
123145
GIT_UNUSED(len);
124146
#else
125147
/*
126-
* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
127-
* 'C:/' here
148+
* Mimic unix behavior where '/.git' returns '/': 'C:/.git'
149+
* will return 'C:/' here
128150
*/
129-
if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path))
130-
return 2;
151+
if (dos_drive_prefix_length(path) == len)
152+
return len;
131153

132154
/*
133155
* Similarly checks if we're dealing with a network computer name
@@ -272,11 +294,11 @@ const char *git_path_topdir(const char *path)
272294

273295
int git_path_root(const char *path)
274296
{
275-
int offset = 0;
297+
int offset = 0, prefix_len;
276298

277299
/* Does the root of the path look like a windows drive ? */
278-
if (LOOKS_LIKE_DRIVE_PREFIX(path))
279-
offset += 2;
300+
if ((prefix_len = dos_drive_prefix_length(path)))
301+
offset += prefix_len;
280302

281303
#ifdef GIT_WIN32
282304
/* Are we dealing with a windows network path? */

tests/path/core.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,14 @@ void test_path_core__join_unrooted(void)
362362

363363
git_buf_dispose(&out);
364364
}
365+
366+
void test_path_core__join_unrooted_respects_funny_windows_roots(void)
367+
{
368+
test_join_unrooted("💩:/foo/bar/foobar", 9, "bar/foobar", "💩:/foo");
369+
test_join_unrooted("💩:/foo/bar/foobar", 13, "foobar", "💩:/foo/bar");
370+
test_join_unrooted("💩:/foo", 5, "💩:/foo", "💩:/asdf");
371+
test_join_unrooted("💩:/foo/bar", 5, "💩:/foo/bar", "💩:/asdf");
372+
test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo");
373+
test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar");
374+
test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/");
375+
}

0 commit comments

Comments
 (0)