fast-import: add 'sign-if-invalid' mode to '--signed-tags=<mode>'

With ee66c793f8 (fast-import: add mode to sign commits with invalid signatures, 2026-03-12), git-fast-import(1) learned to verify commit signatures during import and replace signatures that fail verification with a newly generated one. Extend the same behavior to signed tag objects by introducing a 'sign-if-invalid' mode for the '--signed-tags' option. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Mar 26, 2026 at 14:14 UTC 2b1546c03cc3e02e51261fa38fe47a4f1b4e295b
2 files changed +55 -6
builtin/fast-import.c
+16 -4
@@ -191,6 +191,7 @@ static const char *global_prefix;
191 static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
192 static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
193 static const char *signed_commit_keyid;
194 +static const char *signed_tag_keyid;
195
196 /* Memory pools */
197 static struct mem_pool fi_mem_pool = {
@@ -3110,6 +3111,19 @@ static void handle_tag_signature_if_invalid(struct strbuf *buf,
3111
3112 strbuf_setlen(msg, sig_offset);
3113
3114 + if (signed_tag_mode == SIGN_SIGN_IF_INVALID) {
3115 + strbuf_attach(&payload, sigc.payload, sigc.payload_len,
3116 + sigc.payload_len + 1);
3117 + sigc.payload = NULL;
3118 + strbuf_reset(&signature);
3119 +
3120 + if (sign_buffer(&payload, &signature, signed_tag_keyid,
3121 + SIGN_BUFFER_USE_DEFAULT_KEY))
3122 + die(_("failed to sign tag object"));
3123 +
3124 + strbuf_addbuf(msg, &signature);
3125 + }
3126 +
3127 out:
3128 signature_check_clear(&sigc);
3129 strbuf_release(&signature);
@@ -3142,6 +3156,7 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c
3156 /* Truncate the buffer to remove the signature */
3157 strbuf_setlen(msg, sig_offset);
3158 break;
3159 + case SIGN_SIGN_IF_INVALID:
3160 case SIGN_STRIP_IF_INVALID:
3161 handle_tag_signature_if_invalid(buf, msg, sig_offset);
3162 break;
@@ -3153,9 +3168,6 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c
3168 case SIGN_ABORT_IF_INVALID:
3169 die(_("'abort-if-invalid' is not a valid mode for "
3170 "git fast-import with --signed-tags=<mode>"));
3156 - case SIGN_SIGN_IF_INVALID:
3157 - die(_("'sign-if-invalid' is not a valid mode for "
3158 - "git fast-import with --signed-tags=<mode>"));
3171 default:
3172 BUG("invalid signed_tag_mode value %d from tag '%s'",
3173 signed_tag_mode, name);
@@ -3749,7 +3761,7 @@ static int parse_one_option(const char *option)
3761 if (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid))
3762 usagef(_("unknown --signed-commits mode '%s'"), option);
3763 } else if (skip_prefix(option, "signed-tags=", &option)) {
3752 - if (parse_sign_mode(option, &signed_tag_mode, NULL))
3764 + if (parse_sign_mode(option, &signed_tag_mode, &signed_tag_keyid))
3765 usagef(_("unknown --signed-tags mode '%s'"), option);
3766 } else if (!strcmp(option, "quiet")) {
3767 show_stats = 0;
t/t9306-fast-import-signed-tags.sh
+39 -2
@@ -77,7 +77,7 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '
77 test_grep ! "SSH SIGNATURE" out
78 '
79
80 -for mode in strip-if-invalid
80 +for mode in strip-if-invalid sign-if-invalid
81 do
82 test_expect_success GPG "import tag with no signature with --signed-tags=$mode" '
83 test_when_finished rm -rf import &&
@@ -117,7 +117,15 @@ do
117 IMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) &&
118 test $OPENPGP_SIGNED != $IMPORTED &&
119 git -C import cat-file tag "$IMPORTED" >actual &&
120 - test_grep ! -E "^-----BEGIN PGP SIGNATURE-----" actual &&
120 +
121 + if test "$mode" = strip-if-invalid
122 + then
123 + test_grep ! -E "^-----BEGIN PGP SIGNATURE-----" actual
124 + else
125 + test_grep -E "^-----BEGIN PGP SIGNATURE-----" actual &&
126 + git -C import verify-tag "$IMPORTED"
127 + fi &&
128 +
129 test_must_be_empty log
130 '
131
@@ -150,4 +158,33 @@ do
158 '
159 done
160
161 +test_expect_success GPGSSH 'sign invalid tag with explicit keyid' '
162 + test_when_finished rm -rf import &&
163 + git init import &&
164 +
165 + git fast-export --signed-tags=verbatim ssh-signed >output &&
166 +
167 + # Change the tag message, which invalidates the signature. The tag
168 + # message length should not change though, otherwise the corresponding
169 + # `data <length>` command would have to be changed too.
170 + sed "s/SSH signed tag/SSH forged tag/" output >modified &&
171 +
172 + # Configure the target repository with an invalid default signing key.
173 + test_config -C import user.signingkey "not-a-real-key-id" &&
174 + test_config -C import gpg.format ssh &&
175 + test_config -C import gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
176 + test_must_fail git -C import fast-import --quiet \
177 + --signed-tags=sign-if-invalid <modified >/dev/null 2>&1 &&
178 +
179 + # Import using explicitly provided signing key.
180 + git -C import fast-import --quiet \
181 + --signed-tags=sign-if-invalid="${GPGSSH_KEY_PRIMARY}" <modified &&
182 +
183 + IMPORTED=$(git -C import rev-parse --verify refs/tags/ssh-signed) &&
184 + test $SSH_SIGNED != $IMPORTED &&
185 + git -C import cat-file tag "$IMPORTED" >actual &&
186 + test_grep -E "^-----BEGIN SSH SIGNATURE-----" actual &&
187 + git -C import verify-tag "$IMPORTED"
188 +'
189 +
190 test_done