Tools like `git-filter-repo` should be able to control how commit signatures are handled when regenerating repository content after it has been filtered (see https://github.com/newren/git-filter-repo/issues/139). For this purpose, they need a way for `git fast-import` to control how commit signatures are handled. This small patch series starts to implement such a way by adding a new `--signed-commits=<mode>` option to `git fast-import`. For now this new option behaves in a very similar way as the option with the same name that already exists in `git fast-export`. Especially it supports exactly the same <mode>s and the same aliases for these modes. For example "ignore" is a synonym for "verbatim". In the future I want to implement new <mode>s like "strip-if-invalid", "re-sign", "re-sign-if-invalid" that might be a bit more complex for this option. But for now I prefer to start with the simple modes to validate the general design of the new option. In the future I also plan to add a similar `--signed-tags=<mode>` so that the import of tags can also be controlled. But I prefer to validate the general design of a single new option first. In particular, I am interested in feedback about the following: - Should we keep "ignore" as a synonym for "verbatim" and "warn" as a synonym for "warn-verbatim"? My opinion is that they might be confusing, so we might want to remove them for `git fast-import` even if we keep them for `git fast-export`. The parsing code might be a bit more complex if we do that though, so for now I have kept the synonyms. - Are we still fine with most <mode>s having a "warn-*" variant (like the "warn-strip" variant of "strip" for example)? Or should we have a separate `--verbose` or maybe `--signed-commits-verbose` option dedicated to switching warnings on/off? I think it's good to decide about this before the number of <mode>s increases a lot with new <mode>s like "strip-if-invalid", "re-sign", "re-sign-if-invalid" and possibly others. Changes since v1: ================= Thanks to Junio and Patrick for reviewing V1. In patch 1/2: - the commit message now says that a single option will be added in the following commit - instead of returning success in the `!parse_sign_mode(arg, val)` case, we return error in the `parse_sign_mode(arg, val)` case, - a '.' has been added at the end of a code comment. In patch 2/2: - the import_signature() function has been removed and we just call parse_one_signature() in all cases except `SIGN_ABORT`, before handling the signature according to `signed_commit_mode`, - we now mark for translation all new strings that could be displayed to users, - usagef() is used instead of die() when the mode is unknown, - the warning for `warn-verbatim` has been improved, - the tests have been improved by using more idiomatic and simplified commands CI tests: ========= They have all passed. See: https://github.com/chriscool/git/actions/runs/17672015528 range-diff vs V1: ================= 1: 7c8216a701 ! 1: 87149ae92d gpg-interface: refactor 'enum sign_mode' parsing @@ Commit message only command with '--signed-tags=<mode>' or '--signed-commits=<mode>' options. - In a following commit, we are going to add such options to `git + In a following commit, we are going to add a similar option to `git fast-import`, which will be simpler, easier and cleaner if we can reuse the 'enum sign_mode' defintion and parsing code. @@ builtin/fast-export.c: static struct hashmap anonymized_seeds; - else if (!strcmp(arg, "strip")) - *val = SIGN_STRIP; - else -- return error("Unknown %s mode: %s", opt->long_name, arg); -- return 0; + -+ if (!parse_sign_mode(arg, val)) -+ return 0; ++ if (parse_sign_mode(arg, val)) + return error("Unknown %s mode: %s", opt->long_name, arg); + -+ return error("Unknown %s mode: %s", opt->long_name, arg); + return 0; } - static int parse_opt_tag_of_filtered_mode(const struct option *opt, ## gpg-interface.c ## @@ gpg-interface.c: static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, @@ gpg-interface.h: int check_signature(struct signature_check *sigc, void print_signature_buffer(const struct signature_check *sigc, unsigned flags); -+/* Modes for --signed-tags=<mode> and --signed-commits=<mode> options */ ++/* Modes for --signed-tags=<mode> and --signed-commits=<mode> options. */ +enum sign_mode { + SIGN_ABORT, + SIGN_WARN_VERBATIM, 2: 0294f05ae6 ! 2: e34f015aea fast-import: add '--signed-commits=<mode>' option @@ builtin/fast-import.c: static int global_argc; /* Memory pools */ static struct mem_pool fi_mem_pool = { .block_alloc = 2*1024*1024 - sizeof(struct mp_block), -@@ builtin/fast-import.c: static void store_signature(struct signature_data *stored_sig, - } - } - -+/* Process signatures (up to 2: one "sha1" and one "sha256") */ -+static void import_signature(struct signature_data *sig_sha1, -+ struct signature_data *sig_sha256, -+ const char *v) -+{ -+ struct signature_data sig = { NULL, NULL, STRBUF_INIT }; -+ -+ parse_one_signature(&sig, v); -+ -+ if (!strcmp(sig.hash_algo, "sha1")) -+ store_signature(sig_sha1, &sig, "SHA-1"); -+ else if (!strcmp(sig.hash_algo, "sha256")) -+ store_signature(sig_sha256, &sig, "SHA-256"); -+ else -+ BUG("parse_one_signature() returned unknown hash algo"); -+} -+ - static void parse_new_commit(const char *arg) - { - static struct strbuf msg = STRBUF_INIT; @@ builtin/fast-import.c: static void parse_new_commit(const char *arg) if (!committer) die("Expected committer but didn't get one"); - /* Process signatures (up to 2: one "sha1" and one "sha256") */ while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { -- struct signature_data sig = { NULL, NULL, STRBUF_INIT }; -- + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; + - parse_one_signature(&sig, v); -- ++ if (signed_commit_mode == SIGN_ABORT) ++ die(_("encountered signed commit; use " ++ "--signed-commits=<mode> to handle it")); + - if (!strcmp(sig.hash_algo, "sha1")) - store_signature(&sig_sha1, &sig, "SHA-1"); - else if (!strcmp(sig.hash_algo, "sha256")) - store_signature(&sig_sha256, &sig, "SHA-256"); - else - BUG("parse_one_signature() returned unknown hash algo"); -- -+ struct strbuf data = STRBUF_INIT; ++ parse_one_signature(&sig, v); + + switch (signed_commit_mode) { + case SIGN_ABORT: -+ die("encountered signed commit; use " -+ "--signed-commits=<mode> to handle it"); ++ BUG("SIGN_ABORT should be handled before calling parse_one_signature()"); ++ break; + case SIGN_WARN_VERBATIM: -+ warning("importing a commit signature"); ++ warning(_("importing a commit signature verbatim")); + /* fallthru */ + case SIGN_VERBATIM: -+ import_signature(&sig_sha1, &sig_sha256, v); ++ if (!strcmp(sig.hash_algo, "sha1")) ++ store_signature(&sig_sha1, &sig, "SHA-1"); ++ else if (!strcmp(sig.hash_algo, "sha256")) ++ store_signature(&sig_sha256, &sig, "SHA-256"); ++ else ++ die(_("parse_one_signature() returned unknown hash algo")); + break; + case SIGN_WARN_STRIP: -+ warning("stripping a commit signature"); ++ warning(_("stripping a commit signature")); + /* fallthru */ + case SIGN_STRIP: -+ /* Read signature data and discard it */ -+ read_next_command(); -+ parse_data(&data, 0, NULL); -+ strbuf_release(&data); ++ /* Just discard signature data */ ++ strbuf_release(&sig.data); ++ free(sig.hash_algo); + break; + } read_next_command(); @@ builtin/fast-import.c: static int parse_one_option(const char *option) option_export_pack_edges(option); + } else if (skip_prefix(option, "signed-commits=", &option)) { + if (parse_sign_mode(option, &signed_commit_mode)) -+ die("unknown --signed-commits mode '%s'", option); ++ usagef(_("unknown --signed-commits mode '%s'"), option); } else if (!strcmp(option, "quiet")) { show_stats = 0; quiet = 1; @@ t/t9305-fast-import-signatures.sh (new) +test_description='git fast-import --signed-commits=<mode>' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main -+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success 'set up unsigned initial commit and import repo' ' + test_commit first && -+ mkdir new && -+ git --git-dir=new/.git init ++ git init new +' + +test_expect_success GPG 'set up OpenPGP signed commit' ' @@ t/t9305-fast-import-signatures.sh (new) + SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && + + # Check that the resulting SHA-1 commit has both signatures -+ echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out && ++ git -C explicit-sha256 cat-file -p $SHA1_B >out && + test_grep -E "^gpgsig " out && + test_grep -E "^gpgsig-sha256 " out +' Christian Couder (2): gpg-interface: refactor 'enum sign_mode' parsing fast-import: add '--signed-commits=<mode>' option Documentation/git-fast-import.adoc | 5 ++ builtin/fast-export.c | 19 ++---- builtin/fast-import.c | 41 ++++++++--- gpg-interface.c | 17 +++++ gpg-interface.h | 15 ++++ t/meson.build | 1 + t/t9305-fast-import-signatures.sh | 106 +++++++++++++++++++++++++++++ 7 files changed, 182 insertions(+), 22 deletions(-) create mode 100755 t/t9305-fast-import-signatures.sh -- 2.51.0.195.gf8f8f06677