git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[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 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
Next: Christian Couder
Message 1 of 11 in “fast-import: start controlling how commit signatures are handled”
  1. 0/2 fast-import: start controlling how commit signatures are handledChristian Couder, Sep 12, 2025
  2. 1/2 gpg-interface: refactor 'enum sign_mode' parsingChristian Couder, Sep 12, 2025
  3. 2/2 fast-import: add '--signed-commits=<mode>' optionChristian Couder, Sep 12, 2025
  4. Christian CouderSep 12, 2025
  5. Patrick SteinhardtSep 15, 2025
  6. Christian CouderSep 15, 2025
  7. Patrick SteinhardtSep 15, 2025
  8. Christian CouderSep 17, 2025
  9. 0/2 fast-import: start controlling how commit signatures are handledChristian Couder, Sep 17, 2025
  10. 1/2 gpg-interface: refactor 'enum sign_mode' parsingChristian Couder, Sep 17, 2025
  11. 2/2 fast-import: add '--signed-commits=<mode>' optionChristian Couder, Sep 17, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.