{"thread":{"id":"65347","subject":"[PATCH 0/4] fast-import: extend signed object handling modes","startedAt":"2026-03-24T21:55:31Z","lastAt":"2026-03-25T18:03:05Z","messageCount":7,"participants":["Justin Tobler","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"539880","messageId":"20260324215513.764739-1-jltobler@gmail.com","threadId":"65347","inReplyTo":null,"subject":"[PATCH 0/4] fast-import: extend signed object handling modes","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-03-24T21:55:09Z","receivedAt":"2026-03-24T21:55:31Z","isPatch":true,"body":"Greetings,\n\nThe '--signed-{commits,tags}=<mode>' options for git-fast-import(1)\nallow users to configure how signed objects should be handled at time of\nimport. With c20f112e51 (fast-import: add 'strip-if-invalid' mode to\n--signed-commits=<mode>, 2025-11-17) and ee66c793f8 (fast-import: add\nmode to sign commits with invalid signatures, 2026-03-12), the\n'strip-if-invalid' and 'sign-if-invalid' modes were added for the\n'--signed-commits' option only.\n\nThis series extends '--signed-commits' by adding an 'abort-if-invalid'\nmode which aborts the entire import operation when a commit signature\nfails verification. Additionally, the '--signed-tags' option is brought\ninto parity with '--signed-commits' by supporting equivalent,\n'strip-if-invalid', 'sign-if-invalid', and 'abort-if-invalid' modes.\n\nThis series is built on top of 1080981ddb (The 19th batch, 2026-03-23)\nwith ee66c793f8 (fast-import: add mode to sign commits with invalid\nsignatures, 2026-03-12) merged into it.\n\nThanks,\n-Justin\n\nJustin Tobler (4):\n  fast-import: add 'abort-if-invalid' mode to '--signed-commits=<mode>'\n  fast-import: add 'strip-if-invalid' mode to '--signed-tags=<mode>'\n  fast-import: add 'sign-if-invalid' mode to '--signed-tags=<mode>'\n  fast-import: add 'abort-if-invalid' mode to '--signed-tags=<mode>'\n\n Documentation/git-fast-import.adoc |   9 ++-\n builtin/fast-export.c              |   6 ++\n builtin/fast-import.c              |  71 ++++++++++++++---\n gpg-interface.c                    |   2 +\n gpg-interface.h                    |   1 +\n t/t9305-fast-import-signatures.sh  |  10 ++-\n t/t9306-fast-import-signed-tags.sh | 118 +++++++++++++++++++++++++++++\n 7 files changed, 201 insertions(+), 16 deletions(-)\n\n-- \n2.53.0.381.g628a66ccf6\n\n"},{"id":"539881","messageId":"20260324215513.764739-2-jltobler@gmail.com","threadId":"65347","inReplyTo":"20260324215513.764739-1-jltobler@gmail.com","subject":"[PATCH 1/4] fast-import: add 'abort-if-invalid' mode to '--signed-commits=<mode>'","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-03-24T21:55:10Z","receivedAt":"2026-03-24T21:55:32Z","isPatch":true,"body":"The '--signed-commits=<mode>' option for git-fast-import(1) configures\nhow signed commits are handled when encountered. In cases where an\ninvalid commit signature is encountered, a user may wish to abort the\noperation entirely. Introduce an 'abort-if-invalid' mode to do so.\n\nSigned-off-by: Justin Tobler <jltobler@gmail.com>\n---\n Documentation/git-fast-import.adoc |  2 ++\n builtin/fast-export.c              |  6 ++++++\n builtin/fast-import.c              | 10 +++++++++-\n gpg-interface.c                    |  2 ++\n gpg-interface.h                    |  1 +\n t/t9305-fast-import-signatures.sh  | 10 +++++++++-\n 6 files changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex b3f42d4637..288f2b2a7e 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -90,6 +90,8 @@ already trusted to run their own code.\n   commit signatures and replaces invalid signatures with newly created ones.\n   Valid signatures are left unchanged. If `<keyid>` is provided, that key is\n   used for signing; otherwise the configured default signing key is used.\n+* `abort-if-invalid` will make this program die when encountering a signed\n+  commit that is unable to be verified.\n \n Options for Frontends\n ~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 13621b0d6a..dcbc5bc82d 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -822,6 +822,9 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t\t\tdie(_(\"encountered signed commit %s; use \"\n \t\t\t      \"--signed-commits=<mode> to handle it\"),\n \t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tcase SIGN_ABORT_IF_INVALID:\n+\t\t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n+\t\t\t      \"git fast-export with --signed-commits=<mode>\"));\n \t\tcase SIGN_STRIP_IF_INVALID:\n \t\t\tdie(_(\"'strip-if-invalid' is not a valid mode for \"\n \t\t\t      \"git fast-export with --signed-commits=<mode>\"));\n@@ -970,6 +973,9 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\tdie(_(\"encountered signed tag %s; use \"\n \t\t\t\t      \"--signed-tags=<mode> to handle it\"),\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n+\t\t\tcase SIGN_ABORT_IF_INVALID:\n+\t\t\t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n+\t\t\t\t      \"git fast-export with --signed-tags=<mode>\"));\n \t\t\tcase SIGN_STRIP_IF_INVALID:\n \t\t\t\tdie(_(\"'strip-if-invalid' is not a valid mode for \"\n \t\t\t\t      \"git fast-export with --signed-tags=<mode>\"));\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 9fc6c35b74..08ea27242d 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -2892,6 +2892,9 @@ static void handle_signature_if_invalid(struct strbuf *new_data,\n \tret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check);\n \n \tif (ret) {\n+\t\tif (mode == SIGN_ABORT_IF_INVALID)\n+\t\t\tdie(_(\"aborting due to invalid signature\"));\n+\n \t\twarn_invalid_signature(&signature_check, msg->buf, mode);\n \n \t\tif (mode == SIGN_SIGN_IF_INVALID) {\n@@ -2983,6 +2986,7 @@ static void parse_new_commit(const char *arg)\n \t\tcase SIGN_VERBATIM:\n \t\tcase SIGN_STRIP_IF_INVALID:\n \t\tcase SIGN_SIGN_IF_INVALID:\n+\t\tcase SIGN_ABORT_IF_INVALID:\n \t\t\timport_one_signature(&sig_sha1, &sig_sha256, v);\n \t\t\tbreak;\n \n@@ -3068,7 +3072,8 @@ static void parse_new_commit(const char *arg)\n \t\t\tencoding);\n \n \tif ((signed_commit_mode == SIGN_STRIP_IF_INVALID ||\n-\t     signed_commit_mode == SIGN_SIGN_IF_INVALID) &&\n+\t     signed_commit_mode == SIGN_SIGN_IF_INVALID ||\n+\t     signed_commit_mode == SIGN_ABORT_IF_INVALID) &&\n \t    (sig_sha1.hash_algo || sig_sha256.hash_algo))\n \t\thandle_signature_if_invalid(&new_data, &sig_sha1, &sig_sha256,\n \t\t\t\t\t    &msg, signed_commit_mode);\n@@ -3115,6 +3120,9 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)\n \tcase SIGN_ABORT:\n \t\tdie(_(\"encountered signed tag; use \"\n \t\t      \"--signed-tags=<mode> to handle it\"));\n+\tcase SIGN_ABORT_IF_INVALID:\n+\t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n+\t\t      \"git fast-import with --signed-tags=<mode>\"));\n \tcase SIGN_STRIP_IF_INVALID:\n \t\tdie(_(\"'strip-if-invalid' is not a valid mode for \"\n \t\t      \"git fast-import with --signed-tags=<mode>\"));\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex d517425034..dafd5371fa 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -1164,6 +1164,8 @@ int parse_sign_mode(const char *arg, enum sign_mode *mode, const char **keyid)\n \t\t*mode = SIGN_WARN_STRIP;\n \t} else if (!strcmp(arg, \"strip\")) {\n \t\t*mode = SIGN_STRIP;\n+\t} else if (!strcmp(arg, \"abort-if-invalid\")) {\n+\t\t*mode = SIGN_ABORT_IF_INVALID;\n \t} else if (!strcmp(arg, \"strip-if-invalid\")) {\n \t\t*mode = SIGN_STRIP_IF_INVALID;\n \t} else if (!strcmp(arg, \"sign-if-invalid\")) {\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex a365586ce1..3d95f5ec14 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -115,6 +115,7 @@ void print_signature_buffer(const struct signature_check *sigc,\n /* Modes for --signed-tags=<mode> and --signed-commits=<mode> options. */\n enum sign_mode {\n \tSIGN_ABORT,\n+\tSIGN_ABORT_IF_INVALID,\n \tSIGN_WARN_VERBATIM,\n \tSIGN_VERBATIM,\n \tSIGN_WARN_STRIP,\ndiff --git a/t/t9305-fast-import-signatures.sh b/t/t9305-fast-import-signatures.sh\nindex 18707b3f6c..5667693afd 100755\n--- a/t/t9305-fast-import-signatures.sh\n+++ b/t/t9305-fast-import-signatures.sh\n@@ -103,7 +103,7 @@ test_expect_success RUST,GPG 'strip both OpenPGP signatures with --signed-commit\n \ttest_line_count = 2 out\n '\n \n-for mode in strip-if-invalid sign-if-invalid\n+for mode in strip-if-invalid sign-if-invalid abort-if-invalid\n do\n \ttest_expect_success GPG \"import commit with no signature with --signed-commits=$mode\" '\n \t\tgit fast-export main >output &&\n@@ -135,6 +135,14 @@ do\n \t\t# corresponding `data <length>` command would have to be changed too.\n \t\tsed \"s/OpenPGP signed commit/OpenPGP forged commit/\" output >modified &&\n \n+\t\tif test \"$mode\" = abort-if-invalid\n+\t\tthen\n+\t\t\ttest_must_fail git -C new fast-import --quiet \\\n+\t\t\t\t--signed-commits=$mode <modified >log 2>&1 &&\n+\t\t\ttest_grep \"aborting due to invalid signature\" log &&\n+\t\t\treturn 0\n+\t\tfi &&\n+\n \t\tgit -C new fast-import --quiet --signed-commits=$mode <modified >log 2>&1 &&\n \n \t\tIMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&\n-- \n2.53.0.381.g628a66ccf6\n\n"},{"id":"539882","messageId":"20260324215513.764739-3-jltobler@gmail.com","threadId":"65347","inReplyTo":"20260324215513.764739-1-jltobler@gmail.com","subject":"[PATCH 2/4] fast-import: add 'strip-if-invalid' mode to '--signed-tags=<mode>'","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-03-24T21:55:11Z","receivedAt":"2026-03-24T21:55:33Z","isPatch":true,"body":"With c20f112e51 (fast-import: add 'strip-if-invalid' mode to\n--signed-commits=<mode>, 2025-11-17), git-fast-import(1) learned to\nverify commit signatures during import and strip signatures that fail\nverification. Extend the same behavior to signed tag objects by\nintroducing a 'strip-if-invalid' mode for the '--signed-tags' option.\n\nSigned-off-by: Justin Tobler <jltobler@gmail.com>\n---\n Documentation/git-fast-import.adoc |  7 ++-\n builtin/fast-import.c              | 40 +++++++++++++---\n t/t9306-fast-import-signed-tags.sh | 73 ++++++++++++++++++++++++++++++\n 3 files changed, 110 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 288f2b2a7e..d68bc52b7e 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -66,11 +66,10 @@ fast-import stream! This option is enabled automatically for\n remote-helpers that use the `import` capability, as they are\n already trusted to run their own code.\n \n-`--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)`::\n+`--signed-tags=<mode>`::\n \tSpecify how to handle signed tags. Behaves in the same way as\n-\tthe `--signed-commits=<mode>` below, except that the\n-\t`strip-if-invalid` mode is not yet supported. Like for signed\n-\tcommits, the default mode is `verbatim`.\n+\tthe `--signed-commits=<mode>` below. Like for signed commits,\n+\tthe default mode is `verbatim`.\n \n `--signed-commits=<mode>`::\n \tSpecify how to handle signed commits. The following <mode>s\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 08ea27242d..5e89829aea 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -3089,7 +3089,34 @@ static void parse_new_commit(const char *arg)\n \tb->last_commit = object_count_by_type[OBJ_COMMIT];\n }\n \n-static void handle_tag_signature(struct strbuf *msg, const char *name)\n+static void handle_tag_signature_if_invalid(struct strbuf *buf,\n+\t\t\t\t\t    struct strbuf *msg,\n+\t\t\t\t\t    size_t sig_offset)\n+{\n+\tstruct strbuf signature = STRBUF_INIT;\n+\tstruct strbuf payload = STRBUF_INIT;\n+\tstruct signature_check sigc = { 0 };\n+\n+\tstrbuf_addbuf(&payload, buf);\n+\tstrbuf_addch(&payload, '\\n');\n+\tstrbuf_add(&payload, msg->buf, sig_offset);\n+\tstrbuf_add(&signature, msg->buf + sig_offset, msg->len - sig_offset);\n+\n+\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\n+\tif (!check_signature(&sigc, signature.buf, signature.len))\n+\t\tgoto out;\n+\n+\tstrbuf_setlen(msg, sig_offset);\n+\n+out:\n+\tsignature_check_clear(&sigc);\n+\tstrbuf_release(&signature);\n+\tstrbuf_release(&payload);\n+}\n+\n+static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const char *name)\n {\n \tsize_t sig_offset = parse_signed_buffer(msg->buf, msg->len);\n \n@@ -3115,6 +3142,9 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)\n \t\t/* Truncate the buffer to remove the signature */\n \t\tstrbuf_setlen(msg, sig_offset);\n \t\tbreak;\n+\tcase SIGN_STRIP_IF_INVALID:\n+\t\thandle_tag_signature_if_invalid(buf, msg, sig_offset);\n+\t\tbreak;\n \n \t/* Third, aborting modes */\n \tcase SIGN_ABORT:\n@@ -3123,9 +3153,6 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)\n \tcase SIGN_ABORT_IF_INVALID:\n \t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n \t\t      \"git fast-import with --signed-tags=<mode>\"));\n-\tcase SIGN_STRIP_IF_INVALID:\n-\t\tdie(_(\"'strip-if-invalid' is not a valid mode for \"\n-\t\t      \"git fast-import with --signed-tags=<mode>\"));\n \tcase SIGN_SIGN_IF_INVALID:\n \t\tdie(_(\"'sign-if-invalid' is not a valid mode for \"\n \t\t      \"git fast-import with --signed-tags=<mode>\"));\n@@ -3198,8 +3225,6 @@ static void parse_new_tag(const char *arg)\n \t/* tag payload/message */\n \tparse_data(&msg, 0, NULL);\n \n-\thandle_tag_signature(&msg, t->name);\n-\n \t/* build the tag object */\n \tstrbuf_reset(&new_data);\n \n@@ -3211,6 +3236,9 @@ static void parse_new_tag(const char *arg)\n \tif (tagger)\n \t\tstrbuf_addf(&new_data,\n \t\t\t    \"tagger %s\\n\", tagger);\n+\n+\thandle_tag_signature(&new_data, &msg, t->name);\n+\n \tstrbuf_addch(&new_data, '\\n');\n \tstrbuf_addbuf(&new_data, &msg);\n \tfree(tagger);\ndiff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh\nindex 363619e7d1..fd43b0b52a 100755\n--- a/t/t9306-fast-import-signed-tags.sh\n+++ b/t/t9306-fast-import-signed-tags.sh\n@@ -77,4 +77,77 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '\n \ttest_grep ! \"SSH SIGNATURE\" out\n '\n \n+for mode in strip-if-invalid\n+do\n+\ttest_expect_success GPG \"import tag with no signature with --signed-tags=$mode\" '\n+\t\ttest_when_finished rm -rf import &&\n+\t\tgit init import &&\n+\n+\t\tgit fast-export --signed-tags=verbatim >output &&\n+\t\tgit -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&\n+\t\ttest_must_be_empty log\n+\t'\n+\n+\ttest_expect_success GPG \"keep valid OpenPGP signature with --signed-tags=$mode\" '\n+\t\ttest_when_finished rm -rf import &&\n+\t\tgit init import &&\n+\n+\t\tgit fast-export --signed-tags=verbatim openpgp-signed >output &&\n+\t\tgit -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&\n+\t\tIMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) &&\n+\t\ttest $OPENPGP_SIGNED = $IMPORTED &&\n+\t\tgit -C import cat-file tag \"$IMPORTED\" >actual &&\n+\t\ttest_grep -E \"^-----BEGIN PGP SIGNATURE-----\" actual &&\n+\t\ttest_must_be_empty log\n+\t'\n+\n+\ttest_expect_success GPG \"handle signature invalidated by message change with --signed-tags=$mode\" '\n+\t\ttest_when_finished rm -rf import &&\n+\t\tgit init import &&\n+\n+\t\tgit fast-export --signed-tags=verbatim openpgp-signed >output &&\n+\n+\t\t# Change the tag message, which invalidates the signature. The tag\n+\t\t# message length should not change though, otherwise the corresponding\n+\t\t# `data <length>` command would have to be changed too.\n+\t\tsed \"s/OpenPGP signed tag/OpenPGP forged tag/\" output >modified &&\n+\n+\t\tgit -C import fast-import --quiet --signed-tags=$mode <modified >log 2>&1 &&\n+\n+\t\tIMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) &&\n+\t\ttest $OPENPGP_SIGNED != $IMPORTED &&\n+\t\tgit -C import cat-file tag \"$IMPORTED\" >actual &&\n+\t\ttest_grep ! -E \"^-----BEGIN PGP SIGNATURE-----\" actual &&\n+\t\ttest_must_be_empty log\n+\t'\n+\n+\ttest_expect_success GPGSM \"keep valid X.509 signature with --signed-tags=$mode\" '\n+\t\ttest_when_finished rm -rf import &&\n+\t\tgit init import &&\n+\n+\t\tgit fast-export --signed-tags=verbatim x509-signed >output &&\n+\t\tgit -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&\n+\t\tIMPORTED=$(git -C import rev-parse --verify refs/tags/x509-signed) &&\n+\t\ttest $X509_SIGNED = $IMPORTED &&\n+\t\tgit -C import cat-file tag x509-signed >actual &&\n+\t\ttest_grep -E \"^-----BEGIN SIGNED MESSAGE-----\" actual &&\n+\t\ttest_must_be_empty log\n+\t'\n+\n+\ttest_expect_success GPGSSH \"keep valid SSH signature with --signed-tags=$mode\" '\n+\t\ttest_when_finished rm -rf import &&\n+\t\tgit init import &&\n+\n+\t\ttest_config -C import gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\n+\t\tgit fast-export --signed-tags=verbatim ssh-signed >output &&\n+\t\tgit -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&\n+\t\tIMPORTED=$(git -C import rev-parse --verify refs/tags/ssh-signed) &&\n+\t\ttest $SSH_SIGNED = $IMPORTED &&\n+\t\tgit -C import cat-file tag ssh-signed >actual &&\n+\t\ttest_grep -E \"^-----BEGIN SSH SIGNATURE-----\" actual &&\n+\t\ttest_must_be_empty log\n+\t'\n+done\n+\n test_done\n-- \n2.53.0.381.g628a66ccf6\n\n"},{"id":"539883","messageId":"20260324215513.764739-4-jltobler@gmail.com","threadId":"65347","inReplyTo":"20260324215513.764739-1-jltobler@gmail.com","subject":"[PATCH 3/4] fast-import: add 'sign-if-invalid' mode to '--signed-tags=<mode>'","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-03-24T21:55:12Z","receivedAt":"2026-03-24T21:55:33Z","isPatch":true,"body":"With ee66c793f8 (fast-import: add mode to sign commits with invalid\nsignatures, 2026-03-12), git-fast-import(1) learned to verify commit\nsignatures during import and replace signatures that fail verification\nwith a newly generated one. Extend the same behavior to signed tag\nobjects by introducing a 'sign-if-invalid' mode for the '--signed-tags'\noption.\n\nSigned-off-by: Justin Tobler <jltobler@gmail.com>\n---\n builtin/fast-import.c              | 20 ++++++++++++---\n t/t9306-fast-import-signed-tags.sh | 41 ++++++++++++++++++++++++++++--\n 2 files changed, 55 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 5e89829aea..783e0e7ab4 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -191,6 +191,7 @@ static const char *global_prefix;\n static enum sign_mode signed_tag_mode = SIGN_VERBATIM;\n static enum sign_mode signed_commit_mode = SIGN_VERBATIM;\n static const char *signed_commit_keyid;\n+static const char *signed_tag_keyid;\n \n /* Memory pools */\n static struct mem_pool fi_mem_pool = {\n@@ -3110,6 +3111,19 @@ static void handle_tag_signature_if_invalid(struct strbuf *buf,\n \n \tstrbuf_setlen(msg, sig_offset);\n \n+\tif (signed_tag_mode == SIGN_SIGN_IF_INVALID) {\n+\t\tstrbuf_attach(&payload, sigc.payload, sigc.payload_len,\n+\t\t\t      sigc.payload_len + 1);\n+\t\tsigc.payload = NULL;\n+\t\tstrbuf_reset(&signature);\n+\n+\t\tif (sign_buffer(&payload, &signature, signed_tag_keyid,\n+\t\t\t\tSIGN_BUFFER_USE_DEFAULT_KEY))\n+\t\t\tdie(_(\"failed to sign tag object\"));\n+\n+\t\tstrbuf_addbuf(msg, &signature);\n+\t}\n+\n out:\n \tsignature_check_clear(&sigc);\n \tstrbuf_release(&signature);\n@@ -3142,6 +3156,7 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c\n \t\t/* Truncate the buffer to remove the signature */\n \t\tstrbuf_setlen(msg, sig_offset);\n \t\tbreak;\n+\tcase SIGN_SIGN_IF_INVALID:\n \tcase SIGN_STRIP_IF_INVALID:\n \t\thandle_tag_signature_if_invalid(buf, msg, sig_offset);\n \t\tbreak;\n@@ -3153,9 +3168,6 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c\n \tcase SIGN_ABORT_IF_INVALID:\n \t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n \t\t      \"git fast-import with --signed-tags=<mode>\"));\n-\tcase SIGN_SIGN_IF_INVALID:\n-\t\tdie(_(\"'sign-if-invalid' is not a valid mode for \"\n-\t\t      \"git fast-import with --signed-tags=<mode>\"));\n \tdefault:\n \t\tBUG(\"invalid signed_tag_mode value %d from tag '%s'\",\n \t\t    signed_tag_mode, name);\n@@ -3749,7 +3761,7 @@ static int parse_one_option(const char *option)\n \t\tif (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid))\n \t\t\tusagef(_(\"unknown --signed-commits mode '%s'\"), option);\n \t} else if (skip_prefix(option, \"signed-tags=\", &option)) {\n-\t\tif (parse_sign_mode(option, &signed_tag_mode, NULL))\n+\t\tif (parse_sign_mode(option, &signed_tag_mode, &signed_tag_keyid))\n \t\t\tusagef(_(\"unknown --signed-tags mode '%s'\"), option);\n \t} else if (!strcmp(option, \"quiet\")) {\n \t\tshow_stats = 0;\ndiff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh\nindex fd43b0b52a..bb4c8008ef 100755\n--- a/t/t9306-fast-import-signed-tags.sh\n+++ b/t/t9306-fast-import-signed-tags.sh\n@@ -77,7 +77,7 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '\n \ttest_grep ! \"SSH SIGNATURE\" out\n '\n \n-for mode in strip-if-invalid\n+for mode in strip-if-invalid sign-if-invalid\n do\n \ttest_expect_success GPG \"import tag with no signature with --signed-tags=$mode\" '\n \t\ttest_when_finished rm -rf import &&\n@@ -117,7 +117,15 @@ do\n \t\tIMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) &&\n \t\ttest $OPENPGP_SIGNED != $IMPORTED &&\n \t\tgit -C import cat-file tag \"$IMPORTED\" >actual &&\n-\t\ttest_grep ! -E \"^-----BEGIN PGP SIGNATURE-----\" actual &&\n+\n+\t\tif test \"$mode\" = strip-if-invalid\n+\t\tthen\n+\t\t\ttest_grep ! -E \"^-----BEGIN PGP SIGNATURE-----\" actual\n+\t\telse\n+\t\t\ttest_grep -E \"^-----BEGIN PGP SIGNATURE-----\" actual &&\n+\t\t\tgit -C import verify-tag \"$IMPORTED\"\n+\t\tfi &&\n+\n \t\ttest_must_be_empty log\n \t'\n \n@@ -150,4 +158,33 @@ do\n \t'\n done\n \n+test_expect_success GPGSSH 'sign invalid tag with explicit keyid' '\n+\ttest_when_finished rm -rf import &&\n+\tgit init import &&\n+\n+\tgit fast-export --signed-tags=verbatim ssh-signed >output &&\n+\n+\t# Change the tag message, which invalidates the signature. The tag\n+\t# message length should not change though, otherwise the corresponding\n+\t# `data <length>` command would have to be changed too.\n+\tsed \"s/SSH signed tag/SSH forged tag/\" output >modified &&\n+\n+\t# Configure the target repository with an invalid default signing key.\n+\ttest_config -C import user.signingkey \"not-a-real-key-id\" &&\n+\ttest_config -C import gpg.format ssh &&\n+\ttest_config -C import gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git -C import fast-import --quiet \\\n+\t\t--signed-tags=sign-if-invalid <modified >/dev/null 2>&1 &&\n+\n+\t# Import using explicitly provided signing key.\n+\tgit -C import fast-import --quiet \\\n+\t\t--signed-tags=sign-if-invalid=\"${GPGSSH_KEY_PRIMARY}\" <modified &&\n+\n+\tIMPORTED=$(git -C import rev-parse --verify refs/tags/ssh-signed) &&\n+\ttest $SSH_SIGNED != $IMPORTED &&\n+\tgit -C import cat-file tag \"$IMPORTED\" >actual &&\n+\ttest_grep -E \"^-----BEGIN SSH SIGNATURE-----\" actual &&\n+\tgit -C import verify-tag \"$IMPORTED\"\n+'\n+\n test_done\n-- \n2.53.0.381.g628a66ccf6\n\n"},{"id":"539884","messageId":"20260324215513.764739-5-jltobler@gmail.com","threadId":"65347","inReplyTo":"20260324215513.764739-1-jltobler@gmail.com","subject":"[PATCH 4/4] fast-import: add 'abort-if-invalid' mode to '--signed-tags=<mode>'","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-03-24T21:55:13Z","receivedAt":"2026-03-24T21:55:34Z","isPatch":true,"body":"In git-fast-import(1), the 'abort-if-invalid' mode for the\n'--signed-commits' option verifies commit signatures during import and\naborts the entire operation when verification fails. Extend the same\nbehavior to signed tag objects by introducing an 'abort-if-invalid' mode\nfor the '--signed-tags' option.\n\nSigned-off-by: Justin Tobler <jltobler@gmail.com>\n---\n builtin/fast-import.c              |  7 ++++---\n t/t9306-fast-import-signed-tags.sh | 10 +++++++++-\n 2 files changed, 13 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 783e0e7ab4..cd1181023d 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -3109,6 +3109,9 @@ static void handle_tag_signature_if_invalid(struct strbuf *buf,\n \tif (!check_signature(&sigc, signature.buf, signature.len))\n \t\tgoto out;\n \n+\tif (signed_tag_mode == SIGN_ABORT_IF_INVALID)\n+\t\tdie(_(\"aborting due to invalid signature\"));\n+\n \tstrbuf_setlen(msg, sig_offset);\n \n \tif (signed_tag_mode == SIGN_SIGN_IF_INVALID) {\n@@ -3156,6 +3159,7 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c\n \t\t/* Truncate the buffer to remove the signature */\n \t\tstrbuf_setlen(msg, sig_offset);\n \t\tbreak;\n+\tcase SIGN_ABORT_IF_INVALID:\n \tcase SIGN_SIGN_IF_INVALID:\n \tcase SIGN_STRIP_IF_INVALID:\n \t\thandle_tag_signature_if_invalid(buf, msg, sig_offset);\n@@ -3165,9 +3169,6 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c\n \tcase SIGN_ABORT:\n \t\tdie(_(\"encountered signed tag; use \"\n \t\t      \"--signed-tags=<mode> to handle it\"));\n-\tcase SIGN_ABORT_IF_INVALID:\n-\t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n-\t\t      \"git fast-import with --signed-tags=<mode>\"));\n \tdefault:\n \t\tBUG(\"invalid signed_tag_mode value %d from tag '%s'\",\n \t\t    signed_tag_mode, name);\ndiff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh\nindex bb4c8008ef..ec2b241cdb 100755\n--- a/t/t9306-fast-import-signed-tags.sh\n+++ b/t/t9306-fast-import-signed-tags.sh\n@@ -77,7 +77,7 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '\n \ttest_grep ! \"SSH SIGNATURE\" out\n '\n \n-for mode in strip-if-invalid sign-if-invalid\n+for mode in strip-if-invalid sign-if-invalid abort-if-invalid\n do\n \ttest_expect_success GPG \"import tag with no signature with --signed-tags=$mode\" '\n \t\ttest_when_finished rm -rf import &&\n@@ -112,6 +112,14 @@ do\n \t\t# `data <length>` command would have to be changed too.\n \t\tsed \"s/OpenPGP signed tag/OpenPGP forged tag/\" output >modified &&\n \n+\t\tif test \"$mode\" = abort-if-invalid\n+\t\tthen\n+\t\t\ttest_must_fail git -C import fast-import --quiet \\\n+\t\t\t\t--signed-tags=$mode <modified >log 2>&1 &&\n+\t\t\ttest_grep \"aborting due to invalid signature\" log &&\n+\t\t\treturn 0\n+\t\tfi &&\n+\n \t\tgit -C import fast-import --quiet --signed-tags=$mode <modified >log 2>&1 &&\n \n \t\tIMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) &&\n-- \n2.53.0.381.g628a66ccf6\n\n"},{"id":"539888","messageId":"xmqqpl4syijh.fsf@gitster.g","threadId":"65347","inReplyTo":"20260324215513.764739-2-jltobler@gmail.com","subject":"Re: [PATCH 1/4] fast-import: add 'abort-if-invalid' mode to '--signed-commits=<mode>'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-24T22:22:26Z","receivedAt":"2026-03-24T22:22:29Z","isPatch":true,"body":"Justin Tobler <jltobler@gmail.com> writes:\n\n> diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> index 13621b0d6a..dcbc5bc82d 100644\n> --- a/builtin/fast-export.c\n> +++ b/builtin/fast-export.c\n> @@ -822,6 +822,9 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n>  \t\t\tdie(_(\"encountered signed commit %s; use \"\n>  \t\t\t      \"--signed-commits=<mode> to handle it\"),\n>  \t\t\t    oid_to_hex(&commit->object.oid));\n> +\t\tcase SIGN_ABORT_IF_INVALID:\n> +\t\t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n> +\t\t\t      \"git fast-export with --signed-commits=<mode>\"));\n>  \t\tcase SIGN_STRIP_IF_INVALID:\n>  \t\t\tdie(_(\"'strip-if-invalid' is not a valid mode for \"\n>  \t\t\t      \"git fast-export with --signed-commits=<mode>\"));\n\nThere are a few similar hunks in this patch to fast-export, but I am\nnot sure what is going on here.\n\nI may be misreading the code, but this error, and the similar one\nfor strip-if-invalid that is already there, trigger if the command\nis given \"--signed-commits=abort-if-invalid\" on its command line,\nand when we see a signed commit in the range we walk to export the\ncommits.  Why shouldn't the user get the error immediately while the\ncommand is parsing the command line options?  You may be sharing the\nunderlying parse_sign_mode() with import side that may support more\nvariants, but that is not a good excuse to make these two\n\n    git fast-export --signed-commits=abort-if-invalid\n    git fast-export --signed-commits=i-dont-know-what-i-am-doing\n\nbehave completely differently, no?\n\nYou can either move these \"no, these subset of options are not\navailable here\" to fast-export.c::parse_opt_sign_mode(), or even\nbetter yet, teach parse_sign_mode() an option to say \"hey, you are\nbeing called from fast-export, so pretend that you have never heard\nof options that are only available on fast-import\" and error out\nright there, can't you?  Or would it be too _early_ to give errors\nto users?\n\nThanks.\n\n\n\n\n\n\n\n\n"},{"id":"539975","messageId":"acQeGoBgsLFM7EWp@denethor","threadId":"65347","inReplyTo":"xmqqpl4syijh.fsf@gitster.g","subject":"Re: [PATCH 1/4] fast-import: add 'abort-if-invalid' mode to '--signed-commits=<mode>'","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-03-25T18:03:00Z","receivedAt":"2026-03-25T18:03:05Z","isPatch":true,"body":"On 26/03/24 03:22PM, Junio C Hamano wrote:\n> Justin Tobler <jltobler@gmail.com> writes:\n> \n> > diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> > index 13621b0d6a..dcbc5bc82d 100644\n> > --- a/builtin/fast-export.c\n> > +++ b/builtin/fast-export.c\n> > @@ -822,6 +822,9 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n> >  \t\t\tdie(_(\"encountered signed commit %s; use \"\n> >  \t\t\t      \"--signed-commits=<mode> to handle it\"),\n> >  \t\t\t    oid_to_hex(&commit->object.oid));\n> > +\t\tcase SIGN_ABORT_IF_INVALID:\n> > +\t\t\tdie(_(\"'abort-if-invalid' is not a valid mode for \"\n> > +\t\t\t      \"git fast-export with --signed-commits=<mode>\"));\n> >  \t\tcase SIGN_STRIP_IF_INVALID:\n> >  \t\t\tdie(_(\"'strip-if-invalid' is not a valid mode for \"\n> >  \t\t\t      \"git fast-export with --signed-commits=<mode>\"));\n> \n> There are a few similar hunks in this patch to fast-export, but I am\n> not sure what is going on here.\n\ngit-fast-export(1) does not support the `{strip,sign,abort}-if-invalid`\nmode for the `--signed-{commits,tags}` options. Therefore we die in\ncases where we parse this option with an unsupported mode.\n\n> I may be misreading the code, but this error, and the similar one\n> for strip-if-invalid that is already there, trigger if the command\n> is given \"--signed-commits=abort-if-invalid\" on its command line,\n> and when we see a signed commit in the range we walk to export the\n> commits.  Why shouldn't the user get the error immediately while the\n> command is parsing the command line options?  You may be sharing the\n> underlying parse_sign_mode() with import side that may support more\n> variants, but that is not a good excuse to make these two\n> \n>     git fast-export --signed-commits=abort-if-invalid\n>     git fast-export --signed-commits=i-dont-know-what-i-am-doing\n> \n> behave completely differently, no?\n\nYa, that is a completely fair point. If the mode is unsupported, it\ndoesn't make sense to wait until we encounter a signed commit/tag to\ndeclare the user provided an invalid signed mode for the command. \n\n> You can either move these \"no, these subset of options are not\n> available here\" to fast-export.c::parse_opt_sign_mode(), or even\n> better yet, teach parse_sign_mode() an option to say \"hey, you are\n> being called from fast-export, so pretend that you have never heard\n> of options that are only available on fast-import\" and error out\n> right there, can't you?  Or would it be too _early_ to give errors\n> to users?\n\nI think it definately makes sense to move signing mode verification to\nbe around the same time the option is initially parsed. With this patch\nseries, the `--signed-commits` and `--signed-tags` options for\ngit-fast-import(1) will support the same modes. It will only be\ngit-fast-export(1) that supports a subset of the signing modes. It\nshould be easy enough to update \"fast-export.c:parse_opt_sign_mode()\" to\nexplictly handle the unsupported signing modes upfront.\n\nI'll update in the next version accordingly.\n\nThanks,\n-Justin\n"}]}