Commit 683ff4f323736b6c33110a271f11635dbec51b88

Edward Thomson 2017-02-10T11:01:23

Merge pull request #4111 from pks-t/pks/dos-prefix dirname with DOS prefixes

diff --git a/src/path.c b/src/path.c
index bffde93..c3d3eb1 100644
--- a/src/path.c
+++ b/src/path.c
@@ -111,13 +111,41 @@ Exit:
 }
 
 /*
+ * Determine if the path is a Windows prefix and, if so, returns
+ * its actual lentgh. If it is not a prefix, returns -1.
+ */
+static int win32_prefix_length(const char *path, int len)
+{
+#ifndef GIT_WIN32
+	GIT_UNUSED(path);
+	GIT_UNUSED(len);
+#else
+	/*
+	 * Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
+	 * 'C:/' here
+	 */
+	if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path))
+		return 2;
+
+	/*
+	 * Similarly checks if we're dealing with a network computer name
+	 * '//computername/.git' will return '//computername/'
+	 */
+	if (looks_like_network_computer_name(path, len))
+		return len;
+#endif
+
+	return -1;
+}
+
+/*
  * Based on the Android implementation, BSD licensed.
  * Check http://android.git.kernel.org/
  */
 int git_path_dirname_r(git_buf *buffer, const char *path)
 {
 	const char *endp;
-	int result, len;
+	int is_prefix = 0, len;
 
 	/* Empty or NULL string gets treated as "." */
 	if (path == NULL || *path == '\0') {
@@ -131,6 +159,11 @@ int git_path_dirname_r(git_buf *buffer, const char *path)
 	while (endp > path && *endp == '/')
 		endp--;
 
+	if ((len = win32_prefix_length(path, endp - path + 1)) > 0) {
+		is_prefix = 1;
+		goto Exit;
+	}
+
 	/* Find the start of the dir */
 	while (endp > path && *endp != '/')
 		endp--;
@@ -146,35 +179,23 @@ int git_path_dirname_r(git_buf *buffer, const char *path)
 		endp--;
 	} while (endp > path && *endp == '/');
 
-	/* Cast is safe because max path < max int */
-	len = (int)(endp - path + 1);
-
-#ifdef GIT_WIN32
-	/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
-		'C:/' here */
-
-	if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
-		len = 3;
+	if ((len = win32_prefix_length(path, endp - path + 1)) > 0) {
+		is_prefix = 1;
 		goto Exit;
 	}
 
-	/* Similarly checks if we're dealing with a network computer name
-		'//computername/.git' will return '//computername/' */
-
-	if (looks_like_network_computer_name(path, len)) {
-		len++;
-		goto Exit;
-	}
-
-#endif
+	/* Cast is safe because max path < max int */
+	len = (int)(endp - path + 1);
 
 Exit:
-	result = len;
-
-	if (buffer != NULL && git_buf_set(buffer, path, len) < 0)
-		return -1;
+	if (buffer) {
+		if (git_buf_set(buffer, path, len) < 0)
+			return -1;
+		if (is_prefix && git_buf_putc(buffer, '/') < 0)
+			return -1;
+	}
 
-	return result;
+	return len;
 }
 
 
diff --git a/tests/core/path.c b/tests/core/path.c
index 71c6eda..fefe2ae 100644
--- a/tests/core/path.c
+++ b/tests/core/path.c
@@ -89,8 +89,12 @@ void test_core_path__00_dirname(void)
 	check_dirname(REP16("/abc"), REP15("/abc"));
 
 #ifdef GIT_WIN32
+	check_dirname("C:/", "C:/");
+	check_dirname("C:", "C:/");
 	check_dirname("C:/path/", "C:/");
 	check_dirname("C:/path", "C:/");
+	check_dirname("//computername/", "//computername/");
+	check_dirname("//computername", "//computername/");
 	check_dirname("//computername/path/", "//computername/");
 	check_dirname("//computername/path", "//computername/");
 	check_dirname("//computername/sub/path/", "//computername/sub");
diff --git a/tests/repo/discover.c b/tests/repo/discover.c
index 48aa275..abb7bd1 100644
--- a/tests/repo/discover.c
+++ b/tests/repo/discover.c
@@ -199,3 +199,12 @@ void test_repo_discover__discovery_starting_at_file_succeeds(void)
 
 	ensure_repository_discover(SUB_REPOSITORY_FOLDER "/file", ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
 }
+
+void test_repo_discover__discovery_starting_at_system_root_causes_no_hang(void)
+{
+#ifdef GIT_WIN32
+	git_buf out = GIT_BUF_INIT;
+	cl_git_fail(git_repository_discover(&out, "C:/", 0, NULL));
+	cl_git_fail(git_repository_discover(&out, "//localhost/", 0, NULL));
+#endif
+}
diff --git a/tests/resources/testrepo.git/index b/tests/resources/testrepo.git/index
old mode 100644
new mode 100755
Binary files a/tests/resources/testrepo.git/index and b/tests/resources/testrepo.git/index differ