Commit 3fe046cfdba414f69b09e76da2a550be96eeab7e

Russell Belfer 2013-06-29T13:13:38

Add BARE option to git_repository_open_ext This adds a BARE option to git_repository_open_ext which allows a fast open path that still knows how to read gitlinks and to search for the actual .git directory from a subdirectory. `git_repository_open_bare` is still simpler and faster, but having a gitlink aware fast open is very useful for submodules where we want to quickly be able to peek at the HEAD and index data without doing any other meaningful repo operations.

diff --git a/include/git2/repository.h b/include/git2/repository.h
index c810519..807d834 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -94,10 +94,14 @@ GIT_EXTERN(int) git_repository_discover(
  *   changes from the `stat` system call).  (E.g. Searching in a user's home
  *   directory "/home/user/source/" will not return "/.git/" as the found
  *   repo if "/" is a different filesystem than "/home".)
+ * * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless
+ *   of core.bare config, and defer loading config file for faster setup.
+ *   Unlike `git_repository_open_bare`, this can follow gitlinks.
  */
 typedef enum {
 	GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0),
 	GIT_REPOSITORY_OPEN_CROSS_FS  = (1 << 1),
+	GIT_REPOSITORY_OPEN_BARE      = (1 << 2),
 } git_repository_open_flag_t;
 
 /**
diff --git a/src/repository.c b/src/repository.c
index ed9469c..bd7ef54 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -266,7 +266,7 @@ static int find_ceiling_dir_offset(
 			buf[--len] = '\0';
 
 		if (!strncmp(path, buf2, len) &&
-			path[len] == '/' &&
+			(path[len] == '/' || !path[len]) &&
 			len > max_len)
 		{
 			max_len = len;
@@ -322,17 +322,18 @@ static int find_repo(
 	git_buf path = GIT_BUF_INIT;
 	struct stat st;
 	dev_t initial_device = 0;
-	bool try_with_dot_git = false;
+	bool try_with_dot_git = ((flags & GIT_REPOSITORY_OPEN_BARE) != 0);
 	int ceiling_offset;
 
 	git_buf_free(repo_path);
 
-	if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0)
+	if ((error = git_path_prettify(&path, start_path, NULL)) < 0)
 		return error;
 
 	ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
 
-	if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
+	if (!try_with_dot_git &&
+		(error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
 		return error;
 
 	while (!error && !git_buf_len(repo_path)) {
@@ -384,7 +385,7 @@ static int find_repo(
 		try_with_dot_git = !try_with_dot_git;
 	}
 
-	if (!error && parent_path != NULL) {
+	if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
 		if (!git_buf_len(repo_path))
 			git_buf_clear(parent_path);
 		else {
@@ -460,7 +461,9 @@ int git_repository_open_ext(
 	repo->path_repository = git_buf_detach(&path);
 	GITERR_CHECK_ALLOC(repo->path_repository);
 
-	if ((error = load_config_data(repo)) < 0 ||
+	if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
+		repo->is_bare = 1;
+	else if ((error = load_config_data(repo)) < 0 ||
 		(error = load_workdir(repo, &parent)) < 0)
 	{
 		git_repository_free(repo);
diff --git a/tests-clar/repo/open.c b/tests-clar/repo/open.c
index 8408585..f386612 100644
--- a/tests-clar/repo/open.c
+++ b/tests-clar/repo/open.c
@@ -69,14 +69,23 @@ void test_repo_open__open_with_discover(void)
 	cl_fixture_cleanup("attr");
 }
 
+static void make_gitlink_dir(const char *dir, const char *linktext)
+{
+	git_buf path = GIT_BUF_INIT;
+
+	cl_git_pass(git_futils_mkdir(dir, NULL, 0777, GIT_MKDIR_VERIFY_DIR));
+	cl_git_pass(git_buf_joinpath(&path, dir, ".git"));
+	cl_git_rewritefile(path.ptr, linktext);
+	git_buf_free(&path);
+}
+
 void test_repo_open__gitlinked(void)
 {
 	/* need to have both repo dir and workdir set up correctly */
 	git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
 	git_repository *repo2;
 
-	cl_must_pass(p_mkdir("alternate", 0777));
-	cl_git_mkfile("alternate/.git", "gitdir: ../empty_standard_repo/.git");
+	make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git");
 
 	cl_git_pass(git_repository_open(&repo2, "alternate"));
 
@@ -193,12 +202,11 @@ void test_repo_open__bad_gitlinks(void)
 
 	cl_git_sandbox_init("attr");
 
-	cl_git_pass(p_mkdir("alternate", 0777));
 	cl_git_pass(p_mkdir("invalid", 0777));
 	cl_git_pass(git_futils_mkdir_r("invalid2/.git", NULL, 0777));
 
 	for (scan = bad_links; *scan != NULL; scan++) {
-		cl_git_rewritefile("alternate/.git", *scan);
+		make_gitlink_dir("alternate", *scan);
 		cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL));
 	}
 
@@ -315,3 +323,52 @@ void test_repo_open__no_config(void)
 	git_repository_free(repo);
 	cl_fixture_cleanup("empty_standard_repo");
 }
+
+void test_repo_open__force_bare(void)
+{
+	/* need to have both repo dir and workdir set up correctly */
+	git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+	git_repository *barerepo;
+
+	make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git");
+
+	cl_assert(!git_repository_is_bare(repo));
+
+	cl_git_pass(git_repository_open(&barerepo, "alternate"));
+	cl_assert(!git_repository_is_bare(barerepo));
+	git_repository_free(barerepo);
+
+	cl_git_pass(git_repository_open_bare(
+		&barerepo, "empty_standard_repo/.git"));
+	cl_assert(git_repository_is_bare(barerepo));
+	git_repository_free(barerepo);
+
+	cl_git_fail(git_repository_open_bare(&barerepo, "alternate/.git"));
+
+	cl_git_pass(git_repository_open_ext(
+		&barerepo, "alternate/.git", GIT_REPOSITORY_OPEN_BARE, NULL));
+	cl_assert(git_repository_is_bare(barerepo));
+	git_repository_free(barerepo);
+
+	cl_git_pass(p_mkdir("empty_standard_repo/subdir", 0777));
+	cl_git_mkfile("empty_standard_repo/subdir/something.txt", "something");
+
+	cl_git_fail(git_repository_open_bare(
+		&barerepo, "empty_standard_repo/subdir"));
+
+	cl_git_pass(git_repository_open_ext(
+		&barerepo, "empty_standard_repo/subdir", GIT_REPOSITORY_OPEN_BARE, NULL));
+	cl_assert(git_repository_is_bare(barerepo));
+	git_repository_free(barerepo);
+
+	cl_git_pass(p_mkdir("alternate/subdir", 0777));
+	cl_git_pass(p_mkdir("alternate/subdir/sub2", 0777));
+	cl_git_mkfile("alternate/subdir/sub2/something.txt", "something");
+
+	cl_git_fail(git_repository_open_bare(&barerepo, "alternate/subdir/sub2"));
+
+	cl_git_pass(git_repository_open_ext(
+		&barerepo, "alternate/subdir/sub2", GIT_REPOSITORY_OPEN_BARE, NULL));
+	cl_assert(git_repository_is_bare(barerepo));
+	git_repository_free(barerepo);
+}