Commit 501c51b2446b229655638ea6ad003fdc88b77760

Etienne Samson 2019-06-26T14:49:50

repo: commondir resolution can sometimes fallback to the repodir For example, https://git-scm.com/docs/gitrepository-layout says: info Additional information about the repository is recorded in this directory. This directory is ignored if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/info" will be used instead. So when looking for `info/attributes`, we need to check the commondir first, or fallback to "our" `info/attributes`.

diff --git a/include/git2/repository.h b/include/git2/repository.h
index 364e0ea..45d7962 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -438,7 +438,8 @@ typedef enum {
 	GIT_REPOSITORY_ITEM_HOOKS,
 	GIT_REPOSITORY_ITEM_LOGS,
 	GIT_REPOSITORY_ITEM_MODULES,
-	GIT_REPOSITORY_ITEM_WORKTREES
+	GIT_REPOSITORY_ITEM_WORKTREES,
+	GIT_REPOSITORY_ITEM__LAST
 } git_repository_item_t;
 
 /**
diff --git a/include/git2/sys/repository.h b/include/git2/sys/repository.h
index f1d1c3d..ea2773f 100644
--- a/include/git2/sys/repository.h
+++ b/include/git2/sys/repository.h
@@ -25,6 +25,10 @@ GIT_BEGIN_DECL
  * Note that this is only useful if you wish to associate the repository
  * with a non-filesystem-backed object database and config store.
  *
+ * Caveats: since this repository has no physical location, some systems
+ * can fail to function properly: locations under $GIT_DIR, $GIT_COMMON_DIR,
+ * or $GIT_INFO_DIR are impacted.
+ *
  * @param out The blank repository
  * @return 0 on success, or an error code
  */
diff --git a/src/repository.c b/src/repository.c
index 6d7954a..71386d6 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -42,23 +42,24 @@ bool git_repository__fsync_gitdir = false;
 
 static const struct {
     git_repository_item_t parent;
+	git_repository_item_t fallback;
     const char *name;
     bool directory;
 } items[] = {
-	{ GIT_REPOSITORY_ITEM_GITDIR, NULL, true },
-	{ GIT_REPOSITORY_ITEM_WORKDIR, NULL, true },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, NULL, true },
-	{ GIT_REPOSITORY_ITEM_GITDIR, "index", false },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "objects", true },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "refs", true },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "packed-refs", false },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "remotes", true },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "config", false },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "info", true },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "hooks", true },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "logs", true },
-	{ GIT_REPOSITORY_ITEM_GITDIR, "modules", true },
-	{ GIT_REPOSITORY_ITEM_COMMONDIR, "worktrees", true }
+	{ GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true },
+	{ GIT_REPOSITORY_ITEM_WORKDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true },
+	{ GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "index", false },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "objects", true },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "refs", true },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "packed-refs", false },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "remotes", true },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "config", false },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "info", true },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "hooks", true },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "logs", true },
+	{ GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "modules", true },
+	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true }
 };
 
 static int check_repositoryformatversion(git_config *config);
@@ -2308,11 +2309,11 @@ int git_repository_is_empty(git_repository *repo)
 	return is_empty;
 }
 
-int git_repository_item_path(git_buf *out, const git_repository *repo, git_repository_item_t item)
+static const char *resolved_parent_path(const git_repository *repo, git_repository_item_t item, git_repository_item_t fallback)
 {
 	const char *parent;
 
-	switch (items[item].parent) {
+	switch (item) {
 		case GIT_REPOSITORY_ITEM_GITDIR:
 			parent = git_repository_path(repo);
 			break;
@@ -2324,9 +2325,17 @@ int git_repository_item_path(git_buf *out, const git_repository *repo, git_repos
 			break;
 		default:
 			git_error_set(GIT_ERROR_INVALID, "invalid item directory");
-			return -1;
+			return NULL;
 	}
+	if (!parent && fallback != GIT_REPOSITORY_ITEM__LAST)
+		return resolved_parent_path(repo, fallback, GIT_REPOSITORY_ITEM__LAST);
+
+	return parent;
+}
 
+int git_repository_item_path(git_buf *out, const git_repository *repo, git_repository_item_t item)
+{
+	const char *parent = resolved_parent_path(repo, items[item].parent, items[item].fallback);
 	if (parent == NULL) {
 		git_error_set(GIT_ERROR_INVALID, "path cannot exist in repository");
 		return GIT_ENOTFOUND;