"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: detect unsupported repository format extensions
To:
gameoftrees@openbsd.org
Date:
Wed, 21 Oct 2020 18:09:48 +0200

Download raw body.

Thread
On Wed, Oct 21, 2020 at 04:41:59PM +0200, Stefan Sperling wrote:
> This patch makes Got produce a useful error if an unknown extension
> has been enabled via Git's config file.
> 
> At present Got ignores any of Git's repository format extensions.
> This becomes problematic with Git repositories using SHA256.
> 
> In SHA256 repositories object IDs are SHA256 instead of SHA1 but
> Git's developers did not bump the repository format version number.
> Instead they have introduced a new extension. Older versions of Git
> refuse to work with repositories that have new extensions enabled
> and Got should do the same for compatibility.
> 
> Ok?

New diff which correctly handles config entries that explicitly
disable an extension:

	[extensions]
	foo = false

diff refs/heads/main refs/heads/ext
blob - 0f3e07a914451fb4ee60661753010efd60284036
blob + 5fa8ffa0d2de4c2d61942eb2eed66a240149f6d0
--- include/got_error.h
+++ include/got_error.h
@@ -144,6 +144,7 @@
 #define GOT_ERR_PARSE_CONFIG	127
 #define GOT_ERR_NO_CONFIG_FILE	128
 #define GOT_ERR_BAD_SYMLINK	129
+#define GOT_ERR_GIT_REPO_EXT	130
 
 static const struct got_error {
 	int code;
@@ -295,6 +296,7 @@ static const struct got_error {
 	{ GOT_ERR_NO_CONFIG_FILE, "configuration file doesn't exit" },
 	{ GOT_ERR_BAD_SYMLINK, "symbolic link points outside of paths under "
 	    "version control" },
+	{ GOT_ERR_GIT_REPO_EXT, "unsupported repository format extension" },
 };
 
 /*
blob - 6c638e9d018fe07c5b7e6949507b353ea5495e3e
blob + f4815b5fc99e9f8665fd85b9326c64f18b176d04
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -141,6 +141,7 @@ enum got_imsg_type {
 	/* Messages related to gitconfig files. */
 	GOT_IMSG_GITCONFIG_PARSE_REQUEST,
 	GOT_IMSG_GITCONFIG_REPOSITORY_FORMAT_VERSION_REQUEST,
+	GOT_IMSG_GITCONFIG_REPOSITORY_EXTENSIONS_REQUEST,
 	GOT_IMSG_GITCONFIG_AUTHOR_NAME_REQUEST,
 	GOT_IMSG_GITCONFIG_AUTHOR_EMAIL_REQUEST,
 	GOT_IMSG_GITCONFIG_REMOTES_REQUEST,
@@ -460,6 +461,8 @@ const struct got_error *got_privsep_send_gitconfig_par
     int);
 const struct got_error *
     got_privsep_send_gitconfig_repository_format_version_req(struct imsgbuf *);
