[PATCH v2 0/2] fast-import: start controlling how commit signatures are handled
- From
Christian Couder <christian.couder@gmail.com>
- Date
- Sep 12, 2025, 12:40 UTC
- Message-ID
- <20250912124042.2523683-1-christian.couder@gmail.com>
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 commandsCI 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