Commit fd0574e5ad4e2e5924ff0d0869da7d5846225d8b

Romain Geissler 2011-06-04T01:01:20

Repository: Added the git_repository_discover function that finds by itself the git directory that manage a given directory path.

diff --git a/include/git2/repository.h b/include/git2/repository.h
index 493e82a..4a7303e 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -133,6 +133,34 @@ GIT_EXTERN(int) git_repository_open3(git_repository **repository,
 		const char *git_work_tree);
 
 /**
+ * Look for a git repository and copy its path in the given buffer. The lookup start
+ * from base_path and walk across parent directories if nothing has been found. The
+ * lookup ends when the first repository is found, or when reaching a directory
+ * referenced in ceiling_dirs or when the filesystem changes (in case across_fs
+ * is true).
+ *
+ * The method will automatically detect if the repository is bare (if there is
+ * a repository).
+ *
+ * @param repository_path The user allocated buffer which will contain the found path.
+ *
+ * @param size repository_path size
+ *
+ * @param start_path The base path where the lookup starts.
+ *
+ * @param across_fs If true, then the lookup will not stop when a filesystem device change
+ * is detected while exploring parent directories.
+ *
+ * @param ceiling_dirs A colon separated of absolute symbolic link free paths. The lookup will
+ * stop when any of this paths is reached. Note that the lookup always performs on start_path
+ * no matter start_path appears in ceiling_dirs
+ * ceiling_dirs might be NULL (which is equivalent to an empty string)
+ *
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_repository_discover(char *repository_path, size_t size, const char *start_path, int across_fs, const char *ceiling_dirs);
+
+/**
  * Get the object database behind a Git repository
  *
  * @param repo a repository object
diff --git a/src/repository.c b/src/repository.c
index e564b6b..0b67d14 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -271,6 +271,21 @@ cleanup:
 	return git__rethrow(error, "Failed to open repository");
 }
 
+static int discover_repository_dirs(git_repository *repo, const char *path)
+{
+	int error;
+
+	error = guess_repository_dirs(repo, path);
+	if (error < GIT_SUCCESS)
+		return error;
+
+	error = check_repository_dirs(repo);
+	if (error < GIT_SUCCESS)
+		return error;
+
+	return GIT_SUCCESS;
+}
+
 int git_repository_open(git_repository **repo_out, const char *path)
 {
 	git_repository *repo;
@@ -282,11 +297,7 @@ int git_repository_open(git_repository **repo_out, const char *path)
 	if (repo == NULL)
 		return GIT_ENOMEM;
 
-	error = guess_repository_dirs(repo, path);
-	if (error < GIT_SUCCESS)
-		goto cleanup;
-
-	error = check_repository_dirs(repo);
+	error = discover_repository_dirs(repo, path);
 	if (error < GIT_SUCCESS)
 		goto cleanup;
 
@@ -440,6 +451,119 @@ void git_repository_free(git_repository *repo)
 	free(repo);
 }
 
+int git_repository_discover(char *repository_path, size_t size, const char *start_path, int across_fs, const char *ceiling_dirs)
+{
+	git_repository repo;
+	int error, ceiling_offset;
+	char bare_path[GIT_PATH_MAX];
+	char normal_path[GIT_PATH_MAX];
+	char *found_path;
+	dev_t current_device;
+
+	assert(start_path && repository_path);
+	memset(&repo, 0x0, sizeof(git_repository));
+
+	error = abspath(bare_path, sizeof(bare_path), start_path);
+
+	if (error < GIT_SUCCESS)
+		goto cleanup;
+
+	if (!across_fs) {
+		error = retrieve_device(&current_device, bare_path);
+
+		if (error < GIT_SUCCESS)
+			goto cleanup;
+	}
+
+	ceiling_offset = retrieve_ceiling_directories_offset(bare_path, ceiling_dirs);
+	git__joinpath(normal_path, bare_path, DOT_GIT);
+
+	while(1){
+		//look for .git file
+		if (gitfo_isfile(normal_path) == GIT_SUCCESS) {
+			error = read_gitfile(repository_path, size, normal_path, bare_path);
+
+			if (error < GIT_SUCCESS) {
+				git__rethrow(error, "Unable to read git file `%s`", normal_path);
+				goto cleanup;
+			}
+
+			error = discover_repository_dirs(&repo, repository_path);
+			if (error < GIT_SUCCESS)
+				goto cleanup;
+
+			git_repository__free_dirs(&repo);
+
+			return GIT_SUCCESS;
+		}
+
+		//look for .git repository
+		error = discover_repository_dirs(&repo, normal_path);
+		if (error < GIT_SUCCESS && error != GIT_ENOTAREPO)
+			goto cleanup;
+
+		if (error == GIT_SUCCESS) {
+			found_path = normal_path;
+			break;
+		}
+
+		git_repository__free_dirs(&repo);
+
+		//look for bare repository in current directory
+		error = discover_repository_dirs(&repo, bare_path);
+		if (error < GIT_SUCCESS && error != GIT_ENOTAREPO)
+			goto cleanup;
+
+		if (error == GIT_SUCCESS) {
+			found_path = bare_path;
+			break;
+		}
+
+		git_repository__free_dirs(&repo);
+
+		//nothing has been found, lets try the parent directory
+		if (bare_path[ceiling_offset] == '\0') {
+			error = git__throw(GIT_ENOTAREPO,"Not a git repository (or any of the parent directories): %s", start_path);
+			goto cleanup;
+		}
+
+		if (git__dirname_r(normal_path, sizeof(normal_path), bare_path) < GIT_SUCCESS)
+			goto cleanup;
+
+		if (!across_fs) {
+			dev_t new_device;
+			error = retrieve_device(&new_device, normal_path);
+
+			if (error < GIT_SUCCESS)
+				goto cleanup;
+
+			if (current_device != new_device) {
+				error = git__throw(GIT_ENOTAREPO,"Not a git repository (or any parent up to mount parent %s)\n"
+					"Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM_ENVIRONMENT not set).", bare_path);
+				goto cleanup;
+			}
+			current_device = new_device;
+		}
+
+		strcpy(bare_path, normal_path);
+		git__joinpath(normal_path, bare_path, DOT_GIT);
+	}
+
+	if (size < (strlen(found_path) + 1) * sizeof(char)) {
+		error = git__throw(GIT_EOVERFLOW, "The repository buffer is not long enough to handle the repository path `%s`", found_path);
+		goto cleanup;
+	}
+
+	strcpy(repository_path, found_path);
+	git_repository__free_dirs(&repo);
+
+	return GIT_SUCCESS;
+
+	cleanup:
+		git_repository__free_dirs(&repo);
+		return git__rethrow(error, "Failed to discover repository");
+}
+
 git_odb *git_repository_database(git_repository *repo)
 {
 	assert(repo);