+const struct got_error *got_privsep_send_gitconfig_repository_extensions_req(
+    struct imsgbuf *);
 const struct got_error *got_privsep_send_gitconfig_author_name_req(
     struct imsgbuf *);
 const struct got_error *got_privsep_send_gitconfig_author_email_req(
blob - 87f25580cb957d598ce95524a992a2d67f95d9ad
blob + d2a6b78c48d166aadf6645f719cb29e4663cd851
--- lib/got_lib_repository.h
+++ lib/got_lib_repository.h
@@ -64,6 +64,8 @@ struct got_repository {
 	int ngitconfig_remotes;
 	struct got_remote_repo *gitconfig_remotes;
 	char *gitconfig_owner;
+	char **extensions;
+	int nextensions;
 
 	/* Settings read from got.conf. */
 	struct got_gotconfig *gotconfig;
blob - d15fdd1c19625c14f12b9ef6c2f0a764fc43a0da
blob + 55eb3a41d0b6ef47b2e286aa578da8cb73c0e2f7
--- lib/privsep.c
+++ lib/privsep.c
@@ -1672,6 +1672,19 @@ got_privsep_send_gitconfig_repository_format_version_r
 }
 
 const struct got_error *
+got_privsep_send_gitconfig_repository_extensions_req(struct imsgbuf *ibuf)
+{
+	if (imsg_compose(ibuf,
+	    GOT_IMSG_GITCONFIG_REPOSITORY_EXTENSIONS_REQUEST, 0, 0, -1,
+	    NULL, 0) == -1)
+		return got_error_from_errno("imsg_compose "
+		    "GITCONFIG_REPOSITORY_EXTENSIONS_REQUEST");
+
+	return flush_imsg(ibuf);
+}
+
+
+const struct got_error *
 got_privsep_send_gitconfig_author_name_req(struct imsgbuf *ibuf)
 {
 	if (imsg_compose(ibuf,
blob - 036305e904556fd6910f4e907610f8973a3b2e8a
blob + 3c8f844ad272da6c878fec6aac3e13d424e13af1
--- lib/repository.c
+++ lib/repository.c
@@ -377,7 +377,7 @@ static const struct got_error *
 parse_gitconfig_file(int *gitconfig_repository_format_version,
     char **gitconfig_author_name, char **gitconfig_author_email,
     struct got_remote_repo **remotes, int *nremotes,
-    char **gitconfig_owner,
+    char **gitconfig_owner, char ***extensions, int *nextensions,
     const char *gitconfig_path)
 {
 	const struct got_error *err = NULL, *child_err = NULL;
@@ -387,6 +387,10 @@ parse_gitconfig_file(int *gitconfig_repository_format_
 	struct imsgbuf *ibuf;
 
 	*gitconfig_repository_format_version = 0;
+	if (extensions)
+		*extensions = NULL;
+	if (nextensions)
+		*nextensions = 0;
 	*gitconfig_author_name = NULL;
 	*gitconfig_author_email = NULL;
 	if (remotes)
@@ -445,6 +449,32 @@ parse_gitconfig_file(int *gitconfig_repository_format_
 	if (err)
 		goto done;
 
+	if (extensions && nextensions) {
+		err = got_privsep_send_gitconfig_repository_extensions_req(
+		    ibuf);
+		if (err)
+			goto done;
+		err = got_privsep_recv_gitconfig_int(nextensions, ibuf);
+		if (err)
+			goto done;
+		if (*nextensions > 0) {
+			int i;
+			*extensions = calloc(*nextensions, sizeof(char *));
+			if (*extensions == NULL) {
+				err = got_error_from_errno("calloc");
+				goto done;
+			}
+			for (i = 0; i < *nextensions; i++) {
+				char *ext;
+				err = got_privsep_recv_gitconfig_str(&ext,
+				    ibuf);
+				if (err)
+					goto done;
+				(*extensions)[i] = ext;
+			}
+		}
+	}
+
 	err = got_privsep_send_gitconfig_author_name_req(ibuf);
 	if (err)
 		goto done;
@@ -509,7 +539,7 @@ read_gitconfig(struct got_repository *repo, const char
 		err = parse_gitconfig_file(&dummy_repo_version,
 		    &repo->global_gitconfig_author_name,
 		    &repo->global_gitconfig_author_email,
-		    NULL, NULL, NULL, global_gitconfig_path);
+		    NULL, NULL, NULL, NULL, NULL, global_gitconfig_path);
 		if (err)
 			return err;
 	}
@@ -522,7 +552,8 @@ read_gitconfig(struct got_repository *repo, const char
 	err = parse_gitconfig_file(&repo->gitconfig_repository_format_version,
 	    &repo->gitconfig_author_name, &repo->gitconfig_author_email,
 	    &repo->gitconfig_remotes, &repo->ngitconfig_remotes,
-	    &repo->gitconfig_owner, repo_gitconfig_path);
+	    &repo->gitconfig_owner, &repo->extensions, &repo->nextensions,
+	    repo_gitconfig_path);
 	if (err)
 		goto done;
 done:
@@ -545,6 +576,13 @@ read_gotconfig(struct got_repository *repo)
 	return err;
 }
 
+/* Supported repository format extensions. */
+static const char *repo_extensions[] = {
+	"noop",			/* Got supports repository format version 1. */
+	"preciousObjects",	/* Got has no garbage collection yet. */
+	"worktreeConfig",	/* Got does not care about Git work trees. */
+};
+
 const struct got_error *
 got_repo_open(struct got_repository **repop, const char *path,
     const char *global_gitconfig_path)
@@ -626,6 +664,20 @@ got_repo_open(struct got_repository **repop, const cha
 		goto done;
 	if (repo->gitconfig_repository_format_version != 0)
 		err = got_error_path(path, GOT_ERR_GIT_REPO_FORMAT);
+	for (i = 0; i < repo->nextensions; i++) {
+		char *ext = repo->extensions[i];
+		int j, supported = 0;
+		for (j = 0; j < nitems(repo_extensions); j++) {
+			if (strcmp(ext, repo_extensions[j]) == 0) {
+				supported = 1;
+				break;
+			}
+		}
+		if (!supported) {
+			err = got_error_path(ext, GOT_ERR_GIT_REPO_EXT);
+			goto done;
+		}
+	}
 done:
 	if (err)
 		got_repo_close(repo);
@@ -684,6 +736,9 @@ got_repo_close(struct got_repository *repo)
 	for (i = 0; i < repo->ngitconfig_remotes; i++)
 		got_repo_free_remote_repo_data(&repo->gitconfig_remotes[i]);
 	free(repo->gitconfig_remotes);
+	for (i = 0; i < repo->nextensions; i++)
+		free(repo->extensions[i]);
+	free(repo->extensions);
 	free(repo);
 
 	return err;
blob - e24545d20956871d467322bef47e09dbcb3c46ed
blob + 0356c31dbad98d697e9574c747c5fa52f4601208
--- libexec/got-read-gitconfig/got-read-gitconfig.c
+++ libexec/got-read-gitconfig/got-read-gitconfig.c
@@ -159,6 +159,14 @@ send_gitconfig_remotes(struct imsgbuf *ibuf, struct go
 	return NULL;
 }
 
+static int
+get_boolean_val(char *val)
+{
+    return (strcasecmp(val, "true") == 0 ||
+        strcasecmp(val, "on") == 0 ||
+        strcasecmp(val, "yes") == 0 ||
+	strcmp(val, "1") == 0);
+}
 
 static const struct got_error *
 gitconfig_remotes_request(struct imsgbuf *ibuf, struct got_gitconfig *gitconfig)
@@ -220,11 +228,7 @@ gitconfig_remotes_request(struct imsgbuf *ibuf, struct
 		remotes[i].mirror_references = 0;
 		mirror = got_gitconfig_get_str(gitconfig, node->field,
 		    "mirror");
-		if (mirror != NULL &&
-		    (strcasecmp(mirror, "true") == 0 ||
-		    strcasecmp(mirror, "on") == 0 ||
-		    strcasecmp(mirror, "yes") == 0 ||
-		    strcmp(mirror, "1") == 0))
+		if (mirror != NULL && get_boolean_val(mirror))
 			remotes[i].mirror_references = 1;
 
 		i++;
@@ -254,6 +258,48 @@ gitconfig_owner_request(struct imsgbuf *ibuf, struct g
 	return send_gitconfig_str(ibuf, value);
 }
 
+static const struct got_error *
+gitconfig_extensions_request(struct imsgbuf *ibuf,
+    struct got_gitconfig *gitconfig)
+{
+	const struct got_error *err = NULL;
+	struct got_gitconfig_list *tags;
+	struct got_gitconfig_list_node *node;
+	int nextensions = 0;
+	char *val;
+
+	if (gitconfig == NULL)
+		return got_error(GOT_ERR_PRIVSEP_MSG);
+
+	tags = got_gitconfig_get_tag_list(gitconfig, "extensions");
+	if (tags == NULL)
+		return send_gitconfig_int(ibuf, 0);
+
+	TAILQ_FOREACH(node, &tags->fields, link) {
+		val = got_gitconfig_get_str(gitconfig, "extensions",
+		    node->field);
+		if (get_boolean_val(val))
+			nextensions++;
+	}
+
+	err = send_gitconfig_int(ibuf, nextensions);
+	if (err)
+		goto done;
+
+	TAILQ_FOREACH(node, &tags->fields, link) {
+		val = got_gitconfig_get_str(gitconfig, "extensions",
+		    node->field);
+		if (get_boolean_val(val)) {
+			err = send_gitconfig_str(ibuf, node->field);
+			if (err)
+				goto done;
+		}
+	}
+done:
+	got_gitconfig_free_list(tags);
+	return err;
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -320,6 +366,9 @@ main(int argc, char *argv[])
 		case GOT_IMSG_GITCONFIG_REPOSITORY_FORMAT_VERSION_REQUEST:
 			err = gitconfig_num_request(&ibuf, gitconfig, "core",
 			    "repositoryformatversion", 0);
+			break;
+		case GOT_IMSG_GITCONFIG_REPOSITORY_EXTENSIONS_REQUEST:
+			err = gitconfig_extensions_request(&ibuf, gitconfig);
 			break;
 		case GOT_IMSG_GITCONFIG_AUTHOR_NAME_REQUEST:
 			err = gitconfig_str_request(&ibuf, gitconfig, "user",
blob - 5e018f600067c84e853f29b8bdc2f18470f40437
blob + 0db983c06c5b5b41c77198725d7029c5e5ba451a
--- regress/cmdline/checkout.sh
+++ regress/cmdline/checkout.sh
@@ -756,6 +756,34 @@ test_checkout_symlink_relative_wtpath() {
 	test_done "$testroot" "$ret"
 }
 
+test_checkout_repo_with_unknown_extension() {
+	local testroot=`test_init checkout_repo_with_unknown_extension`
+
+	(cd $testroot/repo &&
+	    git config --add extensions.badExtension true)
+	(cd $testroot/repo &&
+	    git config --add extensions.otherBadExtension 0)
+
+	echo "got: badExtension: unsupported repository format extension" \
+		> $testroot/stderr.expected
+	got checkout $testroot/repo $testroot/wt \
+		> $testroot/stdout 2> $testroot/stderr
+
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "got checkout command succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+	fi
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_checkout_basic
 run_test test_checkout_dir_exists
@@ -768,3 +796,4 @@ run_test test_checkout_read_only
 run_test test_checkout_into_nonempty_dir
 run_test test_checkout_symlink
 run_test test_checkout_symlink_relative_wtpath
+run_test test_checkout_repo_with_unknown_extension