Commit e07c1e1a157b28c99378806aac8b6af9c65c6c52

Carlos Martín Nieto 2015-02-12T00:40:12

Merge pull request #2880 from ethomson/mkdir_root Ensure we can make a repo at the root of the filesystem

diff --git a/appveyor.yml b/appveyor.yml
index d11fec1..8ac6728 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,6 +3,8 @@ branches:
   only:
   - master
 environment:
+  GITTEST_INVASIVE_FILESYSTEM: 1
+
   matrix:
   - GENERATOR: "Visual Studio 11"
     ARCH: 32
diff --git a/src/fileops.c b/src/fileops.c
index 2ee9535..4a62d21 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -330,7 +330,7 @@ int git_futils_mkdir_withperf(
 {
 	int error = -1;
 	git_buf make_path = GIT_BUF_INIT;
-	ssize_t root = 0, min_root_len;
+	ssize_t root = 0, min_root_len, root_len;
 	char lastch = '/', *tail;
 	struct stat st;
 
@@ -343,22 +343,29 @@ int git_futils_mkdir_withperf(
 		goto done;
 	}
 
-	/* remove trailing slashes on path */
-	while (make_path.ptr[make_path.size - 1] == '/') {
-		make_path.size--;
-		make_path.ptr[make_path.size] = '\0';
-	}
+	/* Trim trailing slashes (except the root) */
+	if ((root_len = git_path_root(make_path.ptr)) < 0)
+		root_len = 0;
+	else
+		root_len++;
+
+	while (make_path.size > (size_t)root_len &&
+		make_path.ptr[make_path.size - 1] == '/')
+		make_path.ptr[--make_path.size] = '\0';
 
 	/* if we are not supposed to made the last element, truncate it */
 	if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
-		git_buf_rtruncate_at_char(&make_path, '/');
+		git_path_dirname_r(&make_path, make_path.ptr);
 		flags |= GIT_MKDIR_SKIP_LAST;
 	}
-	if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
-		git_buf_rtruncate_at_char(&make_path, '/');
+	if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
+		git_path_dirname_r(&make_path, make_path.ptr);
+	}
 
-	/* if nothing left after truncation, then we're done! */
-	if (!make_path.size) {
+	/* We were either given the root path (or trimmed it to
+	 * the root), we don't have anything to do.
+	 */
+	if (make_path.size <= (size_t)root_len) {
 		error = 0;
 		goto done;
 	}
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index e446cca..346f537 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -448,12 +448,8 @@ int p_stat(const char* path, struct stat* buf)
 	git_win32_path path_w;
 	int len;
 
-	if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
-		return -1;
-
-	git_win32__path_trim_end(path_w, len);
-
-	if (lstat_w(path_w, buf, false) < 0)
+	if ((len = git_win32_path_from_utf8(path_w, path)) < 0 ||
+		lstat_w(path_w, buf, false) < 0)
 		return -1;
 
 	/* The item is a symbolic link or mount point. No need to iterate
diff --git a/tests/core/stat.c b/tests/core/stat.c
index 2e4abfb..bd9b990 100644
--- a/tests/core/stat.c
+++ b/tests/core/stat.c
@@ -95,3 +95,20 @@ void test_core_stat__0(void)
 	cl_assert_error(ENOTDIR);
 }
 
+void test_core_stat__root(void)
+{
+	const char *sandbox = clar_sandbox_path();
+	git_buf root = GIT_BUF_INIT;
+	int root_len;
+	struct stat st;
+
+	root_len = git_path_root(sandbox);
+	cl_assert(root_len >= 0);
+
+	git_buf_set(&root, sandbox, root_len+1);
+
+	cl_must_pass(p_stat(root.ptr, &st));
+	cl_assert(S_ISDIR(st.st_mode));
+
+	git_buf_free(&root);
+}
diff --git a/tests/repo/init.c b/tests/repo/init.c
index ed86f6e..91747c9 100644
--- a/tests/repo/init.c
+++ b/tests/repo/init.c
@@ -714,3 +714,29 @@ void test_repo_init__init_with_initial_commit(void)
 
 	git_index_free(index);
 }
+
+void test_repo_init__at_filesystem_root(void)
+{
+	git_repository *repo;
+	const char *sandbox = clar_sandbox_path();
+	git_buf root = GIT_BUF_INIT;
+	int root_len;
+
+	if (!cl_getenv("GITTEST_INVASIVE_FILESYSTEM"))
+		cl_skip();
+
+	root_len = git_path_root(sandbox);
+	cl_assert(root_len >= 0);
+
+	git_buf_put(&root, sandbox, root_len+1);
+	git_buf_joinpath(&root, root.ptr, "libgit2_test_dir");
+
+	cl_assert(!git_path_exists(root.ptr));
+
+	cl_git_pass(git_repository_init(&repo, root.ptr, 0));
+	cl_assert(git_path_isdir(root.ptr));
+	cl_git_pass(git_futils_rmdir_r(root.ptr, NULL, GIT_RMDIR_REMOVE_FILES));
+
+	git_buf_free(&root);
+	git_repository_free(repo);
+}