From: Stefan Sperling Subject: detect unsupported repository format extensions To: gameoftrees@openbsd.org Date: Wed, 21 Oct 2020 16:41:59 +0200 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? 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 + 1e06d9b62bc80c9420c7029a717610ce2b0d6ce1 --- libexec/got-read-gitconfig/got-read-gitconfig.c +++ libexec/got-read-gitconfig/got-read-gitconfig.c @@ -254,6 +254,35 @@ 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; + + 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); + + err = send_gitconfig_int(ibuf, tags->cnt); + if (err) + goto done; + + TAILQ_FOREACH(node, &tags->fields, link) { + 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 +349,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 + c4aeff23a0ae05ef23694f84698b35650f570e3a --- regress/cmdline/checkout.sh +++ regress/cmdline/checkout.sh @@ -756,6 +756,32 @@ 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) + + 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 +794,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