Skip to content

Commit a579a66

Browse files
chriscoolgitster
authored andcommitted
fast-import: add '--signed-tags=<mode>' option
Recently, eaaddf5 (fast-import: add '--signed-commits=<mode>' option, 2025-09-17) added support for controlling how signed commits are handled by `git fast-import`, but there is no option yet to decide about signed tags. To remediate that, let's add a '--signed-tags=<mode>' option to `git fast-import` too. With this, both `git fast-export` and `git fast-import` have both a '--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting the same <mode>s. Signed-off-by: Christian Couder <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 73dd290 commit a579a66

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

Documentation/git-fast-import.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ fast-import stream! This option is enabled automatically for
6666
remote-helpers that use the `import` capability, as they are
6767
already trusted to run their own code.
6868

69+
--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::
70+
Specify how to handle signed tags. Behaves in the same way
71+
as the same option in linkgit:git-fast-export[1], except that
72+
default is 'verbatim' (instead of 'abort').
73+
6974
--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::
7075
Specify how to handle signed commits. Behaves in the same way
7176
as the same option in linkgit:git-fast-export[1], except that

builtin/fast-import.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ static int global_argc;
188188
static const char **global_argv;
189189
static const char *global_prefix;
190190

191+
static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
191192
static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
192193

193194
/* Memory pools */
@@ -2961,6 +2962,43 @@ static void parse_new_commit(const char *arg)
29612962
b->last_commit = object_count_by_type[OBJ_COMMIT];
29622963
}
29632964

2965+
static void handle_tag_signature(struct strbuf *msg, const char *name)
2966+
{
2967+
size_t sig_offset = parse_signed_buffer(msg->buf, msg->len);
2968+
2969+
/* If there is no signature, there is nothing to do. */
2970+
if (sig_offset >= msg->len)
2971+
return;
2972+
2973+
switch (signed_tag_mode) {
2974+
2975+
/* First, modes that don't change anything */
2976+
case SIGN_ABORT:
2977+
die("encountered signed tag; use "
2978+
"--signed-tags=<mode> to handle it");
2979+
case SIGN_WARN_VERBATIM:
2980+
warning(_("importing a tag signature verbatim for tag '%s'"), name);
2981+
/* fallthru */
2982+
case SIGN_VERBATIM:
2983+
/* Nothing to do, the signature will be put into the imported tag. */
2984+
break;
2985+
2986+
/* Second, modes that remove the signature */
2987+
case SIGN_WARN_STRIP:
2988+
warning(_("stripping a tag signature for tag '%s'"), name);
2989+
/* fallthru */
2990+
case SIGN_STRIP:
2991+
/* Truncate the buffer to remove the signature */
2992+
strbuf_setlen(msg, sig_offset);
2993+
break;
2994+
2995+
/* Third, BUG */
2996+
default:
2997+
BUG("invalid signed_tag_mode value %d from tag '%s'",
2998+
signed_tag_mode, name);
2999+
}
3000+
}
3001+
29643002
static void parse_new_tag(const char *arg)
29653003
{
29663004
static struct strbuf msg = STRBUF_INIT;
@@ -3024,6 +3062,8 @@ static void parse_new_tag(const char *arg)
30243062
/* tag payload/message */
30253063
parse_data(&msg, 0, NULL);
30263064

3065+
handle_tag_signature(&msg, t->name);
3066+
30273067
/* build the tag object */
30283068
strbuf_reset(&new_data);
30293069

@@ -3544,6 +3584,9 @@ static int parse_one_option(const char *option)
35443584
} else if (skip_prefix(option, "signed-commits=", &option)) {
35453585
if (parse_sign_mode(option, &signed_commit_mode))
35463586
usagef(_("unknown --signed-commits mode '%s'"), option);
3587+
} else if (skip_prefix(option, "signed-tags=", &option)) {
3588+
if (parse_sign_mode(option, &signed_tag_mode))
3589+
usagef(_("unknown --signed-tags mode '%s'"), option);
35473590
} else if (!strcmp(option, "quiet")) {
35483591
show_stats = 0;
35493592
quiet = 1;

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,7 @@ integration_tests = [
10361036
't9303-fast-import-compression.sh',
10371037
't9304-fast-import-marks.sh',
10381038
't9305-fast-import-signatures.sh',
1039+
't9306-fast-import-signed-tags.sh',
10391040
't9350-fast-export.sh',
10401041
't9351-fast-export-anonymize.sh',
10411042
't9400-git-cvsserver-server.sh',

t/t9306-fast-import-signed-tags.sh

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/sh
2+
3+
test_description='git fast-import --signed-tags=<mode>'
4+
5+
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
6+
7+
. ./test-lib.sh
8+
. "$TEST_DIRECTORY/lib-gpg.sh"
9+
10+
test_expect_success 'set up unsigned initial commit and import repo' '
11+
test_commit first &&
12+
git init new
13+
'
14+
15+
test_expect_success 'import no signed tag with --signed-tags=abort' '
16+
git fast-export --signed-tags=verbatim >output &&
17+
git -C new fast-import --quiet --signed-tags=abort <output
18+
'
19+
20+
test_expect_success GPG 'set up OpenPGP signed tag' '
21+
git tag -s -m "OpenPGP signed tag" openpgp-signed first &&
22+
OPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&
23+
git fast-export --signed-tags=verbatim openpgp-signed >output
24+
'
25+
26+
test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '
27+
test_must_fail git -C new fast-import --quiet --signed-tags=abort <output
28+
'
29+
30+
test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '
31+
git -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&
32+
IMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&
33+
test $OPENPGP_SIGNED = $IMPORTED &&
34+
test_must_be_empty log
35+
'
36+
37+
test_expect_success GPGSM 'setup X.509 signed tag' '
38+
test_config gpg.format x509 &&
39+
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
40+
41+
git tag -s -m "X.509 signed tag" x509-signed first &&
42+
X509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&
43+
git fast-export --signed-tags=verbatim x509-signed >output
44+
'
45+
46+
test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '
47+
git -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&
48+
test_grep "stripping a tag signature for tag '\''x509-signed'\''" log &&
49+
IMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&
50+
test $X509_SIGNED != $IMPORTED &&
51+
git -C new cat-file -p x509-signed >out &&
52+
test_grep ! "SIGNED MESSAGE" out
53+
'
54+
55+
test_expect_success GPGSSH 'setup SSH signed tag' '
56+
test_config gpg.format ssh &&
57+
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
58+
59+
git tag -s -m "SSH signed tag" ssh-signed first &&
60+
SSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&
61+
git fast-export --signed-tags=verbatim ssh-signed >output
62+
'
63+
64+
test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '
65+
git -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&
66+
test_grep "importing a tag signature verbatim for tag '\''ssh-signed'\''" log &&
67+
IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
68+
test $SSH_SIGNED = $IMPORTED
69+
'
70+
71+
test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '
72+
git -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&
73+
test_must_be_empty log &&
74+
IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
75+
test $SSH_SIGNED != $IMPORTED &&
76+
git -C new cat-file -p ssh-signed >out &&
77+
test_grep ! "SSH SIGNATURE" out
78+
'
79+
80+
test_done

0 commit comments

Comments
 (0)