Commit 06f0230073e0159933aa19d97fce0cbe3b9a3231

brian m. carlson 2020-02-07T00:33:52

repository: handle format v1 Git has supported repository format version 1 for some time. This format is just like version 0, but it supports extensions. Implementations must reject extensions that they don't support. Add support for this format version and reject any extensions but extensions.noop, which is the only extension we currently support. While we're at it, also clean up an error message.

diff --git a/src/repository.c b/src/repository.c
index 2469e13..721ce5e 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -62,7 +62,8 @@ static const struct {
 	{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true }
 };
 
-static int check_repositoryformatversion(git_config *config);
+static int check_repositoryformatversion(int *version, git_config *config);
+static int check_extensions(git_config *config, int version);
 
 #define GIT_COMMONDIR_FILE "commondir"
 #define GIT_GITDIR_FILE "gitdir"
@@ -72,6 +73,7 @@ static int check_repositoryformatversion(git_config *config);
 #define GIT_BRANCH_MASTER "master"
 
 #define GIT_REPO_VERSION 0
+#define GIT_REPO_MAX_VERSION 1
 
 git_buf git_repository__reserved_names_win32[] = {
 	{ DOT_GIT, 0, CONST_STRLEN(DOT_GIT) },
@@ -804,6 +806,7 @@ int git_repository_open_ext(
 		gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT;
 	git_repository *repo = NULL;
 	git_config *config = NULL;
+	int version = 0;
 
 	if (flags & GIT_REPOSITORY_OPEN_FROM_ENV)
 		return _git_repository_open_ext_from_env(repo_ptr, start_path);
@@ -845,7 +848,10 @@ int git_repository_open_ext(
 	if (error < 0 && error != GIT_ENOTFOUND)
 		goto cleanup;
 
-	if (config && (error = check_repositoryformatversion(config)) < 0)
+	if (config && (error = check_repositoryformatversion(&version, config)) < 0)
+		goto cleanup;
+
+	if ((error = check_extensions(config, version) < 0))
 		goto cleanup;
 
 	if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
@@ -1341,11 +1347,11 @@ bool git_repository__reserved_names(
 }
 #endif
 
-static int check_repositoryformatversion(git_config *config)
+static int check_repositoryformatversion(int *version, git_config *config)
 {
-	int version, error;
+	int error;
 
-	error = git_config_get_int32(&version, config, "core.repositoryformatversion");
+	error = git_config_get_int32(version, config, "core.repositoryformatversion");
 	/* git ignores this if the config variable isn't there */
 	if (error == GIT_ENOTFOUND)
 		return 0;
@@ -1353,16 +1359,35 @@ static int check_repositoryformatversion(git_config *config)
 	if (error < 0)
 		return -1;
 
-	if (GIT_REPO_VERSION < version) {
+	if (GIT_REPO_MAX_VERSION < *version) {
 		git_error_set(GIT_ERROR_REPOSITORY,
-			"unsupported repository version %d. Only versions up to %d are supported.",
-			version, GIT_REPO_VERSION);
+			"unsupported repository version %d; only versions up to %d are supported",
+			*version, GIT_REPO_MAX_VERSION);
 		return -1;
 	}
 
 	return 0;
 }
 
+static int check_valid_extension(const git_config_entry *entry, void *payload)
+{
+	GIT_UNUSED(payload);
+
+	if (!strcmp(entry->name, "extensions.noop"))
+		return 0;
+
+	git_error_set(GIT_ERROR_REPOSITORY, "unsupported extension name %s", entry->name);
+	return -1;
+}
+
+static int check_extensions(git_config *config, int version)
+{
+	if (version < 1)
+		return 0;
+
+	return git_config_foreach_match(config, "^extensions\\.", check_valid_extension, NULL);
+}
+
 int git_repository_create_head(const char *git_dir, const char *ref_name)
 {
 	git_buf ref_path = GIT_BUF_INIT;
@@ -1574,11 +1599,15 @@ static int repo_init_config(
 	git_config *config = NULL;
 	bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0);
 	bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0);
+	int version = 0;
 
 	if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0)
 		goto cleanup;
 
-	if (is_reinit && (error = check_repositoryformatversion(config)) < 0)
+	if (is_reinit && (error = check_repositoryformatversion(&version, config)) < 0)
+		goto cleanup;
+
+	if ((error = check_extensions(config, version) < 0))
 		goto cleanup;
 
 #define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \
diff --git a/tests/repo/open.c b/tests/repo/open.c
index 448ccdc..0f4ecf5 100644
--- a/tests/repo/open.c
+++ b/tests/repo/open.c
@@ -35,7 +35,53 @@ void test_repo_open__format_version_1(void)
 
 	git_config_free(config);
 	git_repository_free(repo);
+
+	git_repository_open(&repo, "empty_bare.git");
+	cl_assert(git_repository_path(repo) != NULL);
+	cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0);
+	git_repository_free(repo);
+}
+
+void test_repo_open__format_version_1_with_valid_extension(void)
+{
+	git_repository *repo;
+	git_config *config;
+
+	repo = cl_git_sandbox_init("empty_bare.git");
+
+	cl_git_pass(git_repository_open(&repo, "empty_bare.git"));
+	cl_git_pass(git_repository_config(&config, repo));
+
+	cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1));
+	cl_git_pass(git_config_set_int32(config, "extensions.noop", 1));
+
+	git_config_free(config);
+	git_repository_free(repo);
+
+	git_repository_open(&repo, "empty_bare.git");
+	cl_assert(git_repository_path(repo) != NULL);
+	cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0);
+	git_repository_free(repo);
+}
+
+void test_repo_open__format_version_1_with_invalid_extension(void)
+{
+	git_repository *repo;
+	git_config *config;
+
+	repo = cl_git_sandbox_init("empty_bare.git");
+
+	cl_git_pass(git_repository_open(&repo, "empty_bare.git"));
+	cl_git_pass(git_repository_config(&config, repo));
+
+	cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1));
+	cl_git_pass(git_config_set_int32(config, "extensions.invalid", 1));
+
+	git_config_free(config);
+	git_repository_free(repo);
+
 	cl_git_fail(git_repository_open(&repo, "empty_bare.git"));
+	git_repository_free(repo);
 }
 
 void test_repo_open__standard_empty_repo_through_gitdir(void)