{"thread":{"id":"64261","subject":"[PATCH 0/5] fast-import: start controlling how tag signatures are handled","startedAt":"2025-10-07T12:30:40Z","lastAt":"2025-10-24T15:03:05Z","messageCount":52,"participants":["Christian Couder","Patrick Steinhardt","Collin Funk","Todd Zullinger","Junio C Hamano","Elijah Newren"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"528091","messageId":"20251007122958.1089680-1-christian.couder@gmail.com","threadId":"64261","inReplyTo":null,"subject":"[PATCH 0/5] fast-import: start controlling how tag signatures are handled","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-07T12:29:53Z","receivedAt":"2025-10-07T12:30:40Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Introduction\n------------\n\nTools like `git-filter-repo` should be able to control how tag\nsignatures are handled when regenerating repository content after it\nhas been filtered. For this purpose, they need a way for `git\nfast-import` to control how tag signatures are handled.\n\nA previous series [1] added a '--signed-commits=<mode>' option to `git\nfast-import` to control how commit signatures are handled, so this is\nadding a similar '--signed-tags=<mode>' for tag signatures.\n\nFor now this new option behaves in a very similar way as the option\nwith the same name that already exists in `git fast-export`.\nEspecially it supports exactly the same <mode>s and the same aliases\nfor these modes. For example \"ignore\" is a synonym for \"verbatim\".\n\nThis way, both `git fast-export` and `git fast-import` have both a\n'--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting the\nsame <mode>s.\n\nIn the future I want to implement new <mode>s like \"strip-if-invalid\",\n\"re-sign\", \"re-sign-if-invalid\" in `git fast-import` for both tag and\ncommit signatures. These might be a bit more complex, so for now I\nprefer to start with the simple modes.\n\n[1] https://lore.kernel.org/git/20250917181427.3193500-1-christian.couder@gmail.com/\n\nNote about the different patches\n--------------------------------\n\nPatch 1/5 (doc: git-tag: stop focussing on GPG signed tags) is a\ndocumentation update for `git tag`. It could go in a separate series\nor be dropped altogether, but while working on this I thought that it\nwould be a good thing to do, as the doc is quite outdated.\n\nPatches 2/5, 3/5 and 4/5 are preparatory patches for the main one\nwhich is patch 5/5 (fast-import: add '--signed-tags=<mode>' option).\n\nI wanted '--signed-tags=<mode>' to work for all kinds of signature in\ntags (OpenPGP, X.509 and SSH) but soon realized that the\n'--signed-tags=<mode>' option of `git fast-export` worked only for\nOpenPGP signatures, so I fixed that issue in patch 4/5 (fast-export:\nhandle all kinds of tag signatures).\n\nWhile working on the tests in patch 4/5, I found a few things to\nimprove that could belong to other patches so that's how I came up\nwith patches 2/5 and 3/5.\n\nCI tests:\n---------\n\nThey have all passed except one on Windows where\n\"t8020-last-modified.sh\" failed. I doubt it's related though. See:\n\nhttps://github.com/chriscool/git/actions/runs/18311274807/job/52140205441\n\nChristian Couder (5):\n  doc: git-tag: stop focussing on GPG signed tags\n  lib-gpg: allow tests with the GPGSM prereq first\n  t9350: properly count annotated tags\n  fast-export: handle all kinds of tag signatures\n  fast-import: add '--signed-tags=<mode>' option\n\n Documentation/git-fast-import.adoc |  5 ++\n Documentation/git-tag.adoc         | 52 ++++++++++++-------\n builtin/fast-export.c              |  7 ++-\n builtin/fast-import.c              | 43 ++++++++++++++++\n t/lib-gpg.sh                       |  2 +-\n t/meson.build                      |  1 +\n t/t9306-fast-import-signed-tags.sh | 80 ++++++++++++++++++++++++++++++\n t/t9350-fast-export.sh             | 60 ++++++++++++++++++++--\n 8 files changed, 224 insertions(+), 26 deletions(-)\n create mode 100755 t/t9306-fast-import-signed-tags.sh\n\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528092","messageId":"20251007122958.1089680-2-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251007122958.1089680-1-christian.couder@gmail.com","subject":"[PATCH 1/5] doc: git-tag: stop focussing on GPG signed tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-07T12:29:54Z","receivedAt":"2025-10-07T12:30:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"It looks like the documentation of `git tag` is focussed a bit too\nmuch on GPG signed tags.\n\nThis starts with the \"NAME\" section where the command is described\nwith:\n\n\"Create, list, delete or verify a tag object signed with GPG\"\n\nwhile for example `git branch` is described with simply:\n\n\"List, create, or delete branches\"\n\nThis could give the false impression that `git tag` only works with\ntag objects, not with lightweight tags, and that tag objects are\nalways GPG signed.\n\nIn the \"DESCRIPTION\" section, it looks like only \"GnuPG signed tag\nobjects\" can be created by the `-s` and `-u <key-id>` options, and it\nseems `gpg.program` can only specify a \"custom GnuPG binary\".\n\nThis goes on in the \"OPTIONS\" section too, especially about the `-s`\nand `-u <key-id>` options.\n\nThe \"CONFIGURATION\" also doesn't talk about how to configure the\ncommand to work with X.509 and SSH signatures.\n\nLet's rework all that to make sure users have a more accurate and\nbalanced view of what the command can do.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-tag.adoc | 52 +++++++++++++++++++++++++-------------\n 1 file changed, 35 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc\nindex a4b1c0ec05..9117754ffb 100644\n--- a/Documentation/git-tag.adoc\n+++ b/Documentation/git-tag.adoc\n@@ -3,7 +3,7 @@ git-tag(1)\n \n NAME\n ----\n-git-tag - Create, list, delete or verify a tag object signed with GPG\n+git-tag - Create, list, delete or verify tags\n \n \n SYNOPSIS\n@@ -38,17 +38,18 @@ and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n \n-A GnuPG signed tag object will be created when `-s` or `-u\n-<key-id>` is used.  When `-u <key-id>` is not used, the\n-committer identity for the current user is used to find the\n-GnuPG key for signing. \tThe configuration variable `gpg.program`\n-is used to specify custom GnuPG binary.\n+A cryptographically signed tag object will be created when `-s` or\n+`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is\n+controlled by the `gpg.format` configuration variable, defaulting to\n+OpenPGP. When `-u <key-id>` is not used, the committer identity for\n+the current user is used to find the key for signing. The\n+configuration variable `gpg.program` is used to specify a custom\n+signing binary.\n \n Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n tags; they contain a creation date, the tagger name and e-mail, a\n-tagging message, and an optional GnuPG signature. Whereas a\n-\"lightweight\" tag is simply a name for an object (usually a commit\n-object).\n+tagging message, and an optional signature. Whereas a \"lightweight\"\n+tag is simply a name for an object (usually a commit object).\n \n Annotated tags are meant for release while lightweight tags are meant\n for private or temporary object labels. For this reason, some git\n@@ -64,10 +65,12 @@ OPTIONS\n \n -s::\n --sign::\n-\tMake a GPG-signed tag, using the default e-mail address's key.\n-\tThe default behavior of tag GPG-signing is controlled by `tag.gpgSign`\n-\tconfiguration variable if it exists, or disabled otherwise.\n-\tSee linkgit:git-config[1].\n+\tMake a signed tag, using the default signing key. The signing\n+\tbackend used depends on the `gpg.format` configuration\n+\tvariable. The default key is determined by the backend. For\n+\tGPG, it's based on the committer's email address, while for\n+\tSSH it may be a specific key file or agent identity. See\n+\tlinkgit:git-config[1].\n \n --no-sign::\n \tOverride `tag.gpgSign` configuration variable that is\n@@ -75,7 +78,9 @@ OPTIONS\n \n -u <key-id>::\n --local-user=<key-id>::\n-\tMake a GPG-signed tag, using the given key.\n+\tMake a signed tag using the given key. The format of the\n+\t<key-id> and the backend used depend on the `gpg.format`\n+\tconfiguration variable. See linkgit:git-config[1].\n \n -f::\n --force::\n@@ -87,7 +92,7 @@ OPTIONS\n \n -v::\n --verify::\n-\tVerify the GPG signature of the given tag names.\n+\tVerify the signature of the given tag names.\n \n -n<num>::\n \t<num> specifies how many lines from the annotation, if any,\n@@ -236,12 +241,25 @@ it in the repository configuration as follows:\n \n -------------------------------------\n [user]\n-    signingKey = <gpg-key-id>\n+    signingKey = <key-id>\n -------------------------------------\n \n+The signing backend is controlled by the `gpg.format` configuration\n+variable, which defaults to `openpgp` for GPG signing. To sign tags\n+using other technologies like X.509 or SSH, set this variable to\n+`x509` or `ssh` respectively.\n+\n+You can also specify the path to the signing program for each\n+format. The `gpg.program` variable (or its synonym\n+`gpg.openpgp.program`) is used for the OpenPGP backend. For other\n+backends, the configuration is `gpg.<format>.program`, for example\n+`gpg.ssh.program` for SSH signing.\n+\n `pager.tag` is only respected when listing tags, i.e., when `-l` is\n used or implied. The default is to use a pager.\n-See linkgit:git-config[1].\n+\n+See linkgit:git-config[1] for more details and other configuration\n+variables.\n \n DISCUSSION\n ----------\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528093","messageId":"20251007122958.1089680-3-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251007122958.1089680-1-christian.couder@gmail.com","subject":"[PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-07T12:29:55Z","receivedAt":"2025-10-07T12:30:43Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"When the 'GPG' prereq is lazily tested, `mkdir \"$GNUPGHOME\"` could\nfail if the \"$GNUPGHOME\" directory already exists. This can happen if\nthe 'GPGSM' prereq has been lazily tested before as it uses\n`mkdir -p \"$GNUPGHOME\"`.\n\nTo allow the GPGSM prereq to appear before the GPG prereq in some\ntest scripts, let's use `mkdir -p \"$GNUPGHOME\"` when the 'GPG' prereq\nis lazily tested too.\n\nThis will be useful in a following commit.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/lib-gpg.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex 937b876bd0..743985efab 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -38,7 +38,7 @@ test_lazy_prereq GPG '\n \t\t# To export ownertrust:\n \t\t#\tgpg --homedir /tmp/gpghome --export-ownertrust \\\n \t\t#\t\t> lib-gpg/ownertrust\n-\t\tmkdir \"$GNUPGHOME\" &&\n+\t\tmkdir -p \"$GNUPGHOME\" &&\n \t\tchmod 0700 \"$GNUPGHOME\" &&\n \t\t(gpgconf --kill all || : ) &&\n \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528094","messageId":"20251007122958.1089680-4-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251007122958.1089680-1-christian.couder@gmail.com","subject":"[PATCH 3/5] t9350: properly count annotated tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-07T12:29:56Z","receivedAt":"2025-10-07T12:30:44Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"In t9350-fast-export.sh, these existing tests:\n\n  - 'fast-export | fast-import when main is tagged'\n  - 'cope with tagger-less tags'\n\nare checking the number of annotated tags in the test repo by comparing\nit with some hardcoded values.\n\nThis could be an issue if some new tests that have some prerequisites\nadd new annotated tags to the repo before these existing tests. When\nthe prerequisites would be satisfied, the number of annotated tags\nwould be different from when some prerequisites would not be satisfied.\n\nAs we are going to add new tests that add new annotated tags in a\nfollowing commit, let's properly count the number of annotated tag in\nthe repo by incrementing a counter each time a new annotated tag is\nadded, and then by comparing the number of annotated tags to the value\nof the counter when checking the number of annotated tags.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t9350-fast-export.sh | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 8f85c69d62..21ff26939c 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -35,6 +35,7 @@ test_expect_success 'setup' '\n \tgit commit -m sitzt file2 &&\n \ttest_tick &&\n \tgit tag -a -m valentin muss &&\n+\tANNOTATED_TAG_COUNT=1 &&\n \tgit merge -s ours main\n \n '\n@@ -229,7 +230,8 @@ EOF\n \n test_expect_success 'set up faked signed tag' '\n \n-\tgit fast-import <signed-tag-import\n+\tgit fast-import <signed-tag-import &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n \n '\n \n@@ -491,8 +493,9 @@ test_expect_success 'fast-export -C -C | fast-import' '\n test_expect_success 'fast-export | fast-import when main is tagged' '\n \n \tgit tag -m msg last &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 3\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT\n \n '\n \n@@ -506,12 +509,13 @@ test_expect_success 'cope with tagger-less tags' '\n \n \tTAG=$(git hash-object --literally -t tag -w tag-content) &&\n \tgit update-ref refs/tags/sonnenschein $TAG &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n \t! grep \"Unspecified Tagger\" output &&\n \tgit fast-export -C -C --signed-tags=strip --all \\\n \t\t--fake-missing-tagger > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n \tgrep \"Unspecified Tagger\" output\n \n '\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528095","messageId":"20251007122958.1089680-5-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251007122958.1089680-1-christian.couder@gmail.com","subject":"[PATCH 4/5] fast-export: handle all kinds of tag signatures","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-07T12:29:57Z","receivedAt":"2025-10-07T12:30:46Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Currently the handle_tag() function in \"builtin/fast-export.c\" searches\nonly for \"\\n-----BEGIN PGP SIGNATURE-----\\n\" in the tag message to find\na tag signature.\n\nThis doesn't handle all kinds of OpenPGP signatures as some can start\nwith \"-----BEGIN PGP MESSAGE-----\" too, and this doesn't handle SSH and\nX.509 signatures either as they use \"-----BEGIN SSH SIGNATURE-----\" and\n\"-----BEGIN SIGNED MESSAGE-----\" respectively.\n\nTo handle all these kinds of tag signatures supported by Git, let's use\nthe parse_signed_buffer() function to properly find signatures in tag\nmessages.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/fast-export.c  |  7 +++---\n t/t9350-fast-export.sh | 48 ++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 51 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex dc2486f9a8..7adbc55f0d 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)\n \n \t/* handle signed tags */\n \tif (message) {\n-\t\tconst char *signature = strstr(message,\n-\t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n-\t\tif (signature)\n+\t\tsize_t sig_offset = parse_signed_buffer(message, message_size);\n+\t\tif (sig_offset < message_size)\n \t\t\tswitch (signed_tag_mode) {\n \t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n@@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n \t\t\tcase SIGN_STRIP:\n-\t\t\t\tmessage_size = signature + 1 - message;\n+\t\t\t\tmessage_size = sig_offset;\n \t\t\t\tbreak;\n \t\t\t}\n \t}\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 21ff26939c..5a46608f65 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -279,6 +279,54 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\n '\n \n+test_expect_success GPGSM 'setup X.509 signed tag' '\n+\n+\ttest_config gpg.format x509 &&\n+\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n+\n+\tgit tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n+\n+'\n+\n+test_expect_success GPGSM 'signed-tags=verbatim with X.509' '\n+\n+\tgit fast-export --signed-tags=verbatim x509-signed > output &&\n+\ttest_grep \"SIGNED MESSAGE\" output\n+\n+'\n+\n+test_expect_success GPGSM 'signed-tags=strip with X.509' '\n+\n+\tgit fast-export --signed-tags=strip x509-signed > output &&\n+\ttest_grep ! \"SIGNED MESSAGE\" output\n+\n+'\n+\n+test_expect_success GPGSSH 'setup SSH signed tag' '\n+\n+\ttest_config gpg.format ssh &&\n+\ttest_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n+\n+\tgit tag -s -m \"SSH signed tag\" ssh-signed $(git rev-parse HEAD) &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n+\n+'\n+\n+test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '\n+\n+\tgit fast-export --signed-tags=verbatim ssh-signed > output &&\n+\ttest_grep \"SSH SIGNATURE\" output\n+\n+'\n+\n+test_expect_success GPGSSH 'signed-tags=strip with SSH' '\n+\n+\tgit fast-export --signed-tags=strip ssh-signed > output &&\n+\ttest_grep ! \"SSH SIGNATURE\" output\n+\n+'\n+\n test_expect_success GPG 'set up signed commit' '\n \n \t# Generate a commit with both \"gpgsig\" and \"encoding\" set, so\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528096","messageId":"20251007122958.1089680-6-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251007122958.1089680-1-christian.couder@gmail.com","subject":"[PATCH 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-07T12:29:58Z","receivedAt":"2025-10-07T12:30:46Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Recently, eaaddf5791 (fast-import: add '--signed-commits=<mode>'\noption, 2025-09-17) added support for controlling how signed commits\nare handled by `git fast-import`, but there is no option yet to\ndecide about signed tags.\n\nTo remediate that, let's add a '--signed-tags=<mode>' option to\n`git fast-import` too.\n\nWith this, both `git fast-export` and `git fast-import` have both\na '--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting\nthe same <mode>s.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-import.adoc |  5 ++\n builtin/fast-import.c              | 43 ++++++++++++++++\n t/meson.build                      |  1 +\n t/t9306-fast-import-signed-tags.sh | 80 ++++++++++++++++++++++++++++++\n 4 files changed, 129 insertions(+)\n create mode 100755 t/t9306-fast-import-signed-tags.sh\n\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 85ed7a7270..b74179a6c8 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -66,6 +66,11 @@ 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+\tSpecify how to handle signed tags.  Behaves in the same way\n+\tas the same option in linkgit:git-fast-export[1], except that\n+\tdefault is 'verbatim' (instead of 'abort').\n+\n --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed commits.  Behaves in the same way\n \tas the same option in linkgit:git-fast-export[1], except that\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 2010e78475..668c926db5 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -188,6 +188,7 @@ static int global_argc;\n static const char **global_argv;\n static const char *global_prefix;\n \n+static enum sign_mode signed_tag_mode = SIGN_VERBATIM;\n static enum sign_mode signed_commit_mode = SIGN_VERBATIM;\n \n /* Memory pools */\n@@ -2961,6 +2962,43 @@ 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+{\n+\tsize_t sig_offset = parse_signed_buffer(msg->buf, msg->len);\n+\n+\t/* If there is no signature, there is nothing to do. */\n+\tif (sig_offset >= msg->len)\n+\t\treturn;\n+\n+\tswitch (signed_tag_mode) {\n+\n+\t/* First, modes that don't change anything */\n+\tcase SIGN_ABORT:\n+\t\tdie(\"encountered signed tag; use \"\n+\t\t    \"--signed-tags=<mode> to handle it\");\n+\tcase SIGN_WARN_VERBATIM:\n+\t\twarning(_(\"importing a tag signature verbatim for tag '%s'\"), name);\n+\t\t\t/* fallthru */\n+\tcase SIGN_VERBATIM:\n+\t\t/* Nothing to do, the signature will be put into the imported tag. */\n+\t\tbreak;\n+\n+\t/* Second, modes that remove the signature */\n+\tcase SIGN_WARN_STRIP:\n+\t\twarning(_(\"stripping a tag signature for tag '%s'\"), name);\n+\t\t\t/* fallthru */\n+\tcase SIGN_STRIP:\n+\t\t/* Truncate the buffer to remove the signature */\n+\t\tstrbuf_setlen(msg, sig_offset);\n+\t\tbreak;\n+\n+\t/* Third, BUG */\n+\tdefault:\n+\t\tBUG(\"invalid signed_tag_mode value %d from tag '%s'\",\n+\t\t    signed_tag_mode, name);\n+\t}\n+}\n+\n static void parse_new_tag(const char *arg)\n {\n \tstatic struct strbuf msg = STRBUF_INIT;\n@@ -3024,6 +3062,8 @@ 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@@ -3544,6 +3584,9 @@ static int parse_one_option(const char *option)\n \t} else if (skip_prefix(option, \"signed-commits=\", &option)) {\n \t\tif (parse_sign_mode(option, &signed_commit_mode))\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))\n+\t\t\tusagef(_(\"unknown --signed-tags mode '%s'\"), option);\n \t} else if (!strcmp(option, \"quiet\")) {\n \t\tshow_stats = 0;\n \t\tquiet = 1;\ndiff --git a/t/meson.build b/t/meson.build\nindex 11376b9e25..cb8c2b4b30 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -1036,6 +1036,7 @@ integration_tests = [\n   't9303-fast-import-compression.sh',\n   't9304-fast-import-marks.sh',\n   't9305-fast-import-signatures.sh',\n+  't9306-fast-import-signed-tags.sh',\n   't9350-fast-export.sh',\n   't9351-fast-export-anonymize.sh',\n   't9400-git-cvsserver-server.sh',\ndiff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh\nnew file mode 100755\nindex 0000000000..363619e7d1\n--- /dev/null\n+++ b/t/t9306-fast-import-signed-tags.sh\n@@ -0,0 +1,80 @@\n+#!/bin/sh\n+\n+test_description='git fast-import --signed-tags=<mode>'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n+\n+test_expect_success 'set up unsigned initial commit and import repo' '\n+\ttest_commit first &&\n+\tgit init new\n+'\n+\n+test_expect_success 'import no signed tag with --signed-tags=abort' '\n+\tgit fast-export --signed-tags=verbatim >output &&\n+\tgit -C new fast-import --quiet --signed-tags=abort <output\n+'\n+\n+test_expect_success GPG 'set up OpenPGP signed tag' '\n+\tgit tag -s -m \"OpenPGP signed tag\" openpgp-signed first &&\n+\tOPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&\n+\tgit fast-export --signed-tags=verbatim openpgp-signed >output\n+'\n+\n+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '\n+\ttest_must_fail git -C new fast-import --quiet --signed-tags=abort <output\n+'\n+\n+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '\n+\tgit -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&\n+\ttest $OPENPGP_SIGNED = $IMPORTED &&\n+\ttest_must_be_empty log\n+'\n+\n+test_expect_success GPGSM 'setup X.509 signed tag' '\n+\ttest_config gpg.format x509 &&\n+\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n+\n+\tgit tag -s -m \"X.509 signed tag\" x509-signed first &&\n+\tX509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&\n+\tgit fast-export --signed-tags=verbatim x509-signed >output\n+'\n+\n+test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '\n+\tgit -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&\n+\ttest_grep \"stripping a tag signature for tag '\\''x509-signed'\\''\" log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&\n+\ttest $X509_SIGNED != $IMPORTED &&\n+\tgit -C new cat-file -p x509-signed >out &&\n+\ttest_grep ! \"SIGNED MESSAGE\" out\n+'\n+\n+test_expect_success GPGSSH 'setup SSH signed tag' '\n+\ttest_config gpg.format ssh &&\n+\ttest_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n+\n+\tgit tag -s -m \"SSH signed tag\" ssh-signed first &&\n+\tSSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&\n+\tgit fast-export --signed-tags=verbatim ssh-signed >output\n+'\n+\n+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '\n+\tgit -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&\n+\ttest_grep \"importing a tag signature verbatim for tag '\\''ssh-signed'\\''\" log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n+\ttest $SSH_SIGNED = $IMPORTED\n+'\n+\n+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '\n+\tgit -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&\n+\ttest_must_be_empty log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n+\ttest $SSH_SIGNED != $IMPORTED &&\n+\tgit -C new cat-file -p ssh-signed >out &&\n+\ttest_grep ! \"SSH SIGNATURE\" out\n+'\n+\n+test_done\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528211","messageId":"aOYPRKoexRtYUDsh@pks.im","threadId":"64261","inReplyTo":"20251007122958.1089680-2-christian.couder@gmail.com","subject":"Re: [PATCH 1/5] doc: git-tag: stop focussing on GPG signed tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T07:14:12Z","receivedAt":"2025-10-08T07:14:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 02:29:54PM +0200, Christian Couder wrote:\n> diff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc\n> index a4b1c0ec05..9117754ffb 100644\n> --- a/Documentation/git-tag.adoc\n> +++ b/Documentation/git-tag.adoc\n> @@ -3,7 +3,7 @@ git-tag(1)\n>  \n>  NAME\n>  ----\n> -git-tag - Create, list, delete or verify a tag object signed with GPG\n> +git-tag - Create, list, delete or verify tags\n\nThis is an obvious improvement.\n\n> @@ -38,17 +38,18 @@ and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n>  Otherwise, a tag reference that points directly at the given object\n>  (i.e., a lightweight tag) is created.\n>  \n> -A GnuPG signed tag object will be created when `-s` or `-u\n> -<key-id>` is used.  When `-u <key-id>` is not used, the\n> -committer identity for the current user is used to find the\n> -GnuPG key for signing. \tThe configuration variable `gpg.program`\n> -is used to specify custom GnuPG binary.\n> +A cryptographically signed tag object will be created when `-s` or\n> +`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is\n> +controlled by the `gpg.format` configuration variable, defaulting to\n> +OpenPGP. When `-u <key-id>` is not used, the committer identity for\n> +the current user is used to find the key for signing. The\n> +configuration variable `gpg.program` is used to specify a custom\n> +signing binary.\n>  \n>  Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n>  tags; they contain a creation date, the tagger name and e-mail, a\n> -tagging message, and an optional GnuPG signature. Whereas a\n> -\"lightweight\" tag is simply a name for an object (usually a commit\n> -object).\n> +tagging message, and an optional signature. Whereas a \"lightweight\"\n\nNit: let's rather say \"cryptographic signature\" here.\n\n> +tag is simply a name for an object (usually a commit object).\n>  \n>  Annotated tags are meant for release while lightweight tags are meant\n>  for private or temporary object labels. For this reason, some git\n> @@ -64,10 +65,12 @@ OPTIONS\n>  \n>  -s::\n>  --sign::\n> -\tMake a GPG-signed tag, using the default e-mail address's key.\n> -\tThe default behavior of tag GPG-signing is controlled by `tag.gpgSign`\n> -\tconfiguration variable if it exists, or disabled otherwise.\n> -\tSee linkgit:git-config[1].\n> +\tMake a signed tag, using the default signing key. The signing\n\nSame here, let's say \"cryptographically signed tag\".\n\n> @@ -75,7 +78,9 @@ OPTIONS\n>  \n>  -u <key-id>::\n>  --local-user=<key-id>::\n> -\tMake a GPG-signed tag, using the given key.\n> +\tMake a signed tag using the given key. The format of the\n\nSame.\n\n> +\t<key-id> and the backend used depend on the `gpg.format`\n> +\tconfiguration variable. See linkgit:git-config[1].\n>  \n>  -f::\n>  --force::\n> @@ -87,7 +92,7 @@ OPTIONS\n>  \n>  -v::\n>  --verify::\n> -\tVerify the GPG signature of the given tag names.\n> +\tVerify the signature of the given tag names.\n\nSame.\n\n> @@ -236,12 +241,25 @@ it in the repository configuration as follows:\n>  \n>  -------------------------------------\n>  [user]\n> -    signingKey = <gpg-key-id>\n> +    signingKey = <key-id>\n>  -------------------------------------\n>  \n> +The signing backend is controlled by the `gpg.format` configuration\n> +variable, which defaults to `openpgp` for GPG signing. To sign tags\n> +using other technologies like X.509 or SSH, set this variable to\n> +`x509` or `ssh` respectively.\n> +\n\nIt might make sense to use a bulleted list here to list the different\navailable formats. On the other hand, we could just as well refer to\ngit-config(1) so that we don't have to repeat any of the information\nhere, but instead have it at a central place.\n\nThat might not be worth it though. In the end there aren't too many\ndifferent commands that write signed objects.\n\nOverall this change makes a lot of sense to me, thanks!\n\nPatrick\n"},{"id":"528212","messageId":"aOYPTKG9t4ZB_Mbi@pks.im","threadId":"64261","inReplyTo":"20251007122958.1089680-3-christian.couder@gmail.com","subject":"Re: [PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T07:14:20Z","receivedAt":"2025-10-08T07:14:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 02:29:55PM +0200, Christian Couder wrote:\n> diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\n> index 937b876bd0..743985efab 100644\n> --- a/t/lib-gpg.sh\n> +++ b/t/lib-gpg.sh\n> @@ -38,7 +38,7 @@ test_lazy_prereq GPG '\n>  \t\t# To export ownertrust:\n>  \t\t#\tgpg --homedir /tmp/gpghome --export-ownertrust \\\n>  \t\t#\t\t> lib-gpg/ownertrust\n> -\t\tmkdir \"$GNUPGHOME\" &&\n> +\t\tmkdir -p \"$GNUPGHOME\" &&\n>  \t\tchmod 0700 \"$GNUPGHOME\" &&\n>  \t\t(gpgconf --kill all || : ) &&\n>  \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n\nOkay. I wonder why we even have to create the directory manually. We\ndon't do it in the GPGSM prereq either, as gpgsm seems to handle this\nfor us. Doesn't `gpg --homedir ... --import` create the home directory\nin a similar way?\n\nPatrick\n"},{"id":"528213","messageId":"aOYPUyKJPFyfKD46@pks.im","threadId":"64261","inReplyTo":"20251007122958.1089680-4-christian.couder@gmail.com","subject":"Re: [PATCH 3/5] t9350: properly count annotated tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T07:14:27Z","receivedAt":"2025-10-08T07:14:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 02:29:56PM +0200, Christian Couder wrote:\n> In t9350-fast-export.sh, these existing tests:\n> \n>   - 'fast-export | fast-import when main is tagged'\n>   - 'cope with tagger-less tags'\n> \n> are checking the number of annotated tags in the test repo by comparing\n> it with some hardcoded values.\n> \n> This could be an issue if some new tests that have some prerequisites\n> add new annotated tags to the repo before these existing tests. When\n> the prerequisites would be satisfied, the number of annotated tags\n> would be different from when some prerequisites would not be satisfied.\n> \n> As we are going to add new tests that add new annotated tags in a\n> following commit, let's properly count the number of annotated tag in\n> the repo by incrementing a counter each time a new annotated tag is\n> added, and then by comparing the number of annotated tags to the value\n> of the counter when checking the number of annotated tags.\n\nHm, okay. I think having tests interdepend on one another is bad test\ndesign in the first place, but it's not a new problem you create. An\nalternative solution could of course be to change the new test so that\nit works in a standalone repository, or to add it towards the end of the\ntest suite.\n\nHave you considered these alternatives?\n\nPatrick\n"},{"id":"528214","messageId":"aOYPWvdE4VnL8T7z@pks.im","threadId":"64261","inReplyTo":"20251007122958.1089680-5-christian.couder@gmail.com","subject":"Re: [PATCH 4/5] fast-export: handle all kinds of tag signatures","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T07:14:34Z","receivedAt":"2025-10-08T07:14:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 02:29:57PM +0200, Christian Couder wrote:\n> diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> index dc2486f9a8..7adbc55f0d 100644\n> --- a/builtin/fast-export.c\n> +++ b/builtin/fast-export.c\n> @@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)\n>  \n>  \t/* handle signed tags */\n>  \tif (message) {\n> -\t\tconst char *signature = strstr(message,\n> -\t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n> -\t\tif (signature)\n> +\t\tsize_t sig_offset = parse_signed_buffer(message, message_size);\n> +\t\tif (sig_offset < message_size)\n\nYup. The function either returns `message_size` in case there is no\nsignature, or it returns the offset at which the signature starts.\n\n>  \t\t\tswitch (signed_tag_mode) {\n>  \t\t\tcase SIGN_ABORT:\n>  \t\t\t\tdie(\"encountered signed tag %s; use \"\n\nI was afraid at first that we're now open-coding all these different\nsignature formats. But this implementation makes me quite happy, as we\neven remove the existing check instead of using a central function.\nNice.\n\n> @@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)\n>  \t\t\t\t\toid_to_hex(&tag->object.oid));\n>  \t\t\t\t/* fallthru */\n>  \t\t\tcase SIGN_STRIP:\n> -\t\t\t\tmessage_size = signature + 1 - message;\n> +\t\t\t\tmessage_size = sig_offset;\n>  \t\t\t\tbreak;\n>  \t\t\t}\n>  \t}\n\nMakes sense.\n\n> diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> index 21ff26939c..5a46608f65 100755\n> --- a/t/t9350-fast-export.sh\n> +++ b/t/t9350-fast-export.sh\n> @@ -279,6 +279,54 @@ test_expect_success 'signed-tags=warn-strip' '\n>  \ttest -s err\n>  '\n>  \n> +test_expect_success GPGSM 'setup X.509 signed tag' '\n> +\n> +\ttest_config gpg.format x509 &&\n> +\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n> +\n> +\tgit tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n> +\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n> +\n> +'\n\nNit for this test and all of the below ones: our modern style does not\nhave empty lines at the beginning and end of a test case.\n\nPatrick\n"},{"id":"528215","messageId":"aOYPYEk5sT6b1kuS@pks.im","threadId":"64261","inReplyTo":"20251007122958.1089680-6-christian.couder@gmail.com","subject":"Re: [PATCH 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T07:14:40Z","receivedAt":"2025-10-08T07:14:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 02:29:58PM +0200, Christian Couder wrote:\n> diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\n> index 85ed7a7270..b74179a6c8 100644\n> --- a/Documentation/git-fast-import.adoc\n> +++ b/Documentation/git-fast-import.adoc\n> @@ -66,6 +66,11 @@ 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> +\tSpecify how to handle signed tags.  Behaves in the same way\n> +\tas the same option in linkgit:git-fast-export[1], except that\n> +\tdefault is 'verbatim' (instead of 'abort').\n> +\n\nNit: I would've ordered this after \"--signed-commits\", mostly so that\nthese two are ordered alphabetically.\n\n>  --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n>  \tSpecify how to handle signed commits.  Behaves in the same way\n>  \tas the same option in linkgit:git-fast-export[1], except that\n> diff --git a/builtin/fast-import.c b/builtin/fast-import.c\n> index 2010e78475..668c926db5 100644\n> --- a/builtin/fast-import.c\n> +++ b/builtin/fast-import.c\n> @@ -2961,6 +2962,43 @@ 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> +{\n> +\tsize_t sig_offset = parse_signed_buffer(msg->buf, msg->len);\n> +\n> +\t/* If there is no signature, there is nothing to do. */\n> +\tif (sig_offset >= msg->len)\n> +\t\treturn;\n> +\n> +\tswitch (signed_tag_mode) {\n> +\n> +\t/* First, modes that don't change anything */\n> +\tcase SIGN_ABORT:\n> +\t\tdie(\"encountered signed tag; use \"\n> +\t\t    \"--signed-tags=<mode> to handle it\");\n\nThis message needs to be marked for translation.\n\n> +\tcase SIGN_WARN_VERBATIM:\n> +\t\twarning(_(\"importing a tag signature verbatim for tag '%s'\"), name);\n> +\t\t\t/* fallthru */\n\nThis comment is misindented.\n\n> +\tcase SIGN_VERBATIM:\n> +\t\t/* Nothing to do, the signature will be put into the imported tag. */\n> +\t\tbreak;\n> +\n> +\t/* Second, modes that remove the signature */\n> +\tcase SIGN_WARN_STRIP:\n> +\t\twarning(_(\"stripping a tag signature for tag '%s'\"), name);\n> +\t\t\t/* fallthru */\n\nSame here, the comment is misindented.\n\n> +\tcase SIGN_STRIP:\n> +\t\t/* Truncate the buffer to remove the signature */\n> +\t\tstrbuf_setlen(msg, sig_offset);\n> +\t\tbreak;\n\nI'm not familiar with the signature format, so it's probably a dumb\nquestion: does the signature always extend until the end of the tag\nmessage? Doesn't the tag message come after it?\n\nPatrick\n"},{"id":"528228","messageId":"CAP8UFD2VRfZuaycCdWt4kpVpRv_UhNHdzqJ2vL8uyKJusUttnA@mail.gmail.com","threadId":"64261","inReplyTo":"aOYPTKG9t4ZB_Mbi@pks.im","subject":"Re: [PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-08T09:42:20Z","receivedAt":"2025-10-08T09:42:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Oct 07, 2025 at 02:29:55PM +0200, Christian Couder wrote:\n> > diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\n> > index 937b876bd0..743985efab 100644\n> > --- a/t/lib-gpg.sh\n> > +++ b/t/lib-gpg.sh\n> > @@ -38,7 +38,7 @@ test_lazy_prereq GPG '\n> >               # To export ownertrust:\n> >               #       gpg --homedir /tmp/gpghome --export-ownertrust \\\n> >               #               > lib-gpg/ownertrust\n> > -             mkdir \"$GNUPGHOME\" &&\n> > +             mkdir -p \"$GNUPGHOME\" &&\n> >               chmod 0700 \"$GNUPGHOME\" &&\n> >               (gpgconf --kill all || : ) &&\n> >               gpg --homedir \"${GNUPGHOME}\" --import \\\n>\n> Okay. I wonder why we even have to create the directory manually. We\n> don't do it in the GPGSM prereq either, as gpgsm seems to handle this\n> for us.\n\nYeah, the GPGSSH prereq does `mkdir -p \"$GNUPGHOME\"`, but not the GPGSM prereq.\n\n> Doesn't `gpg --homedir ... --import` create the home directory\n> in a similar way?\n\nI am not sure. It might depend on the gpg version. Or maybe gpgsm\n does it but not gpg. I will check.\n"},{"id":"528229","messageId":"CAP8UFD0UJt+L9Ri4VyWJ-1M4Si2q=i5xG_=a315G9m1NFvXnQA@mail.gmail.com","threadId":"64261","inReplyTo":"aOYPRKoexRtYUDsh@pks.im","subject":"Re: [PATCH 1/5] doc: git-tag: stop focussing on GPG signed tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-08T09:52:44Z","receivedAt":"2025-10-08T09:52:58Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Oct 07, 2025 at 02:29:54PM +0200, Christian Couder wrote:\n> > diff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc\n> > index a4b1c0ec05..9117754ffb 100644\n> > --- a/Documentation/git-tag.adoc\n> > +++ b/Documentation/git-tag.adoc\n> > @@ -3,7 +3,7 @@ git-tag(1)\n> >\n> >  NAME\n> >  ----\n> > -git-tag - Create, list, delete or verify a tag object signed with GPG\n> > +git-tag - Create, list, delete or verify tags\n>\n> This is an obvious improvement.\n\n[...]\n\n> >  Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n> >  tags; they contain a creation date, the tagger name and e-mail, a\n> > -tagging message, and an optional GnuPG signature. Whereas a\n> > -\"lightweight\" tag is simply a name for an object (usually a commit\n> > -object).\n> > +tagging message, and an optional signature. Whereas a \"lightweight\"\n>\n> Nit: let's rather say \"cryptographic signature\" here.\n\nOK, I will make this change in V2.\n\n> > +tag is simply a name for an object (usually a commit object).\n> >\n> >  Annotated tags are meant for release while lightweight tags are meant\n> >  for private or temporary object labels. For this reason, some git\n> > @@ -64,10 +65,12 @@ OPTIONS\n> >\n> >  -s::\n> >  --sign::\n> > -     Make a GPG-signed tag, using the default e-mail address's key.\n> > -     The default behavior of tag GPG-signing is controlled by `tag.gpgSign`\n> > -     configuration variable if it exists, or disabled otherwise.\n> > -     See linkgit:git-config[1].\n> > +     Make a signed tag, using the default signing key. The signing\n>\n> Same here, let's say \"cryptographically signed tag\".\n>\n> > @@ -75,7 +78,9 @@ OPTIONS\n> >\n> >  -u <key-id>::\n> >  --local-user=<key-id>::\n> > -     Make a GPG-signed tag, using the given key.\n> > +     Make a signed tag using the given key. The format of the\n>\n> Same.\n>\n> > +     <key-id> and the backend used depend on the `gpg.format`\n> > +     configuration variable. See linkgit:git-config[1].\n> >\n> >  -f::\n> >  --force::\n> > @@ -87,7 +92,7 @@ OPTIONS\n> >\n> >  -v::\n> >  --verify::\n> > -     Verify the GPG signature of the given tag names.\n> > +     Verify the signature of the given tag names.\n>\n> Same.\n\nIt's a bit cumbersome to have to say \"cryptographic\" or\n\"cryptographically\" everywhere though. Maybe saying it a few times at\nthe beginning is enough?\n\n> > @@ -236,12 +241,25 @@ it in the repository configuration as follows:\n> >\n> >  -------------------------------------\n> >  [user]\n> > -    signingKey = <gpg-key-id>\n> > +    signingKey = <key-id>\n> >  -------------------------------------\n> >\n> > +The signing backend is controlled by the `gpg.format` configuration\n> > +variable, which defaults to `openpgp` for GPG signing. To sign tags\n> > +using other technologies like X.509 or SSH, set this variable to\n> > +`x509` or `ssh` respectively.\n> > +\n>\n> It might make sense to use a bulleted list here to list the different\n> available formats.\n\nWhat should we say about each format though?\n\n> On the other hand, we could just as well refer to\n> git-config(1) so that we don't have to repeat any of the information\n> here, but instead have it at a central place.\n>\n> That might not be worth it though. In the end there aren't too many\n> different commands that write signed objects.\n\nI think this CONFIGURATION section should talk only briefly about the\nmost important config options and refer to the git-config(1) doc for\ndetails and less important config options. So I am not sure what you\nsuggest exactly about this.\n\n> Overall this change makes a lot of sense to me, thanks!\n\nThanks.\n"},{"id":"528231","messageId":"CAP8UFD2dncBPHMH6oKUvT29iV2-qPg60i5Lt9wEsPCj2JYcwJQ@mail.gmail.com","threadId":"64261","inReplyTo":"aOYPUyKJPFyfKD46@pks.im","subject":"Re: [PATCH 3/5] t9350: properly count annotated tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-08T10:00:21Z","receivedAt":"2025-10-08T10:00:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Oct 07, 2025 at 02:29:56PM +0200, Christian Couder wrote:\n> > In t9350-fast-export.sh, these existing tests:\n> >\n> >   - 'fast-export | fast-import when main is tagged'\n> >   - 'cope with tagger-less tags'\n> >\n> > are checking the number of annotated tags in the test repo by comparing\n> > it with some hardcoded values.\n> >\n> > This could be an issue if some new tests that have some prerequisites\n> > add new annotated tags to the repo before these existing tests. When\n> > the prerequisites would be satisfied, the number of annotated tags\n> > would be different from when some prerequisites would not be satisfied.\n> >\n> > As we are going to add new tests that add new annotated tags in a\n> > following commit, let's properly count the number of annotated tag in\n> > the repo by incrementing a counter each time a new annotated tag is\n> > added, and then by comparing the number of annotated tags to the value\n> > of the counter when checking the number of annotated tags.\n>\n> Hm, okay. I think having tests interdepend on one another is bad test\n> design in the first place, but it's not a new problem you create. An\n> alternative solution could of course be to change the new test so that\n> it works in a standalone repository, or to add it towards the end of the\n> test suite.\n>\n> Have you considered these alternatives?\n\nYes, I have considered them, but I think those workarounds could make\nthe technical debt worse.\n\nFor example if I move those tests towards the end of the test script\nor in another separate test script, then someone might wonder later\nwhy they are not at the logical place where they should be. They would\nthen move them and realize that it creates problems with subsequent\ntests. This would waste time.\n\nSo I think it's a good thing to make the interdependency clearly\nvisible instead. This is a bit ugly, but it shows the existing\ntechnical debt instead of hiding it.\n"},{"id":"528232","messageId":"CAP8UFD2HxcJjaWfBWX3bo_LM4gSQMmFZZiYS0Yqp1zV3yZPvag@mail.gmail.com","threadId":"64261","inReplyTo":"aOYPWvdE4VnL8T7z@pks.im","subject":"Re: [PATCH 4/5] fast-export: handle all kinds of tag signatures","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-08T10:02:59Z","receivedAt":"2025-10-08T10:03:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n\n> > diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> > index 21ff26939c..5a46608f65 100755\n> > --- a/t/t9350-fast-export.sh\n> > +++ b/t/t9350-fast-export.sh\n> > @@ -279,6 +279,54 @@ test_expect_success 'signed-tags=warn-strip' '\n> >       test -s err\n> >  '\n> >\n> > +test_expect_success GPGSM 'setup X.509 signed tag' '\n> > +\n> > +     test_config gpg.format x509 &&\n> > +     test_config user.signingkey $GIT_COMMITTER_EMAIL &&\n> > +\n> > +     git tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n> > +     ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n> > +\n> > +'\n>\n> Nit for this test and all of the below ones: our modern style does not\n> have empty lines at the beginning and end of a test case.\n\nOK, I will use the modern style (instead of the existing style in this\ntest script) in V2.\n"},{"id":"528233","messageId":"CAP8UFD0E+5K1yL1rj5jXVMX9hQyoA_sH0f=fUP6aCj==TtfAbQ@mail.gmail.com","threadId":"64261","inReplyTo":"aOYPYEk5sT6b1kuS@pks.im","subject":"Re: [PATCH 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-08T10:50:53Z","receivedAt":"2025-10-08T10:51:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Oct 07, 2025 at 02:29:58PM +0200, Christian Couder wrote:\n> > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\n> > index 85ed7a7270..b74179a6c8 100644\n> > --- a/Documentation/git-fast-import.adoc\n> > +++ b/Documentation/git-fast-import.adoc\n> > @@ -66,6 +66,11 @@ 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> > +     Specify how to handle signed tags.  Behaves in the same way\n> > +     as the same option in linkgit:git-fast-export[1], except that\n> > +     default is 'verbatim' (instead of 'abort').\n> > +\n>\n> Nit: I would've ordered this after \"--signed-commits\", mostly so that\n> these two are ordered alphabetically.\n\nIn the fast-export doc --signed-tags is before --signed-commits. Also\nin the previous patch series Junio mentioned that historically signed\ntags came before signed commits. And the other options are not sorted\nalphabetically.\n\n> >  --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> >       Specify how to handle signed commits.  Behaves in the same way\n> >       as the same option in linkgit:git-fast-export[1], except that\n> > diff --git a/builtin/fast-import.c b/builtin/fast-import.c\n> > index 2010e78475..668c926db5 100644\n> > --- a/builtin/fast-import.c\n> > +++ b/builtin/fast-import.c\n> > @@ -2961,6 +2962,43 @@ static void parse_new_commit(const char *arg)\n> >       b->last_commit = object_count_by_type[OBJ_COMMIT];\n> >  }\n> >\n> > +static void handle_tag_signature(struct strbuf *msg, const char *name)\n> > +{\n> > +     size_t sig_offset = parse_signed_buffer(msg->buf, msg->len);\n> > +\n> > +     /* If there is no signature, there is nothing to do. */\n> > +     if (sig_offset >= msg->len)\n> > +             return;\n> > +\n> > +     switch (signed_tag_mode) {\n> > +\n> > +     /* First, modes that don't change anything */\n> > +     case SIGN_ABORT:\n> > +             die(\"encountered signed tag; use \"\n> > +                 \"--signed-tags=<mode> to handle it\");\n>\n> This message needs to be marked for translation.\n\nYeah, I will fix it in V2.\n\n> > +     case SIGN_WARN_VERBATIM:\n> > +             warning(_(\"importing a tag signature verbatim for tag '%s'\"), name);\n> > +                     /* fallthru */\n>\n> This comment is misindented.\n\nWill fix it in V2. Same with other misindented comments.\n\n> > +     case SIGN_STRIP:\n> > +             /* Truncate the buffer to remove the signature */\n> > +             strbuf_setlen(msg, sig_offset);\n> > +             break;\n>\n> I'm not familiar with the signature format, so it's probably a dumb\n> question: does the signature always extend until the end of the tag\n> message? Doesn't the tag message come after it?\n\nUsers can add anything in a tag message, including signatures created\nhowever they want and copy-pasted there, followed by whatever content\nthey want. I don't think we need to take care of those signatures,\nexcept perhaps to warn in our docs that Git could mistake them with\nthe one Git creates.\n\nWhen Git itself signs a tag, it appends the signature to the tag\nmessage. See do_sign() in \"builtin/tag.c\" for more details. It looks\nlike 2 signatures can be created in \"compat\" mode, but the compat\nsignature is added into an object header, not appended to the tag\nmessage.\n\nSo I think this is the right thing to do and relatively safe.\n"},{"id":"528236","messageId":"aOZPd0VqdulySIGi@pks.im","threadId":"64261","inReplyTo":"CAP8UFD0UJt+L9Ri4VyWJ-1M4Si2q=i5xG_=a315G9m1NFvXnQA@mail.gmail.com","subject":"Re: [PATCH 1/5] doc: git-tag: stop focussing on GPG signed tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T11:48:07Z","receivedAt":"2025-10-08T11:48:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 08, 2025 at 11:52:44AM +0200, Christian Couder wrote:\n> On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > On Tue, Oct 07, 2025 at 02:29:54PM +0200, Christian Couder wrote:\n> > > diff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc\n> > > index a4b1c0ec05..9117754ffb 100644\n> > > --- a/Documentation/git-tag.adoc\n> > > +++ b/Documentation/git-tag.adoc\n> > > @@ -236,12 +241,25 @@ it in the repository configuration as follows:\n> > >\n> > >  -------------------------------------\n> > >  [user]\n> > > -    signingKey = <gpg-key-id>\n> > > +    signingKey = <key-id>\n> > >  -------------------------------------\n> > >\n> > > +The signing backend is controlled by the `gpg.format` configuration\n> > > +variable, which defaults to `openpgp` for GPG signing. To sign tags\n> > > +using other technologies like X.509 or SSH, set this variable to\n> > > +`x509` or `ssh` respectively.\n> > > +\n> >\n> > It might make sense to use a bulleted list here to list the different\n> > available formats.\n> \n> What should we say about each format though?\n> \n> > On the other hand, we could just as well refer to\n> > git-config(1) so that we don't have to repeat any of the information\n> > here, but instead have it at a central place.\n> >\n> > That might not be worth it though. In the end there aren't too many\n> > different commands that write signed objects.\n> \n> I think this CONFIGURATION section should talk only briefly about the\n> most important config options and refer to the git-config(1) doc for\n> details and less important config options. So I am not sure what you\n> suggest exactly about this.\n\nYeah, I'm fine with referring to git-config(1). I mostly want to avoid\nthat we have N locations that we need to update every time something\nchanges here, as those are bound to become stale.\n\nMaybe a solution would be to only point out the config keys without\ngoing into much detail what the respective values are? In that case we\nwoulds imply refer to git-config(1) and call it a day.\n\nPatrick\n"},{"id":"528238","messageId":"aOZQwBZo90Gjn85m@pks.im","threadId":"64261","inReplyTo":"CAP8UFD0E+5K1yL1rj5jXVMX9hQyoA_sH0f=fUP6aCj==TtfAbQ@mail.gmail.com","subject":"Re: [PATCH 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T11:53:36Z","receivedAt":"2025-10-08T11:53:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 08, 2025 at 12:50:53PM +0200, Christian Couder wrote:\n> On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n> >\n> > On Tue, Oct 07, 2025 at 02:29:58PM +0200, Christian Couder wrote:\n> > > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\n> > > index 85ed7a7270..b74179a6c8 100644\n> > > --- a/Documentation/git-fast-import.adoc\n> > > +++ b/Documentation/git-fast-import.adoc\n> > > @@ -66,6 +66,11 @@ 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> > > +     Specify how to handle signed tags.  Behaves in the same way\n> > > +     as the same option in linkgit:git-fast-export[1], except that\n> > > +     default is 'verbatim' (instead of 'abort').\n> > > +\n> >\n> > Nit: I would've ordered this after \"--signed-commits\", mostly so that\n> > these two are ordered alphabetically.\n> \n> In the fast-export doc --signed-tags is before --signed-commits. Also\n> in the previous patch series Junio mentioned that historically signed\n> tags came before signed commits. And the other options are not sorted\n> alphabetically.\n\nOkay, makes sense.\n\n> > > +     case SIGN_STRIP:\n> > > +             /* Truncate the buffer to remove the signature */\n> > > +             strbuf_setlen(msg, sig_offset);\n> > > +             break;\n> >\n> > I'm not familiar with the signature format, so it's probably a dumb\n> > question: does the signature always extend until the end of the tag\n> > message? Doesn't the tag message come after it?\n> \n> Users can add anything in a tag message, including signatures created\n> however they want and copy-pasted there, followed by whatever content\n> they want. I don't think we need to take care of those signatures,\n> except perhaps to warn in our docs that Git could mistake them with\n> the one Git creates.\n> \n> When Git itself signs a tag, it appends the signature to the tag\n> message. See do_sign() in \"builtin/tag.c\" for more details. It looks\n> like 2 signatures can be created in \"compat\" mode, but the compat\n> signature is added into an object header, not appended to the tag\n> message.\n> \n> So I think this is the right thing to do and relatively safe.\n\nOkay, thanks for clarifying.\n\nPatrick\n"},{"id":"528347","messageId":"871pncdfrw.fsf@gmail.com","threadId":"64261","inReplyTo":"CAP8UFD2VRfZuaycCdWt4kpVpRv_UhNHdzqJ2vL8uyKJusUttnA@mail.gmail.com","subject":"Re: [PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Collin Funk","fromEmail":"collin.funk1@gmail.com","sentAt":"2025-10-09T01:29:07Z","receivedAt":"2025-10-09T01:29:09Z","isPatch":true,"sender":{"key":"collin.funk1@gmail.com","avatar":"https://avatars.githubusercontent.com/u/65689063?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n>> Okay. I wonder why we even have to create the directory manually. We\n>> don't do it in the GPGSM prereq either, as gpgsm seems to handle this\n>> for us.\n>\n> Yeah, the GPGSSH prereq does `mkdir -p \"$GNUPGHOME\"`, but not the GPGSM prereq.\n>\n>> Doesn't `gpg --homedir ... --import` create the home directory\n>> in a similar way?\n>\n> I am not sure. It might depend on the gpg version. Or maybe gpgsm\n>  does it but not gpg. I will check.\n\nIf $GNUPGHOME or --homedir is the default (usually ~/.gnupg) gets\ncreated by 'gpg' and 'gpgsm':\n\n    $ ls ~/.gnupg\n    ls: cannot access '/root/.gnupg': No such file or directory\n    $ gpgsm\n    gpgsm: directory '/root/.gnupg' created\n    gpgsm: invalid command (there is no implicit command)\n    $ rm -rf ~/.gnupg && gpg\n    gpg: directory '/root/.gnupg' created\n    [...]\n\nIf it is not the default then it will not be created:\n\n    $ GNUPGHOME=$HOME/test gpgsm\n    gpgsm: keyblock resource '/root/test/pubring.kbx': No such file or directory\n    $ GNUPGHOME=$HOME/test gpg\n    gpg: keyblock resource '/root/test/pubring.kbx': No such file or directory\n\nCollin\n"},{"id":"528348","messageId":"aOcfzgxOwGemReNm@teonanacatl.net","threadId":"64261","inReplyTo":"871pncdfrw.fsf@gmail.com","subject":"Re: [PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2025-10-09T02:37:02Z","receivedAt":"2025-10-09T02:37:05Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"Collin Funk wrote:\n> Christian Couder <christian.couder@gmail.com> writes:\n> \n>>> Okay. I wonder why we even have to create the directory manually. We\n>>> don't do it in the GPGSM prereq either, as gpgsm seems to handle this\n>>> for us.\n>>\n>> Yeah, the GPGSSH prereq does `mkdir -p \"$GNUPGHOME\"`, but not the GPGSM prereq.\n>>\n>>> Doesn't `gpg --homedir ... --import` create the home directory\n>>> in a similar way?\n>>\n>> I am not sure. It might depend on the gpg version. Or maybe gpgsm\n>>  does it but not gpg. I will check.\n> \n> If $GNUPGHOME or --homedir is the default (usually ~/.gnupg) gets\n> created by 'gpg' and 'gpgsm':\n> \n>     $ ls ~/.gnupg\n>     ls: cannot access '/root/.gnupg': No such file or directory\n>     $ gpgsm\n>     gpgsm: directory '/root/.gnupg' created\n>     gpgsm: invalid command (there is no implicit command)\n>     $ rm -rf ~/.gnupg && gpg\n>     gpg: directory '/root/.gnupg' created\n>     [...]\n> \n> If it is not the default then it will not be created:\n> \n>     $ GNUPGHOME=$HOME/test gpgsm\n>     gpgsm: keyblock resource '/root/test/pubring.kbx': No such file or directory\n>     $ GNUPGHOME=$HOME/test gpg\n>     gpg: keyblock resource '/root/test/pubring.kbx': No such file or directory\n> \n> Collin\n> \n\nI sent a series long ago to fix this issue¹, but it wasn't\npicked up.\n\nFixing the issue exposes broken tests which use the gpg2\nprereq.  That breakage turns up in our CI and other build\nenvironments, like Fedora's, but I was never able to\nreliably trigger it locally and track down what was broken\nabout those test.\n\nI believe I asked about it again a few months later and it\ndid not gain any attention.\n\nI simply apply the patches locally and then disable those\ntests -- tests which don't run reliably are not worth\nrunning IMO. :)\n\n¹ <20240703153738.916469-1-tmz@pobox.com>\n\n-- \nTodd\n"},{"id":"528381","messageId":"20251009122457.1273701-1-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251007122958.1089680-1-christian.couder@gmail.com","subject":"[PATCH v2 0/5] fast-import: start controlling how tag signatures are handled","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:24:52Z","receivedAt":"2025-10-09T12:25:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Introduction\n------------\n\nTools like `git-filter-repo` should be able to control how tag\nsignatures are handled when regenerating repository content after it\nhas been filtered. For this purpose, they need a way for `git\nfast-import` to control how tag signatures are handled.\n\nA previous series [1] added a '--signed-commits=<mode>' option to `git\nfast-import` to control how commit signatures are handled, so this is\nadding a similar '--signed-tags=<mode>' for tag signatures.\n\nFor now this new option behaves in a very similar way as the option\nwith the same name that already exists in `git fast-export`.\nEspecially it supports exactly the same <mode>s and the same aliases\nfor these modes. For example \"ignore\" is a synonym for \"verbatim\".\n\nThis way, both `git fast-export` and `git fast-import` have both a\n'--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting the\nsame <mode>s.\n\nIn the future I want to implement new <mode>s like \"strip-if-invalid\",\n\"re-sign\", \"re-sign-if-invalid\" in `git fast-import` for both tag and\ncommit signatures. These might be a bit more complex, so for now I\nprefer to start with the simple modes.\n\n[1] https://lore.kernel.org/git/20250917181427.3193500-1-christian.couder@gmail.com/\n\nNote about the different patches\n--------------------------------\n\nPatch 1/5 (doc: git-tag: stop focussing on GPG signed tags) is a\ndocumentation update for `git tag`. It could go in a separate series\nor be dropped altogether, but while working on this I thought that it\nwould be a good thing to do, as the doc is quite outdated.\n\nPatches 2/5, 3/5 and 4/5 are preparatory patches for the main one\nwhich is patch 5/5 (fast-import: add '--signed-tags=<mode>' option).\n\nI wanted '--signed-tags=<mode>' to work for all kinds of signature in\ntags (OpenPGP, X.509 and SSH) but soon realized that the\n'--signed-tags=<mode>' option of `git fast-export` worked only for\nOpenPGP signatures, so I fixed that issue in patch 4/5 (fast-export:\nhandle all kinds of tag signatures).\n\nWhile working on the tests in patch 4/5, I found a few things to\nimprove that could belong to other patches so that's how I came up\nwith patches 2/5 and 3/5.\n\nChanges since v1\n----------------\n\nThanks to Patrick Steinhardt, Todd Zullinger and Collin Funk who\nreviewed or commented on the v1.\n\n- In patch 1/5, in the commit message:\n\n  - \"focussing\" and \"focussed\" have been replaced with \"focusing\" and\n    \"focused\" respectively as the former is UK Eglish while the latter\n    is US English,\n\n  - the missing \"section\" word has been added.\n\n- In patch 1/5, in the git-tag doc:\n\n  - \"cryptographic\" or \"cryptographically\" has been added to number of\n    places,\n\n  - the changes to the CONFIGURATION section have been shortened by\n    not mentioning the supported signing backend (X.509 and SSH) other\n    than OpenPGP, and by referring to git-config(1) more.\n\n- In patch 2/5, the approach taken is now the one previously used by\n  Todd Zullinger in:\n\n  https://lore.kernel.org/git/20240703153738.916469-2-tmz@pobox.com/\n\n  so this patch looks like a completely different patch in the range\n  diff.\n\n- In patch 3/5, in the commit message:\n\n  - t9350-fast-export.sh has been quoted,\n\n  - some explanations about alternative solutions that have been\n    considered have been added.\n\n- In patch 4/5, the added tests are now written in a modern style,\n  instead of the old style used elsewhere in the script.\n\n- In patch 5/5, a die() message has been marked for translation and\n  some \"/* fallthru */\" comments have been properly indented.\n\nCI tests\n--------\n\nThey have all passed except again one on Windows where\n\"t8020-last-modified.sh\" failed. See:\n\nhttps://github.com/chriscool/git/actions/runs/18373100224\n\nRange diff since v1\n-------------------\n\n1:  05d0b86de6 ! 1:  eb65af631d doc: git-tag: stop focussing on GPG signed tags\n    @@ Metadata\n     Author: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Commit message ##\n    -    doc: git-tag: stop focussing on GPG signed tags\n    +    doc: git-tag: stop focusing on GPG signed tags\n     \n    -    It looks like the documentation of `git tag` is focussed a bit too\n    +    It looks like the documentation of `git tag` is focused a bit too\n         much on GPG signed tags.\n     \n         This starts with the \"NAME\" section where the command is described\n    @@ Commit message\n         This goes on in the \"OPTIONS\" section too, especially about the `-s`\n         and `-u <key-id>` options.\n     \n    -    The \"CONFIGURATION\" also doesn't talk about how to configure the\n    -    command to work with X.509 and SSH signatures.\n    +    The \"CONFIGURATION\" section also doesn't talk about how to configure\n    +    the command to work with X.509 and SSH signatures.\n     \n         Let's rework all that to make sure users have a more accurate and\n         balanced view of what the command can do.\n     \n    +    Helped-by: Patrick Steinhardt <ps@pks.im>\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Documentation/git-tag.adoc ##\n    @@ Documentation/git-tag.adoc: and `-a`, `-s`, and `-u <key-id>` are absent, `-a` i\n      Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n      tags; they contain a creation date, the tagger name and e-mail, a\n     -tagging message, and an optional GnuPG signature. Whereas a\n    --\"lightweight\" tag is simply a name for an object (usually a commit\n    --object).\n    -+tagging message, and an optional signature. Whereas a \"lightweight\"\n    -+tag is simply a name for an object (usually a commit object).\n    ++tagging message, and an optional cryptographic signature. Whereas a\n    + \"lightweight\" tag is simply a name for an object (usually a commit\n    + object).\n      \n    - Annotated tags are meant for release while lightweight tags are meant\n    - for private or temporary object labels. For this reason, some git\n     @@ Documentation/git-tag.adoc: OPTIONS\n      \n      -s::\n    @@ Documentation/git-tag.adoc: OPTIONS\n     -\tThe default behavior of tag GPG-signing is controlled by `tag.gpgSign`\n     -\tconfiguration variable if it exists, or disabled otherwise.\n     -\tSee linkgit:git-config[1].\n    -+\tMake a signed tag, using the default signing key. The signing\n    -+\tbackend used depends on the `gpg.format` configuration\n    -+\tvariable. The default key is determined by the backend. For\n    -+\tGPG, it's based on the committer's email address, while for\n    -+\tSSH it may be a specific key file or agent identity. See\n    -+\tlinkgit:git-config[1].\n    ++\tMake a cryptographically signed tag, using the default signing\n    ++\tkey. The signing backend used depends on the `gpg.format`\n    ++\tconfiguration variable. The default key is determined by the\n    ++\tbackend. For GPG, it's based on the committer's email address,\n    ++\twhile for SSH it may be a specific key file or agent\n    ++\tidentity. See linkgit:git-config[1].\n      \n      --no-sign::\n      \tOverride `tag.gpgSign` configuration variable that is\n    @@ Documentation/git-tag.adoc: OPTIONS\n      -u <key-id>::\n      --local-user=<key-id>::\n     -\tMake a GPG-signed tag, using the given key.\n    -+\tMake a signed tag using the given key. The format of the\n    -+\t<key-id> and the backend used depend on the `gpg.format`\n    -+\tconfiguration variable. See linkgit:git-config[1].\n    ++\tMake a cryptographically signed tag using the given key. The\n    ++\tformat of the <key-id> and the backend used depend on the\n    ++\t`gpg.format` configuration variable. See\n    ++\tlinkgit:git-config[1].\n      \n      -f::\n      --force::\n    @@ Documentation/git-tag.adoc: it in the repository configuration as follows:\n     +    signingKey = <key-id>\n      -------------------------------------\n      \n    -+The signing backend is controlled by the `gpg.format` configuration\n    -+variable, which defaults to `openpgp` for GPG signing. To sign tags\n    -+using other technologies like X.509 or SSH, set this variable to\n    -+`x509` or `ssh` respectively.\n    ++The signing backend can be chosen via the `gpg.format` configuration\n    ++variable, which defaults to `openpgp`. See linkgit:git-config[1]\n    ++for a list of other supported formats.\n     +\n    -+You can also specify the path to the signing program for each\n    -+format. The `gpg.program` variable (or its synonym\n    -+`gpg.openpgp.program`) is used for the OpenPGP backend. For other\n    -+backends, the configuration is `gpg.<format>.program`, for example\n    -+`gpg.ssh.program` for SSH signing.\n    ++The path to the program used for each signing backend can be specified\n    ++with the `gpg.<format>.program` configuration variable. For the\n    ++`openpgp` backend, `gpg.program` can be used as a synonym for\n    ++`gpg.openpgp.program`. See linkgit:git-config[1] for details.\n     +\n      `pager.tag` is only respected when listing tags, i.e., when `-l` is\n      used or implied. The default is to use a pager.\n2:  61a1116542 < -:  ---------- lib-gpg: allow tests with the GPGSM prereq first\n-:  ---------- > 2:  640204ef26 lib-gpg: allow tests with GPGSM or GPGSSH prereq first\n3:  b2b703ae9d ! 3:  8f788bafe1 t9350: properly count annotated tags\n    @@ Metadata\n      ## Commit message ##\n         t9350: properly count annotated tags\n     \n    -    In t9350-fast-export.sh, these existing tests:\n    +    In \"t9350-fast-export.sh\", these existing tests:\n     \n           - 'fast-export | fast-import when main is tagged'\n           - 'cope with tagger-less tags'\n    @@ Commit message\n         added, and then by comparing the number of annotated tags to the value\n         of the counter when checking the number of annotated tags.\n     \n    +    This is a bit ugly, but it makes it explicit that some tests are\n    +    interdependent. Alternative solutions, like moving the new tests to\n    +    the end of the script, were considered, but were rejected because they\n    +    would instead hide the technical debt and could confuse developers in\n    +    the future.\n    +\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## t/t9350-fast-export.sh ##\n4:  b51e904f90 ! 4:  d62a43905c fast-export: handle all kinds of tag signatures\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n      '\n      \n     +test_expect_success GPGSM 'setup X.509 signed tag' '\n    -+\n     +\ttest_config gpg.format x509 &&\n     +\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n     +\n     +\tgit tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n     +\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n    -+\n     +'\n     +\n     +test_expect_success GPGSM 'signed-tags=verbatim with X.509' '\n    -+\n     +\tgit fast-export --signed-tags=verbatim x509-signed > output &&\n     +\ttest_grep \"SIGNED MESSAGE\" output\n    -+\n     +'\n     +\n     +test_expect_success GPGSM 'signed-tags=strip with X.509' '\n    -+\n     +\tgit fast-export --signed-tags=strip x509-signed > output &&\n     +\ttest_grep ! \"SIGNED MESSAGE\" output\n    -+\n     +'\n     +\n     +test_expect_success GPGSSH 'setup SSH signed tag' '\n    -+\n     +\ttest_config gpg.format ssh &&\n     +\ttest_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n     +\n     +\tgit tag -s -m \"SSH signed tag\" ssh-signed $(git rev-parse HEAD) &&\n     +\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n    -+\n     +'\n     +\n     +test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '\n    -+\n     +\tgit fast-export --signed-tags=verbatim ssh-signed > output &&\n     +\ttest_grep \"SSH SIGNATURE\" output\n    -+\n     +'\n     +\n     +test_expect_success GPGSSH 'signed-tags=strip with SSH' '\n    -+\n     +\tgit fast-export --signed-tags=strip ssh-signed > output &&\n     +\ttest_grep ! \"SSH SIGNATURE\" output\n    -+\n     +'\n     +\n      test_expect_success GPG 'set up signed commit' '\n5:  6987fc0bae ! 5:  9094f37b46 fast-import: add '--signed-tags=<mode>' option\n    @@ builtin/fast-import.c: static void parse_new_commit(const char *arg)\n     +\n     +\t/* First, modes that don't change anything */\n     +\tcase SIGN_ABORT:\n    -+\t\tdie(\"encountered signed tag; use \"\n    -+\t\t    \"--signed-tags=<mode> to handle it\");\n    ++\t\tdie(_(\"encountered signed tag; use \"\n    ++\t\t      \"--signed-tags=<mode> to handle it\"));\n     +\tcase SIGN_WARN_VERBATIM:\n     +\t\twarning(_(\"importing a tag signature verbatim for tag '%s'\"), name);\n    -+\t\t\t/* fallthru */\n    ++\t\t/* fallthru */\n     +\tcase SIGN_VERBATIM:\n     +\t\t/* Nothing to do, the signature will be put into the imported tag. */\n     +\t\tbreak;\n    @@ builtin/fast-import.c: static void parse_new_commit(const char *arg)\n     +\t/* Second, modes that remove the signature */\n     +\tcase SIGN_WARN_STRIP:\n     +\t\twarning(_(\"stripping a tag signature for tag '%s'\"), name);\n    -+\t\t\t/* fallthru */\n    ++\t\t/* fallthru */\n     +\tcase SIGN_STRIP:\n     +\t\t/* Truncate the buffer to remove the signature */\n     +\t\tstrbuf_setlen(msg, sig_offset);\n\n\n\nChristian Couder (5):\n  doc: git-tag: stop focusing on GPG signed tags\n  lib-gpg: allow tests with GPGSM or GPGSSH prereq first\n  t9350: properly count annotated tags\n  fast-export: handle all kinds of tag signatures\n  fast-import: add '--signed-tags=<mode>' option\n\n Documentation/git-fast-import.adoc |  5 ++\n Documentation/git-tag.adoc         | 48 ++++++++++++------\n builtin/fast-export.c              |  7 ++-\n builtin/fast-import.c              | 43 ++++++++++++++++\n t/lib-gpg.sh                       | 24 +++++++--\n t/meson.build                      |  1 +\n t/t9306-fast-import-signed-tags.sh | 80 ++++++++++++++++++++++++++++++\n t/t9350-fast-export.sh             | 48 ++++++++++++++++--\n 8 files changed, 229 insertions(+), 27 deletions(-)\n create mode 100755 t/t9306-fast-import-signed-tags.sh\n\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528382","messageId":"20251009122457.1273701-2-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251009122457.1273701-1-christian.couder@gmail.com","subject":"[PATCH v2 1/5] doc: git-tag: stop focusing on GPG signed tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:24:53Z","receivedAt":"2025-10-09T12:25:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"It looks like the documentation of `git tag` is focused a bit too\nmuch on GPG signed tags.\n\nThis starts with the \"NAME\" section where the command is described\nwith:\n\n\"Create, list, delete or verify a tag object signed with GPG\"\n\nwhile for example `git branch` is described with simply:\n\n\"List, create, or delete branches\"\n\nThis could give the false impression that `git tag` only works with\ntag objects, not with lightweight tags, and that tag objects are\nalways GPG signed.\n\nIn the \"DESCRIPTION\" section, it looks like only \"GnuPG signed tag\nobjects\" can be created by the `-s` and `-u <key-id>` options, and it\nseems `gpg.program` can only specify a \"custom GnuPG binary\".\n\nThis goes on in the \"OPTIONS\" section too, especially about the `-s`\nand `-u <key-id>` options.\n\nThe \"CONFIGURATION\" section also doesn't talk about how to configure\nthe command to work with X.509 and SSH signatures.\n\nLet's rework all that to make sure users have a more accurate and\nbalanced view of what the command can do.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-tag.adoc | 48 ++++++++++++++++++++++++++------------\n 1 file changed, 33 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc\nindex a4b1c0ec05..3519e5b9b2 100644\n--- a/Documentation/git-tag.adoc\n+++ b/Documentation/git-tag.adoc\n@@ -3,7 +3,7 @@ git-tag(1)\n \n NAME\n ----\n-git-tag - Create, list, delete or verify a tag object signed with GPG\n+git-tag - Create, list, delete or verify tags\n \n \n SYNOPSIS\n@@ -38,15 +38,17 @@ and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n \n-A GnuPG signed tag object will be created when `-s` or `-u\n-<key-id>` is used.  When `-u <key-id>` is not used, the\n-committer identity for the current user is used to find the\n-GnuPG key for signing. \tThe configuration variable `gpg.program`\n-is used to specify custom GnuPG binary.\n+A cryptographically signed tag object will be created when `-s` or\n+`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is\n+controlled by the `gpg.format` configuration variable, defaulting to\n+OpenPGP. When `-u <key-id>` is not used, the committer identity for\n+the current user is used to find the key for signing. The\n+configuration variable `gpg.program` is used to specify a custom\n+signing binary.\n \n Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n tags; they contain a creation date, the tagger name and e-mail, a\n-tagging message, and an optional GnuPG signature. Whereas a\n+tagging message, and an optional cryptographic signature. Whereas a\n \"lightweight\" tag is simply a name for an object (usually a commit\n object).\n \n@@ -64,10 +66,12 @@ OPTIONS\n \n -s::\n --sign::\n-\tMake a GPG-signed tag, using the default e-mail address's key.\n-\tThe default behavior of tag GPG-signing is controlled by `tag.gpgSign`\n-\tconfiguration variable if it exists, or disabled otherwise.\n-\tSee linkgit:git-config[1].\n+\tMake a cryptographically signed tag, using the default signing\n+\tkey. The signing backend used depends on the `gpg.format`\n+\tconfiguration variable. The default key is determined by the\n+\tbackend. For GPG, it's based on the committer's email address,\n+\twhile for SSH it may be a specific key file or agent\n+\tidentity. See linkgit:git-config[1].\n \n --no-sign::\n \tOverride `tag.gpgSign` configuration variable that is\n@@ -75,7 +79,10 @@ OPTIONS\n \n -u <key-id>::\n --local-user=<key-id>::\n-\tMake a GPG-signed tag, using the given key.\n+\tMake a cryptographically signed tag using the given key. The\n+\tformat of the <key-id> and the backend used depend on the\n+\t`gpg.format` configuration variable. See\n+\tlinkgit:git-config[1].\n \n -f::\n --force::\n@@ -87,7 +94,7 @@ OPTIONS\n \n -v::\n --verify::\n-\tVerify the GPG signature of the given tag names.\n+\tVerify the signature of the given tag names.\n \n -n<num>::\n \t<num> specifies how many lines from the annotation, if any,\n@@ -236,12 +243,23 @@ it in the repository configuration as follows:\n \n -------------------------------------\n [user]\n-    signingKey = <gpg-key-id>\n+    signingKey = <key-id>\n -------------------------------------\n \n+The signing backend can be chosen via the `gpg.format` configuration\n+variable, which defaults to `openpgp`. See linkgit:git-config[1]\n+for a list of other supported formats.\n+\n+The path to the program used for each signing backend can be specified\n+with the `gpg.<format>.program` configuration variable. For the\n+`openpgp` backend, `gpg.program` can be used as a synonym for\n+`gpg.openpgp.program`. See linkgit:git-config[1] for details.\n+\n `pager.tag` is only respected when listing tags, i.e., when `-l` is\n used or implied. The default is to use a pager.\n-See linkgit:git-config[1].\n+\n+See linkgit:git-config[1] for more details and other configuration\n+variables.\n \n DISCUSSION\n ----------\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528383","messageId":"20251009122457.1273701-3-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251009122457.1273701-1-christian.couder@gmail.com","subject":"[PATCH v2 2/5] lib-gpg: allow tests with GPGSM or GPGSSH prereq first","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:24:54Z","receivedAt":"2025-10-09T12:25:16Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"When the 'GPG' prereq is lazily tested, `mkdir \"$GNUPGHOME\"` could\nfail if the \"$GNUPGHOME\" directory already exists. This can happen if\nthe 'GPGSM' or the 'GPGSSH' prereq has been lazily tested before as they\nalready create \"$GNUPGHOME\".\n\nTo allow the GPGSM or the GPGSSH prereq to appear before the GPG prereq\nin some test scripts, let's refactor the creation and setup of the\n\"$GNUPGHOME\"` directory in a new prepare_gnupghome() function that uses\n`mkdir -p \"$GNUPGHOME\"`.\n\nThis will be useful in a following commit.\n\nUnfortunately the new prepare_gnupghome() function cannot be used when\nlazily testing the GPG2 prereq, because that would expose existing,\nhidden bugs in \"t1016-compatObjectFormat.sh\", so let's just document\nthat with a NEEDSWORK comment.\n\nHelped-by: Todd Zullinger <tmz@pobox.com>\nHelped-by: Collin Funk <collin.funk1@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/lib-gpg.sh | 24 ++++++++++++++++++++----\n 1 file changed, 20 insertions(+), 4 deletions(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex 937b876bd0..b99ae39a06 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -9,6 +9,16 @@\n GNUPGHOME=\"$(pwd)/gpghome\"\n export GNUPGHOME\n \n+# All the \"test_lazy_prereq GPG*\" below should use\n+# `prepare_gnupghome()` either directly or through a call to\n+# `test_have_prereq GPG*`. That's because `gpg` and `gpgsm`\n+# only create the directory specified using \"$GNUPGHOME\" or\n+# `--homedir` if it's the default (usually \"~/.gnupg\").\n+prepare_gnupghome() {\n+\tmkdir -p \"$GNUPGHOME\" &&\n+\tchmod 0700 \"$GNUPGHOME\"\n+}\n+\n test_lazy_prereq GPG '\n \tgpg_version=$(gpg --version 2>&1)\n \ttest $? != 127 || exit 1\n@@ -38,8 +48,7 @@ test_lazy_prereq GPG '\n \t\t# To export ownertrust:\n \t\t#\tgpg --homedir /tmp/gpghome --export-ownertrust \\\n \t\t#\t\t> lib-gpg/ownertrust\n-\t\tmkdir \"$GNUPGHOME\" &&\n-\t\tchmod 0700 \"$GNUPGHOME\" &&\n+\t\tprepare_gnupghome &&\n \t\t(gpgconf --kill all || : ) &&\n \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n \t\t\t\"$TEST_DIRECTORY\"/lib-gpg/keyring.gpg &&\n@@ -63,6 +72,14 @@ test_lazy_prereq GPG2 '\n \t\t;;\n \t*)\n \t\t(gpgconf --kill all || : ) &&\n+\n+\t\t# NEEDSWORK: prepare_gnupghome() should definitely be\n+\t\t# called here, but it looks like it exposes a\n+\t\t# pre-existing, hidden bug by allowing some tests in\n+\t\t# t1016-compatObjectFormat.sh to run instead of being\n+\t\t# skipped. See:\n+\t\t# https://lore.kernel.org/git/ZoV8b2RvYxLOotSJ@teonanacatl.net/\n+\n \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n \t\t\t\"$TEST_DIRECTORY\"/lib-gpg/keyring.gpg &&\n \t\tgpg --homedir \"${GNUPGHOME}\" --import-ownertrust \\\n@@ -132,8 +149,7 @@ test_lazy_prereq GPGSSH '\n \ttest $? = 0 || exit 1;\n \n \t# Setup some keys and an allowed signers file\n-\tmkdir -p \"${GNUPGHOME}\" &&\n-\tchmod 0700 \"${GNUPGHOME}\" &&\n+\tprepare_gnupghome &&\n \t(setfacl -k \"${GNUPGHOME}\" 2>/dev/null || true) &&\n \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_PRIMARY}\" >/dev/null &&\n \tssh-keygen -t rsa -b 2048 -N \"\" -C \"git rsa2048 key\" -f \"${GPGSSH_KEY_SECONDARY}\" >/dev/null &&\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528384","messageId":"20251009122457.1273701-4-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251009122457.1273701-1-christian.couder@gmail.com","subject":"[PATCH v2 3/5] t9350: properly count annotated tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:24:55Z","receivedAt":"2025-10-09T12:25:18Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"In \"t9350-fast-export.sh\", these existing tests:\n\n  - 'fast-export | fast-import when main is tagged'\n  - 'cope with tagger-less tags'\n\nare checking the number of annotated tags in the test repo by comparing\nit with some hardcoded values.\n\nThis could be an issue if some new tests that have some prerequisites\nadd new annotated tags to the repo before these existing tests. When\nthe prerequisites would be satisfied, the number of annotated tags\nwould be different from when some prerequisites would not be satisfied.\n\nAs we are going to add new tests that add new annotated tags in a\nfollowing commit, let's properly count the number of annotated tag in\nthe repo by incrementing a counter each time a new annotated tag is\nadded, and then by comparing the number of annotated tags to the value\nof the counter when checking the number of annotated tags.\n\nThis is a bit ugly, but it makes it explicit that some tests are\ninterdependent. Alternative solutions, like moving the new tests to\nthe end of the script, were considered, but were rejected because they\nwould instead hide the technical debt and could confuse developers in\nthe future.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t9350-fast-export.sh | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 8f85c69d62..21ff26939c 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -35,6 +35,7 @@ test_expect_success 'setup' '\n \tgit commit -m sitzt file2 &&\n \ttest_tick &&\n \tgit tag -a -m valentin muss &&\n+\tANNOTATED_TAG_COUNT=1 &&\n \tgit merge -s ours main\n \n '\n@@ -229,7 +230,8 @@ EOF\n \n test_expect_success 'set up faked signed tag' '\n \n-\tgit fast-import <signed-tag-import\n+\tgit fast-import <signed-tag-import &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n \n '\n \n@@ -491,8 +493,9 @@ test_expect_success 'fast-export -C -C | fast-import' '\n test_expect_success 'fast-export | fast-import when main is tagged' '\n \n \tgit tag -m msg last &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 3\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT\n \n '\n \n@@ -506,12 +509,13 @@ test_expect_success 'cope with tagger-less tags' '\n \n \tTAG=$(git hash-object --literally -t tag -w tag-content) &&\n \tgit update-ref refs/tags/sonnenschein $TAG &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n \t! grep \"Unspecified Tagger\" output &&\n \tgit fast-export -C -C --signed-tags=strip --all \\\n \t\t--fake-missing-tagger > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n \tgrep \"Unspecified Tagger\" output\n \n '\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528385","messageId":"20251009122457.1273701-5-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251009122457.1273701-1-christian.couder@gmail.com","subject":"[PATCH v2 4/5] fast-export: handle all kinds of tag signatures","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:24:56Z","receivedAt":"2025-10-09T12:25:19Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Currently the handle_tag() function in \"builtin/fast-export.c\" searches\nonly for \"\\n-----BEGIN PGP SIGNATURE-----\\n\" in the tag message to find\na tag signature.\n\nThis doesn't handle all kinds of OpenPGP signatures as some can start\nwith \"-----BEGIN PGP MESSAGE-----\" too, and this doesn't handle SSH and\nX.509 signatures either as they use \"-----BEGIN SSH SIGNATURE-----\" and\n\"-----BEGIN SIGNED MESSAGE-----\" respectively.\n\nTo handle all these kinds of tag signatures supported by Git, let's use\nthe parse_signed_buffer() function to properly find signatures in tag\nmessages.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/fast-export.c  |  7 +++----\n t/t9350-fast-export.sh | 36 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 39 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex dc2486f9a8..7adbc55f0d 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)\n \n \t/* handle signed tags */\n \tif (message) {\n-\t\tconst char *signature = strstr(message,\n-\t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n-\t\tif (signature)\n+\t\tsize_t sig_offset = parse_signed_buffer(message, message_size);\n+\t\tif (sig_offset < message_size)\n \t\t\tswitch (signed_tag_mode) {\n \t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n@@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n \t\t\tcase SIGN_STRIP:\n-\t\t\t\tmessage_size = signature + 1 - message;\n+\t\t\t\tmessage_size = sig_offset;\n \t\t\t\tbreak;\n \t\t\t}\n \t}\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 21ff26939c..3d153a4805 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -279,6 +279,42 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\n '\n \n+test_expect_success GPGSM 'setup X.509 signed tag' '\n+\ttest_config gpg.format x509 &&\n+\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n+\n+\tgit tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n+'\n+\n+test_expect_success GPGSM 'signed-tags=verbatim with X.509' '\n+\tgit fast-export --signed-tags=verbatim x509-signed > output &&\n+\ttest_grep \"SIGNED MESSAGE\" output\n+'\n+\n+test_expect_success GPGSM 'signed-tags=strip with X.509' '\n+\tgit fast-export --signed-tags=strip x509-signed > output &&\n+\ttest_grep ! \"SIGNED MESSAGE\" output\n+'\n+\n+test_expect_success GPGSSH 'setup SSH signed tag' '\n+\ttest_config gpg.format ssh &&\n+\ttest_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n+\n+\tgit tag -s -m \"SSH signed tag\" ssh-signed $(git rev-parse HEAD) &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n+'\n+\n+test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '\n+\tgit fast-export --signed-tags=verbatim ssh-signed > output &&\n+\ttest_grep \"SSH SIGNATURE\" output\n+'\n+\n+test_expect_success GPGSSH 'signed-tags=strip with SSH' '\n+\tgit fast-export --signed-tags=strip ssh-signed > output &&\n+\ttest_grep ! \"SSH SIGNATURE\" output\n+'\n+\n test_expect_success GPG 'set up signed commit' '\n \n \t# Generate a commit with both \"gpgsig\" and \"encoding\" set, so\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528386","messageId":"20251009122457.1273701-6-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251009122457.1273701-1-christian.couder@gmail.com","subject":"[PATCH v2 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:24:57Z","receivedAt":"2025-10-09T12:25:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Recently, eaaddf5791 (fast-import: add '--signed-commits=<mode>'\noption, 2025-09-17) added support for controlling how signed commits\nare handled by `git fast-import`, but there is no option yet to\ndecide about signed tags.\n\nTo remediate that, let's add a '--signed-tags=<mode>' option to\n`git fast-import` too.\n\nWith this, both `git fast-export` and `git fast-import` have both\na '--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting\nthe same <mode>s.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-import.adoc |  5 ++\n builtin/fast-import.c              | 43 ++++++++++++++++\n t/meson.build                      |  1 +\n t/t9306-fast-import-signed-tags.sh | 80 ++++++++++++++++++++++++++++++\n 4 files changed, 129 insertions(+)\n create mode 100755 t/t9306-fast-import-signed-tags.sh\n\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 85ed7a7270..b74179a6c8 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -66,6 +66,11 @@ 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+\tSpecify how to handle signed tags.  Behaves in the same way\n+\tas the same option in linkgit:git-fast-export[1], except that\n+\tdefault is 'verbatim' (instead of 'abort').\n+\n --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed commits.  Behaves in the same way\n \tas the same option in linkgit:git-fast-export[1], except that\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 2010e78475..60d6faa465 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -188,6 +188,7 @@ static int global_argc;\n static const char **global_argv;\n static const char *global_prefix;\n \n+static enum sign_mode signed_tag_mode = SIGN_VERBATIM;\n static enum sign_mode signed_commit_mode = SIGN_VERBATIM;\n \n /* Memory pools */\n@@ -2961,6 +2962,43 @@ 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+{\n+\tsize_t sig_offset = parse_signed_buffer(msg->buf, msg->len);\n+\n+\t/* If there is no signature, there is nothing to do. */\n+\tif (sig_offset >= msg->len)\n+\t\treturn;\n+\n+\tswitch (signed_tag_mode) {\n+\n+\t/* First, modes that don't change anything */\n+\tcase SIGN_ABORT:\n+\t\tdie(_(\"encountered signed tag; use \"\n+\t\t      \"--signed-tags=<mode> to handle it\"));\n+\tcase SIGN_WARN_VERBATIM:\n+\t\twarning(_(\"importing a tag signature verbatim for tag '%s'\"), name);\n+\t\t/* fallthru */\n+\tcase SIGN_VERBATIM:\n+\t\t/* Nothing to do, the signature will be put into the imported tag. */\n+\t\tbreak;\n+\n+\t/* Second, modes that remove the signature */\n+\tcase SIGN_WARN_STRIP:\n+\t\twarning(_(\"stripping a tag signature for tag '%s'\"), name);\n+\t\t/* fallthru */\n+\tcase SIGN_STRIP:\n+\t\t/* Truncate the buffer to remove the signature */\n+\t\tstrbuf_setlen(msg, sig_offset);\n+\t\tbreak;\n+\n+\t/* Third, BUG */\n+\tdefault:\n+\t\tBUG(\"invalid signed_tag_mode value %d from tag '%s'\",\n+\t\t    signed_tag_mode, name);\n+\t}\n+}\n+\n static void parse_new_tag(const char *arg)\n {\n \tstatic struct strbuf msg = STRBUF_INIT;\n@@ -3024,6 +3062,8 @@ 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@@ -3544,6 +3584,9 @@ static int parse_one_option(const char *option)\n \t} else if (skip_prefix(option, \"signed-commits=\", &option)) {\n \t\tif (parse_sign_mode(option, &signed_commit_mode))\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))\n+\t\t\tusagef(_(\"unknown --signed-tags mode '%s'\"), option);\n \t} else if (!strcmp(option, \"quiet\")) {\n \t\tshow_stats = 0;\n \t\tquiet = 1;\ndiff --git a/t/meson.build b/t/meson.build\nindex 11376b9e25..cb8c2b4b30 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -1036,6 +1036,7 @@ integration_tests = [\n   't9303-fast-import-compression.sh',\n   't9304-fast-import-marks.sh',\n   't9305-fast-import-signatures.sh',\n+  't9306-fast-import-signed-tags.sh',\n   't9350-fast-export.sh',\n   't9351-fast-export-anonymize.sh',\n   't9400-git-cvsserver-server.sh',\ndiff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh\nnew file mode 100755\nindex 0000000000..363619e7d1\n--- /dev/null\n+++ b/t/t9306-fast-import-signed-tags.sh\n@@ -0,0 +1,80 @@\n+#!/bin/sh\n+\n+test_description='git fast-import --signed-tags=<mode>'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n+\n+test_expect_success 'set up unsigned initial commit and import repo' '\n+\ttest_commit first &&\n+\tgit init new\n+'\n+\n+test_expect_success 'import no signed tag with --signed-tags=abort' '\n+\tgit fast-export --signed-tags=verbatim >output &&\n+\tgit -C new fast-import --quiet --signed-tags=abort <output\n+'\n+\n+test_expect_success GPG 'set up OpenPGP signed tag' '\n+\tgit tag -s -m \"OpenPGP signed tag\" openpgp-signed first &&\n+\tOPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&\n+\tgit fast-export --signed-tags=verbatim openpgp-signed >output\n+'\n+\n+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '\n+\ttest_must_fail git -C new fast-import --quiet --signed-tags=abort <output\n+'\n+\n+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '\n+\tgit -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&\n+\ttest $OPENPGP_SIGNED = $IMPORTED &&\n+\ttest_must_be_empty log\n+'\n+\n+test_expect_success GPGSM 'setup X.509 signed tag' '\n+\ttest_config gpg.format x509 &&\n+\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n+\n+\tgit tag -s -m \"X.509 signed tag\" x509-signed first &&\n+\tX509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&\n+\tgit fast-export --signed-tags=verbatim x509-signed >output\n+'\n+\n+test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '\n+\tgit -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&\n+\ttest_grep \"stripping a tag signature for tag '\\''x509-signed'\\''\" log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&\n+\ttest $X509_SIGNED != $IMPORTED &&\n+\tgit -C new cat-file -p x509-signed >out &&\n+\ttest_grep ! \"SIGNED MESSAGE\" out\n+'\n+\n+test_expect_success GPGSSH 'setup SSH signed tag' '\n+\ttest_config gpg.format ssh &&\n+\ttest_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n+\n+\tgit tag -s -m \"SSH signed tag\" ssh-signed first &&\n+\tSSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&\n+\tgit fast-export --signed-tags=verbatim ssh-signed >output\n+'\n+\n+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '\n+\tgit -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&\n+\ttest_grep \"importing a tag signature verbatim for tag '\\''ssh-signed'\\''\" log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n+\ttest $SSH_SIGNED = $IMPORTED\n+'\n+\n+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '\n+\tgit -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&\n+\ttest_must_be_empty log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n+\ttest $SSH_SIGNED != $IMPORTED &&\n+\tgit -C new cat-file -p ssh-signed >out &&\n+\ttest_grep ! \"SSH SIGNATURE\" out\n+'\n+\n+test_done\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528387","messageId":"CAP8UFD1RKL0PgkghQstNA=o2TzgATXWcqzfEwEs-KadyLKSK-g@mail.gmail.com","threadId":"64261","inReplyTo":"aOcfzgxOwGemReNm@teonanacatl.net","subject":"Re: [PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:29:09Z","receivedAt":"2025-10-09T12:29:23Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Oct 9, 2025 at 4:37 AM Todd Zullinger <tmz@pobox.com> wrote:\n\n> I sent a series long ago to fix this issueš, but it wasn't\n> picked up.\n>\n> Fixing the issue exposes broken tests which use the gpg2\n> prereq.  That breakage turns up in our CI and other build\n> environments, like Fedora's, but I was never able to\n> reliably trigger it locally and track down what was broken\n> about those test.\n>\n> I believe I asked about it again a few months later and it\n> did not gain any attention.\n>\n> I simply apply the patches locally and then disable those\n> tests -- tests which don't run reliably are not worth\n> running IMO. :)\n>\n> š <20240703153738.916469-1-tmz@pobox.com>\n\nThanks for mentioning it and sorry that your series didn't get more attention.\n\nI have just sent a v2 of my series with a patch based on your first\npatch. About the GPG2 prereq I added a NEEDSWORK comment but didn't\nchange the actual code to not trigger the test failures.\n"},{"id":"528388","messageId":"CAP8UFD1FOy1DZnhRCqxyPe9g8+S1yDMR9R=JstGNC4dtOQ1ntQ@mail.gmail.com","threadId":"64261","inReplyTo":"871pncdfrw.fsf@gmail.com","subject":"Re: [PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:30:23Z","receivedAt":"2025-10-09T12:30:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Oct 9, 2025 at 3:29 AM Collin Funk <collin.funk1@gmail.com> wrote:\n\n> > I am not sure. It might depend on the gpg version. Or maybe gpgsm\n> >  does it but not gpg. I will check.\n>\n> If $GNUPGHOME or --homedir is the default (usually ~/.gnupg) gets\n> created by 'gpg' and 'gpgsm':\n>\n>     $ ls ~/.gnupg\n>     ls: cannot access '/root/.gnupg': No such file or directory\n>     $ gpgsm\n>     gpgsm: directory '/root/.gnupg' created\n>     gpgsm: invalid command (there is no implicit command)\n>     $ rm -rf ~/.gnupg && gpg\n>     gpg: directory '/root/.gnupg' created\n>     [...]\n>\n> If it is not the default then it will not be created:\n>\n>     $ GNUPGHOME=$HOME/test gpgsm\n>     gpgsm: keyblock resource '/root/test/pubring.kbx': No such file or directory\n>     $ GNUPGHOME=$HOME/test gpg\n>     gpg: keyblock resource '/root/test/pubring.kbx': No such file or directory\n\nThanks for this useful information. I have added a code comment to\nexplain this in the v2 series I just sent.\n"},{"id":"528389","messageId":"CAP8UFD2g1usFU3fhV-8bdr8CCiipSOw=XuYGHrx3N8MUkVXjDQ@mail.gmail.com","threadId":"64261","inReplyTo":"aOYPWvdE4VnL8T7z@pks.im","subject":"Re: [PATCH 4/5] fast-export: handle all kinds of tag signatures","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-09T12:33:15Z","receivedAt":"2025-10-09T12:33:28Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Oct 8, 2025 at 11:21 AM Patrick Steinhardt <ps@pks.im> wrote:\n\n> > diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> > index 21ff26939c..5a46608f65 100755\n> > --- a/t/t9350-fast-export.sh\n> > +++ b/t/t9350-fast-export.sh\n> > @@ -279,6 +279,54 @@ test_expect_success 'signed-tags=warn-strip' '\n> >       test -s err\n> >  '\n> >\n> > +test_expect_success GPGSM 'setup X.509 signed tag' '\n> > +\n> > +     test_config gpg.format x509 &&\n> > +     test_config user.signingkey $GIT_COMMITTER_EMAIL &&\n> > +\n> > +     git tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n> > +     ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n> > +\n> > +'\n>\n> Nit for this test and all of the below ones: our modern style does not\n> have empty lines at the beginning and end of a test case.\n\nThanks. I think I have addressed all your comments like this one in\nthe v2 I just sent.\n"},{"id":"528396","messageId":"xmqqecrcdjml.fsf@gitster.g","threadId":"64261","inReplyTo":"aOcfzgxOwGemReNm@teonanacatl.net","subject":"Re: [PATCH 2/5] lib-gpg: allow tests with the GPGSM prereq first","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-09T18:18:10Z","receivedAt":"2025-10-09T18:18:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Todd Zullinger <tmz@pobox.com> writes:\n\n> I sent a series long ago to fix this issue¹, but it wasn't\n> picked up.\n>\n> Fixing the issue exposes broken tests which use the gpg2\n> prereq.  That breakage turns up in our CI and other build\n> environments, like Fedora's, but I was never able to\n> reliably trigger it locally and track down what was broken\n> about those test.\n>\n> I believe I asked about it again a few months later and it\n> did not gain any attention.\n>\n> I simply apply the patches locally and then disable those\n> tests -- tests which don't run reliably are not worth\n> running IMO. :)\n>\n> ¹ <20240703153738.916469-1-tmz@pobox.com>\n\nTrue, the archive shows that the two-patch series got no attention\nfrom anybody, it seems.  Perhaps nobody was looking at the list at\naround the beginning of July last year?\n\nLet me pick it up belatedly, but I'd appreciate an extra sets or two\nof eyes while the issue is fresh in our minds.\n\nThanks.\n\n"},{"id":"528406","messageId":"xmqqsefrdaic.fsf@gitster.g","threadId":"64261","inReplyTo":"20251009122457.1273701-1-christian.couder@gmail.com","subject":"Re: [PATCH v2 0/5] fast-import: start controlling how tag signatures are handled","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-09T21:35:07Z","receivedAt":"2025-10-09T21:35:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> Changes since v1\n> ----------------\n>\n> Thanks to Patrick Steinhardt, Todd Zullinger and Collin Funk who\n> reviewed or commented on the v1.\n>\n> - In patch 1/5, in the commit message:\n>\n>   - \"focussing\" and \"focussed\" have been replaced with \"focusing\" and\n>     \"focused\" respectively as the former is UK Eglish while the latter\n>     is US English,\n>\n>   - the missing \"section\" word has been added.\n>\n> - In patch 1/5, in the git-tag doc:\n>\n>   - \"cryptographic\" or \"cryptographically\" has been added to number of\n>     places,\n>\n>   - the changes to the CONFIGURATION section have been shortened by\n>     not mentioning the supported signing backend (X.509 and SSH) other\n>     than OpenPGP, and by referring to git-config(1) more.\n>\n> - In patch 2/5, the approach taken is now the one previously used by\n>   Todd Zullinger in:\n>\n>   https://lore.kernel.org/git/20240703153738.916469-2-tmz@pobox.com/\n>\n>   so this patch looks like a completely different patch in the range\n>   diff.\n>\n> - In patch 3/5, in the commit message:\n>\n>   - t9350-fast-export.sh has been quoted,\n>\n>   - some explanations about alternative solutions that have been\n>     considered have been added.\n>\n> - In patch 4/5, the added tests are now written in a modern style,\n>   instead of the old style used elsewhere in the script.\n>\n> - In patch 5/5, a die() message has been marked for translation and\n>   some \"/* fallthru */\" comments have been properly indented.\n\nLooking good.  Thanks, will queue.\n"},{"id":"528449","messageId":"xmqqsefrblk5.fsf@gitster.g","threadId":"64261","inReplyTo":"20251009122457.1273701-2-christian.couder@gmail.com","subject":"Re: [PATCH v2 1/5] doc: git-tag: stop focusing on GPG signed tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-10T01:19:22Z","receivedAt":"2025-10-10T01:19:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n>  -s::\n>  --sign::\n> -\tMake a GPG-signed tag, using the default e-mail address's key.\n> ...\n> +\tMake a cryptographically signed tag, using the default signing\n> ...\n>  -u <key-id>::\n>  --local-user=<key-id>::\n> -\tMake a GPG-signed tag, using the given key.\n> +\tMake a cryptographically signed tag using the given key. The\n\nGiven the above ...\n\n>  -v::\n>  --verify::\n> -\tVerify the GPG signature of the given tag names.\n> +\tVerify the signature of the given tag names.\n\n... it would be more consistent to say it with \"cryptographic\"\nsomewhere.  Also what we verify are \"tags\", not their names.\nSo, something like\n\n\tVerify the cryptographic signature of the given tags.\n\nperhaps?\n\n"},{"id":"528472","messageId":"aOisaq-rSdwjwo6b@pks.im","threadId":"64261","inReplyTo":"20251009122457.1273701-3-christian.couder@gmail.com","subject":"Re: [PATCH v2 2/5] lib-gpg: allow tests with GPGSM or GPGSSH prereq first","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-10T06:49:14Z","receivedAt":"2025-10-10T06:49:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 09, 2025 at 02:24:54PM +0200, Christian Couder wrote:\n> diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\n> index 937b876bd0..b99ae39a06 100644\n> --- a/t/lib-gpg.sh\n> +++ b/t/lib-gpg.sh\n> @@ -9,6 +9,16 @@\n> @@ -63,6 +72,14 @@ test_lazy_prereq GPG2 '\n>  \t\t;;\n>  \t*)\n>  \t\t(gpgconf --kill all || : ) &&\n> +\n> +\t\t# NEEDSWORK: prepare_gnupghome() should definitely be\n> +\t\t# called here, but it looks like it exposes a\n> +\t\t# pre-existing, hidden bug by allowing some tests in\n> +\t\t# t1016-compatObjectFormat.sh to run instead of being\n> +\t\t# skipped. See:\n> +\t\t# https://lore.kernel.org/git/ZoV8b2RvYxLOotSJ@teonanacatl.net/\n> +\n>  \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n>  \t\t\t\"$TEST_DIRECTORY\"/lib-gpg/keyring.gpg &&\n>  \t\tgpg --homedir \"${GNUPGHOME}\" --import-ownertrust \\\n\nInteresting. So I assume that these gpg commands here fail because the\nGPG home doesn't exist, and thus we disable the prereq? Too bad, but I\nagree that this doesn't necessarily have to be fixed by this patch\nseries.\n\nThe remaining patches look good to me and address my feedback, thanks!\n\nPatrick\n"},{"id":"528474","messageId":"CAP8UFD10eMwKdacEzLumdXUghV2nYZViDT4o44u1qgXVU5Pzew@mail.gmail.com","threadId":"64261","inReplyTo":"xmqqsefrblk5.fsf@gitster.g","subject":"Re: [PATCH v2 1/5] doc: git-tag: stop focusing on GPG signed tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-10T07:06:55Z","receivedAt":"2025-10-10T07:07:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Oct 10, 2025 at 3:19 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> >  -s::\n> >  --sign::\n> > -     Make a GPG-signed tag, using the default e-mail address's key.\n> > ...\n> > +     Make a cryptographically signed tag, using the default signing\n> > ...\n> >  -u <key-id>::\n> >  --local-user=<key-id>::\n> > -     Make a GPG-signed tag, using the given key.\n> > +     Make a cryptographically signed tag using the given key. The\n>\n> Given the above ...\n>\n> >  -v::\n> >  --verify::\n> > -     Verify the GPG signature of the given tag names.\n> > +     Verify the signature of the given tag names.\n>\n> ... it would be more consistent to say it with \"cryptographic\"\n> somewhere.  Also what we verify are \"tags\", not their names.\n> So, something like\n>\n>         Verify the cryptographic signature of the given tags.\n>\n> perhaps?\n\nYeah, it looks better. I have changed this in my current version, and\nwill send it in a v3 in a few days.\n\nThanks.\n"},{"id":"528509","messageId":"aOkTs7G2GGLKajUf@teonanacatl.net","threadId":"64261","inReplyTo":"aOisaq-rSdwjwo6b@pks.im","subject":"Re: [PATCH v2 2/5] lib-gpg: allow tests with GPGSM or GPGSSH prereq first","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2025-10-10T14:09:55Z","receivedAt":"2025-10-10T14:09:58Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"Patrick Steinhardt wrote:\n> On Thu, Oct 09, 2025 at 02:24:54PM +0200, Christian Couder wrote:\n>> diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\n>> index 937b876bd0..b99ae39a06 100644\n>> --- a/t/lib-gpg.sh\n>> +++ b/t/lib-gpg.sh\n>> @@ -9,6 +9,16 @@\n>> @@ -63,6 +72,14 @@ test_lazy_prereq GPG2 '\n>>  \t\t;;\n>>  \t*)\n>>  \t\t(gpgconf --kill all || : ) &&\n>> +\n>> +\t\t# NEEDSWORK: prepare_gnupghome() should definitely be\n>> +\t\t# called here, but it looks like it exposes a\n>> +\t\t# pre-existing, hidden bug by allowing some tests in\n>> +\t\t# t1016-compatObjectFormat.sh to run instead of being\n>> +\t\t# skipped. See:\n>> +\t\t# https://lore.kernel.org/git/ZoV8b2RvYxLOotSJ@teonanacatl.net/\n>> +\n>>  \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n>>  \t\t\t\"$TEST_DIRECTORY\"/lib-gpg/keyring.gpg &&\n>>  \t\tgpg --homedir \"${GNUPGHOME}\" --import-ownertrust \\\n> \n> Interesting. So I assume that these gpg commands here fail because the\n> GPG home doesn't exist, and thus we disable the prereq? Too bad, but I\n> agree that this doesn't necessarily have to be fixed by this patch\n> series.\n\nI agree.  But it is ugly that any tests we have which rely\non the GPG2 prereq simply never run.  That should be fixed\nand, if it were me, I'd do so by dropping the flaky tests in\nt1016 initially.  Someone who cares about those tests\nrunning could debug it more and hopefully fix the problem.\n\nAs it stands, this breakage blocks tests in t1461-refs-list,\nt6300-for-each-ref, and t7510-signed-commit.  Anyone adding\na test with a GPG2 prereq should be aware that thoses tests\njust won't be run.\n\nThe t1016-compatObjectFormat tests have been flaky since\nthey were added and no one really noticed.  That's at least\npartly a failure of our CI output, which hides these sort of\nskipped tests that we just presume are running.  I don't\nhave any good suggestions for fixing that, unfortunately.\n\n-- \nTodd\n"},{"id":"528521","messageId":"xmqqbjmeafqm.fsf@gitster.g","threadId":"64261","inReplyTo":"aOkTs7G2GGLKajUf@teonanacatl.net","subject":"Re: [PATCH v2 2/5] lib-gpg: allow tests with GPGSM or GPGSSH prereq first","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-10T16:22:41Z","receivedAt":"2025-10-10T16:22:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Todd Zullinger <tmz@pobox.com> writes:\n\n> I agree.  But it is ugly that any tests we have which rely\n> on the GPG2 prereq simply never run.  That should be fixed\n> and, if it were me, I'd do so by dropping the flaky tests in\n> t1016 initially.  Someone who cares about those tests\n> running could debug it more and hopefully fix the problem.\n\nLet me queue your two patches as-is to leave the tip of 'seen'\nbroken for a few days to see if anybody bites ;-).  After that, we\nmay do \"s|test_expect_success|test_expect_failure|\" on those tests\nthat you call \"flaky\".  Are they flaky in the sense that they\nsometimes pass sometimes fail depending on the timing, or just\nsimply buggy and always fail?\n\n> The t1016-compatObjectFormat tests have been flaky since\n> they were added and no one really noticed.  That's at least\n> partly a failure of our CI output, which hides these sort of\n> skipped tests that we just presume are running.  I don't\n> have any good suggestions for fixing that, unfortunately.\n\nThanks.\n"},{"id":"528557","messageId":"aOm9fLW-8_oJQZy9@teonanacatl.net","threadId":"64261","inReplyTo":"xmqqbjmeafqm.fsf@gitster.g","subject":"Re: [PATCH v2 2/5] lib-gpg: allow tests with GPGSM or GPGSSH prereq first","fromName":"Todd Zullinger","fromEmail":"tmz@pobox.com","sentAt":"2025-10-11T02:14:20Z","receivedAt":"2025-10-11T02:14:23Z","isPatch":true,"sender":{"key":"tmz@pobox.com","avatar":"https://avatars.githubusercontent.com/u/806319?v=4"},"body":"Junio C Hamano wrote:\n> Todd Zullinger <tmz@pobox.com> writes:\n> \n>> I agree.  But it is ugly that any tests we have which rely\n>> on the GPG2 prereq simply never run.  That should be fixed\n>> and, if it were me, I'd do so by dropping the flaky tests in\n>> t1016 initially.  Someone who cares about those tests\n>> running could debug it more and hopefully fix the problem.\n> \n> Let me queue your two patches as-is to leave the tip of 'seen'\n> broken for a few days to see if anybody bites ;-).  After that, we\n> may do \"s|test_expect_success|test_expect_failure|\" on those tests\n> that you call \"flaky\".  Are they flaky in the sense that they\n> sometimes pass sometimes fail depending on the timing, or just\n> simply buggy and always fail?\n\nIn my recollection, they fail all (or nearly all?) of the\ntime in our CI runs and when I was building git for Fedora\ninfrastructure, they failed consistently on the Fedora\nbuilders as well.\n\nThey fail rarely (if ever) when I run them locally, even\nwith --stress options.  That made it rather difficult to\nwork out the issue.  I thought that it was a timing problem\nfor a while, but I wasn't able to find a way to demonstrate\nthat.\n\nThanks for the willingness to suffer some test breakage to\nsee if it can flush out a fix. :)\n\nI suspect there are folks here who know the test suite and\ncode being tested well enough that it may be really obvious\nto them.  Whether there is an intersection of those folks\nand spare \"round tuits\" is another matter.\n\n-- \nTodd\n"},{"id":"528580","messageId":"xmqqa51x561m.fsf@gitster.g","threadId":"64261","inReplyTo":"aOm9fLW-8_oJQZy9@teonanacatl.net","subject":"Re: [PATCH v2 2/5] lib-gpg: allow tests with GPGSM or GPGSSH prereq first","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-12T00:15:33Z","receivedAt":"2025-10-12T00:15:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Todd Zullinger <tmz@pobox.com> writes:\n\n> In my recollection, they fail all (or nearly all?) of the\n> time in our CI runs and when I was building git for Fedora\n> infrastructure, they failed consistently on the Fedora\n> builders as well.\n>\n> They fail rarely (if ever) when I run them locally, even\n> with --stress options.  That made it rather difficult to\n> work out the issue.  I thought that it was a timing problem\n> for a while, but I wasn't able to find a way to demonstrate\n> that.\n>\n> Thanks for the willingness to suffer some test breakage to\n> see if it can flush out a fix. :)\n\nOr I can just revert these two patches if nothing happens ;-).\n\n> I suspect there are folks here who know the test suite and\n> code being tested well enough that it may be really obvious\n> to them.  Whether there is an intersection of those folks\n> and spare \"round tuits\" is another matter.\n"},{"id":"528594","messageId":"20251013084857.1646783-1-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251007122958.1089680-1-christian.couder@gmail.com","subject":"[PATCH v3 0/5] fast-import: start controlling how tag signatures are handled","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-13T08:48:52Z","receivedAt":"2025-10-13T08:49:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Introduction\n------------\n\nTools like `git-filter-repo` should be able to control how tag\nsignatures are handled when regenerating repository content after it\nhas been filtered. For this purpose, they need a way for `git\nfast-import` to control how tag signatures are handled.\n\nA previous series [1] added a '--signed-commits=<mode>' option to `git\nfast-import` to control how commit signatures are handled, so this is\nadding a similar '--signed-tags=<mode>' for tag signatures.\n\nFor now this new option behaves in a very similar way as the option\nwith the same name that already exists in `git fast-export`.\nEspecially it supports exactly the same <mode>s and the same aliases\nfor these modes. For example \"ignore\" is a synonym for \"verbatim\".\n\nThis way, both `git fast-export` and `git fast-import` have both a\n'--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting the\nsame <mode>s.\n\nIn the future I want to implement new <mode>s like \"strip-if-invalid\",\n\"re-sign\", \"re-sign-if-invalid\" in `git fast-import` for both tag and\ncommit signatures. These might be a bit more complex, so for now I\nprefer to start with the simple modes.\n\n[1] https://lore.kernel.org/git/20250917181427.3193500-1-christian.couder@gmail.com/\n\nNote about the different patches\n--------------------------------\n\nPatch 1/5 (doc: git-tag: stop focussing on GPG signed tags) is a\ndocumentation update for `git tag`. It could go in a separate series\nor be dropped altogether, but while working on this I thought that it\nwould be a good thing to do, as the doc is quite outdated.\n\nPatches 2/5, 3/5 and 4/5 are preparatory patches for the main one\nwhich is patch 5/5 (fast-import: add '--signed-tags=<mode>' option).\n\nI wanted '--signed-tags=<mode>' to work for all kinds of signature in\ntags (OpenPGP, X.509 and SSH) but soon realized that the\n'--signed-tags=<mode>' option of `git fast-export` worked only for\nOpenPGP signatures, so I fixed that issue in patch 4/5 (fast-export:\nhandle all kinds of tag signatures).\n\nWhile working on the tests in patch 4/5, I found a few things to\nimprove that could belong to other patches so that's how I came up\nwith patches 2/5 and 3/5.\n\nChanges since v2\n----------------\n\nThanks to Patrick Steinhardt, Todd Zullinger and Collin Funk who\nreviewed or commented on the v1 and v2.\n\nThere is a single change in the first patch (doc: git-tag: stop\nfocusing on GPG signed tags) where the description of the\n`-v | --verify` option of `git tag` has been improved.\n\nCI tests\n--------\n\nI haven't run CI tests because there is a single documentation change\nsince v2 that is very unlikely to make things break.\n\nRange diff since v2\n-------------------\n\n1:  eb65af631d ! 1:  ac67d927ad doc: git-tag: stop focusing on GPG signed tags\n    @@ Documentation/git-tag.adoc: OPTIONS\n      -v::\n      --verify::\n     -  Verify the GPG signature of the given tag names.\n    -+  Verify the signature of the given tag names.\n    ++  Verify the cryptographic signature of the given tags.\n      \n      -n<num>::\n        <num> specifies how many lines from the annotation, if any,\n2:  640204ef26 = 2:  f0208527ff lib-gpg: allow tests with GPGSM or GPGSSH prereq first\n3:  8f788bafe1 = 3:  e9e3d8c081 t9350: properly count annotated tags\n4:  d62a43905c = 4:  8d318a0046 fast-export: handle all kinds of tag signatures\n5:  9094f37b46 = 5:  962ad96b4a fast-import: add '--signed-tags=<mode>' option\n\n\nChristian Couder (5):\n  doc: git-tag: stop focusing on GPG signed tags\n  lib-gpg: allow tests with GPGSM or GPGSSH prereq first\n  t9350: properly count annotated tags\n  fast-export: handle all kinds of tag signatures\n  fast-import: add '--signed-tags=<mode>' option\n\n Documentation/git-fast-import.adoc |  5 ++\n Documentation/git-tag.adoc         | 48 ++++++++++++------\n builtin/fast-export.c              |  7 ++-\n builtin/fast-import.c              | 43 ++++++++++++++++\n t/lib-gpg.sh                       | 24 +++++++--\n t/meson.build                      |  1 +\n t/t9306-fast-import-signed-tags.sh | 80 ++++++++++++++++++++++++++++++\n t/t9350-fast-export.sh             | 48 ++++++++++++++++--\n 8 files changed, 229 insertions(+), 27 deletions(-)\n create mode 100755 t/t9306-fast-import-signed-tags.sh\n\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528595","messageId":"20251013084857.1646783-2-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-1-christian.couder@gmail.com","subject":"[PATCH v3 1/5] doc: git-tag: stop focusing on GPG signed tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-13T08:48:53Z","receivedAt":"2025-10-13T08:49:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"It looks like the documentation of `git tag` is focused a bit too\nmuch on GPG signed tags.\n\nThis starts with the \"NAME\" section where the command is described\nwith:\n\n\"Create, list, delete or verify a tag object signed with GPG\"\n\nwhile for example `git branch` is described with simply:\n\n\"List, create, or delete branches\"\n\nThis could give the false impression that `git tag` only works with\ntag objects, not with lightweight tags, and that tag objects are\nalways GPG signed.\n\nIn the \"DESCRIPTION\" section, it looks like only \"GnuPG signed tag\nobjects\" can be created by the `-s` and `-u <key-id>` options, and it\nseems `gpg.program` can only specify a \"custom GnuPG binary\".\n\nThis goes on in the \"OPTIONS\" section too, especially about the `-s`\nand `-u <key-id>` options.\n\nThe \"CONFIGURATION\" section also doesn't talk about how to configure\nthe command to work with X.509 and SSH signatures.\n\nLet's rework all that to make sure users have a more accurate and\nbalanced view of what the command can do.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-tag.adoc | 48 ++++++++++++++++++++++++++------------\n 1 file changed, 33 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc\nindex a4b1c0ec05..28d6fe4e1a 100644\n--- a/Documentation/git-tag.adoc\n+++ b/Documentation/git-tag.adoc\n@@ -3,7 +3,7 @@ git-tag(1)\n \n NAME\n ----\n-git-tag - Create, list, delete or verify a tag object signed with GPG\n+git-tag - Create, list, delete or verify tags\n \n \n SYNOPSIS\n@@ -38,15 +38,17 @@ and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n \n-A GnuPG signed tag object will be created when `-s` or `-u\n-<key-id>` is used.  When `-u <key-id>` is not used, the\n-committer identity for the current user is used to find the\n-GnuPG key for signing. \tThe configuration variable `gpg.program`\n-is used to specify custom GnuPG binary.\n+A cryptographically signed tag object will be created when `-s` or\n+`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is\n+controlled by the `gpg.format` configuration variable, defaulting to\n+OpenPGP. When `-u <key-id>` is not used, the committer identity for\n+the current user is used to find the key for signing. The\n+configuration variable `gpg.program` is used to specify a custom\n+signing binary.\n \n Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n tags; they contain a creation date, the tagger name and e-mail, a\n-tagging message, and an optional GnuPG signature. Whereas a\n+tagging message, and an optional cryptographic signature. Whereas a\n \"lightweight\" tag is simply a name for an object (usually a commit\n object).\n \n@@ -64,10 +66,12 @@ OPTIONS\n \n -s::\n --sign::\n-\tMake a GPG-signed tag, using the default e-mail address's key.\n-\tThe default behavior of tag GPG-signing is controlled by `tag.gpgSign`\n-\tconfiguration variable if it exists, or disabled otherwise.\n-\tSee linkgit:git-config[1].\n+\tMake a cryptographically signed tag, using the default signing\n+\tkey. The signing backend used depends on the `gpg.format`\n+\tconfiguration variable. The default key is determined by the\n+\tbackend. For GPG, it's based on the committer's email address,\n+\twhile for SSH it may be a specific key file or agent\n+\tidentity. See linkgit:git-config[1].\n \n --no-sign::\n \tOverride `tag.gpgSign` configuration variable that is\n@@ -75,7 +79,10 @@ OPTIONS\n \n -u <key-id>::\n --local-user=<key-id>::\n-\tMake a GPG-signed tag, using the given key.\n+\tMake a cryptographically signed tag using the given key. The\n+\tformat of the <key-id> and the backend used depend on the\n+\t`gpg.format` configuration variable. See\n+\tlinkgit:git-config[1].\n \n -f::\n --force::\n@@ -87,7 +94,7 @@ OPTIONS\n \n -v::\n --verify::\n-\tVerify the GPG signature of the given tag names.\n+\tVerify the cryptographic signature of the given tags.\n \n -n<num>::\n \t<num> specifies how many lines from the annotation, if any,\n@@ -236,12 +243,23 @@ it in the repository configuration as follows:\n \n -------------------------------------\n [user]\n-    signingKey = <gpg-key-id>\n+    signingKey = <key-id>\n -------------------------------------\n \n+The signing backend can be chosen via the `gpg.format` configuration\n+variable, which defaults to `openpgp`. See linkgit:git-config[1]\n+for a list of other supported formats.\n+\n+The path to the program used for each signing backend can be specified\n+with the `gpg.<format>.program` configuration variable. For the\n+`openpgp` backend, `gpg.program` can be used as a synonym for\n+`gpg.openpgp.program`. See linkgit:git-config[1] for details.\n+\n `pager.tag` is only respected when listing tags, i.e., when `-l` is\n used or implied. The default is to use a pager.\n-See linkgit:git-config[1].\n+\n+See linkgit:git-config[1] for more details and other configuration\n+variables.\n \n DISCUSSION\n ----------\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528596","messageId":"20251013084857.1646783-3-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-1-christian.couder@gmail.com","subject":"[PATCH v3 2/5] lib-gpg: allow tests with GPGSM or GPGSSH prereq first","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-13T08:48:54Z","receivedAt":"2025-10-13T08:49:15Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"When the 'GPG' prereq is lazily tested, `mkdir \"$GNUPGHOME\"` could\nfail if the \"$GNUPGHOME\" directory already exists. This can happen if\nthe 'GPGSM' or the 'GPGSSH' prereq has been lazily tested before as they\nalready create \"$GNUPGHOME\".\n\nTo allow the GPGSM or the GPGSSH prereq to appear before the GPG prereq\nin some test scripts, let's refactor the creation and setup of the\n\"$GNUPGHOME\"` directory in a new prepare_gnupghome() function that uses\n`mkdir -p \"$GNUPGHOME\"`.\n\nThis will be useful in a following commit.\n\nUnfortunately the new prepare_gnupghome() function cannot be used when\nlazily testing the GPG2 prereq, because that would expose existing,\nhidden bugs in \"t1016-compatObjectFormat.sh\", so let's just document\nthat with a NEEDSWORK comment.\n\nHelped-by: Todd Zullinger <tmz@pobox.com>\nHelped-by: Collin Funk <collin.funk1@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/lib-gpg.sh | 24 ++++++++++++++++++++----\n 1 file changed, 20 insertions(+), 4 deletions(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex 937b876bd0..b99ae39a06 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -9,6 +9,16 @@\n GNUPGHOME=\"$(pwd)/gpghome\"\n export GNUPGHOME\n \n+# All the \"test_lazy_prereq GPG*\" below should use\n+# `prepare_gnupghome()` either directly or through a call to\n+# `test_have_prereq GPG*`. That's because `gpg` and `gpgsm`\n+# only create the directory specified using \"$GNUPGHOME\" or\n+# `--homedir` if it's the default (usually \"~/.gnupg\").\n+prepare_gnupghome() {\n+\tmkdir -p \"$GNUPGHOME\" &&\n+\tchmod 0700 \"$GNUPGHOME\"\n+}\n+\n test_lazy_prereq GPG '\n \tgpg_version=$(gpg --version 2>&1)\n \ttest $? != 127 || exit 1\n@@ -38,8 +48,7 @@ test_lazy_prereq GPG '\n \t\t# To export ownertrust:\n \t\t#\tgpg --homedir /tmp/gpghome --export-ownertrust \\\n \t\t#\t\t> lib-gpg/ownertrust\n-\t\tmkdir \"$GNUPGHOME\" &&\n-\t\tchmod 0700 \"$GNUPGHOME\" &&\n+\t\tprepare_gnupghome &&\n \t\t(gpgconf --kill all || : ) &&\n \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n \t\t\t\"$TEST_DIRECTORY\"/lib-gpg/keyring.gpg &&\n@@ -63,6 +72,14 @@ test_lazy_prereq GPG2 '\n \t\t;;\n \t*)\n \t\t(gpgconf --kill all || : ) &&\n+\n+\t\t# NEEDSWORK: prepare_gnupghome() should definitely be\n+\t\t# called here, but it looks like it exposes a\n+\t\t# pre-existing, hidden bug by allowing some tests in\n+\t\t# t1016-compatObjectFormat.sh to run instead of being\n+\t\t# skipped. See:\n+\t\t# https://lore.kernel.org/git/ZoV8b2RvYxLOotSJ@teonanacatl.net/\n+\n \t\tgpg --homedir \"${GNUPGHOME}\" --import \\\n \t\t\t\"$TEST_DIRECTORY\"/lib-gpg/keyring.gpg &&\n \t\tgpg --homedir \"${GNUPGHOME}\" --import-ownertrust \\\n@@ -132,8 +149,7 @@ test_lazy_prereq GPGSSH '\n \ttest $? = 0 || exit 1;\n \n \t# Setup some keys and an allowed signers file\n-\tmkdir -p \"${GNUPGHOME}\" &&\n-\tchmod 0700 \"${GNUPGHOME}\" &&\n+\tprepare_gnupghome &&\n \t(setfacl -k \"${GNUPGHOME}\" 2>/dev/null || true) &&\n \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_PRIMARY}\" >/dev/null &&\n \tssh-keygen -t rsa -b 2048 -N \"\" -C \"git rsa2048 key\" -f \"${GPGSSH_KEY_SECONDARY}\" >/dev/null &&\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528597","messageId":"20251013084857.1646783-4-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-1-christian.couder@gmail.com","subject":"[PATCH v3 3/5] t9350: properly count annotated tags","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-13T08:48:55Z","receivedAt":"2025-10-13T08:49:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"In \"t9350-fast-export.sh\", these existing tests:\n\n  - 'fast-export | fast-import when main is tagged'\n  - 'cope with tagger-less tags'\n\nare checking the number of annotated tags in the test repo by comparing\nit with some hardcoded values.\n\nThis could be an issue if some new tests that have some prerequisites\nadd new annotated tags to the repo before these existing tests. When\nthe prerequisites would be satisfied, the number of annotated tags\nwould be different from when some prerequisites would not be satisfied.\n\nAs we are going to add new tests that add new annotated tags in a\nfollowing commit, let's properly count the number of annotated tag in\nthe repo by incrementing a counter each time a new annotated tag is\nadded, and then by comparing the number of annotated tags to the value\nof the counter when checking the number of annotated tags.\n\nThis is a bit ugly, but it makes it explicit that some tests are\ninterdependent. Alternative solutions, like moving the new tests to\nthe end of the script, were considered, but were rejected because they\nwould instead hide the technical debt and could confuse developers in\nthe future.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t9350-fast-export.sh | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 8f85c69d62..21ff26939c 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -35,6 +35,7 @@ test_expect_success 'setup' '\n \tgit commit -m sitzt file2 &&\n \ttest_tick &&\n \tgit tag -a -m valentin muss &&\n+\tANNOTATED_TAG_COUNT=1 &&\n \tgit merge -s ours main\n \n '\n@@ -229,7 +230,8 @@ EOF\n \n test_expect_success 'set up faked signed tag' '\n \n-\tgit fast-import <signed-tag-import\n+\tgit fast-import <signed-tag-import &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n \n '\n \n@@ -491,8 +493,9 @@ test_expect_success 'fast-export -C -C | fast-import' '\n test_expect_success 'fast-export | fast-import when main is tagged' '\n \n \tgit tag -m msg last &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 3\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT\n \n '\n \n@@ -506,12 +509,13 @@ test_expect_success 'cope with tagger-less tags' '\n \n \tTAG=$(git hash-object --literally -t tag -w tag-content) &&\n \tgit update-ref refs/tags/sonnenschein $TAG &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n \t! grep \"Unspecified Tagger\" output &&\n \tgit fast-export -C -C --signed-tags=strip --all \\\n \t\t--fake-missing-tagger > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n \tgrep \"Unspecified Tagger\" output\n \n '\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528598","messageId":"20251013084857.1646783-5-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-1-christian.couder@gmail.com","subject":"[PATCH v3 4/5] fast-export: handle all kinds of tag signatures","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-13T08:48:56Z","receivedAt":"2025-10-13T08:49:18Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Currently the handle_tag() function in \"builtin/fast-export.c\" searches\nonly for \"\\n-----BEGIN PGP SIGNATURE-----\\n\" in the tag message to find\na tag signature.\n\nThis doesn't handle all kinds of OpenPGP signatures as some can start\nwith \"-----BEGIN PGP MESSAGE-----\" too, and this doesn't handle SSH and\nX.509 signatures either as they use \"-----BEGIN SSH SIGNATURE-----\" and\n\"-----BEGIN SIGNED MESSAGE-----\" respectively.\n\nTo handle all these kinds of tag signatures supported by Git, let's use\nthe parse_signed_buffer() function to properly find signatures in tag\nmessages.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/fast-export.c  |  7 +++----\n t/t9350-fast-export.sh | 36 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 39 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex dc2486f9a8..7adbc55f0d 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)\n \n \t/* handle signed tags */\n \tif (message) {\n-\t\tconst char *signature = strstr(message,\n-\t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n-\t\tif (signature)\n+\t\tsize_t sig_offset = parse_signed_buffer(message, message_size);\n+\t\tif (sig_offset < message_size)\n \t\t\tswitch (signed_tag_mode) {\n \t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n@@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n \t\t\tcase SIGN_STRIP:\n-\t\t\t\tmessage_size = signature + 1 - message;\n+\t\t\t\tmessage_size = sig_offset;\n \t\t\t\tbreak;\n \t\t\t}\n \t}\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 21ff26939c..3d153a4805 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -279,6 +279,42 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\n '\n \n+test_expect_success GPGSM 'setup X.509 signed tag' '\n+\ttest_config gpg.format x509 &&\n+\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n+\n+\tgit tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n+'\n+\n+test_expect_success GPGSM 'signed-tags=verbatim with X.509' '\n+\tgit fast-export --signed-tags=verbatim x509-signed > output &&\n+\ttest_grep \"SIGNED MESSAGE\" output\n+'\n+\n+test_expect_success GPGSM 'signed-tags=strip with X.509' '\n+\tgit fast-export --signed-tags=strip x509-signed > output &&\n+\ttest_grep ! \"SIGNED MESSAGE\" output\n+'\n+\n+test_expect_success GPGSSH 'setup SSH signed tag' '\n+\ttest_config gpg.format ssh &&\n+\ttest_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n+\n+\tgit tag -s -m \"SSH signed tag\" ssh-signed $(git rev-parse HEAD) &&\n+\tANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n+'\n+\n+test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '\n+\tgit fast-export --signed-tags=verbatim ssh-signed > output &&\n+\ttest_grep \"SSH SIGNATURE\" output\n+'\n+\n+test_expect_success GPGSSH 'signed-tags=strip with SSH' '\n+\tgit fast-export --signed-tags=strip ssh-signed > output &&\n+\ttest_grep ! \"SSH SIGNATURE\" output\n+'\n+\n test_expect_success GPG 'set up signed commit' '\n \n \t# Generate a commit with both \"gpgsig\" and \"encoding\" set, so\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528599","messageId":"20251013084857.1646783-6-christian.couder@gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-1-christian.couder@gmail.com","subject":"[PATCH v3 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-13T08:48:57Z","receivedAt":"2025-10-13T08:49:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Recently, eaaddf5791 (fast-import: add '--signed-commits=<mode>'\noption, 2025-09-17) added support for controlling how signed commits\nare handled by `git fast-import`, but there is no option yet to\ndecide about signed tags.\n\nTo remediate that, let's add a '--signed-tags=<mode>' option to\n`git fast-import` too.\n\nWith this, both `git fast-export` and `git fast-import` have both\na '--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting\nthe same <mode>s.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-import.adoc |  5 ++\n builtin/fast-import.c              | 43 ++++++++++++++++\n t/meson.build                      |  1 +\n t/t9306-fast-import-signed-tags.sh | 80 ++++++++++++++++++++++++++++++\n 4 files changed, 129 insertions(+)\n create mode 100755 t/t9306-fast-import-signed-tags.sh\n\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 85ed7a7270..b74179a6c8 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -66,6 +66,11 @@ 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+\tSpecify how to handle signed tags.  Behaves in the same way\n+\tas the same option in linkgit:git-fast-export[1], except that\n+\tdefault is 'verbatim' (instead of 'abort').\n+\n --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed commits.  Behaves in the same way\n \tas the same option in linkgit:git-fast-export[1], except that\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 2010e78475..60d6faa465 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -188,6 +188,7 @@ static int global_argc;\n static const char **global_argv;\n static const char *global_prefix;\n \n+static enum sign_mode signed_tag_mode = SIGN_VERBATIM;\n static enum sign_mode signed_commit_mode = SIGN_VERBATIM;\n \n /* Memory pools */\n@@ -2961,6 +2962,43 @@ 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+{\n+\tsize_t sig_offset = parse_signed_buffer(msg->buf, msg->len);\n+\n+\t/* If there is no signature, there is nothing to do. */\n+\tif (sig_offset >= msg->len)\n+\t\treturn;\n+\n+\tswitch (signed_tag_mode) {\n+\n+\t/* First, modes that don't change anything */\n+\tcase SIGN_ABORT:\n+\t\tdie(_(\"encountered signed tag; use \"\n+\t\t      \"--signed-tags=<mode> to handle it\"));\n+\tcase SIGN_WARN_VERBATIM:\n+\t\twarning(_(\"importing a tag signature verbatim for tag '%s'\"), name);\n+\t\t/* fallthru */\n+\tcase SIGN_VERBATIM:\n+\t\t/* Nothing to do, the signature will be put into the imported tag. */\n+\t\tbreak;\n+\n+\t/* Second, modes that remove the signature */\n+\tcase SIGN_WARN_STRIP:\n+\t\twarning(_(\"stripping a tag signature for tag '%s'\"), name);\n+\t\t/* fallthru */\n+\tcase SIGN_STRIP:\n+\t\t/* Truncate the buffer to remove the signature */\n+\t\tstrbuf_setlen(msg, sig_offset);\n+\t\tbreak;\n+\n+\t/* Third, BUG */\n+\tdefault:\n+\t\tBUG(\"invalid signed_tag_mode value %d from tag '%s'\",\n+\t\t    signed_tag_mode, name);\n+\t}\n+}\n+\n static void parse_new_tag(const char *arg)\n {\n \tstatic struct strbuf msg = STRBUF_INIT;\n@@ -3024,6 +3062,8 @@ 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@@ -3544,6 +3584,9 @@ static int parse_one_option(const char *option)\n \t} else if (skip_prefix(option, \"signed-commits=\", &option)) {\n \t\tif (parse_sign_mode(option, &signed_commit_mode))\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))\n+\t\t\tusagef(_(\"unknown --signed-tags mode '%s'\"), option);\n \t} else if (!strcmp(option, \"quiet\")) {\n \t\tshow_stats = 0;\n \t\tquiet = 1;\ndiff --git a/t/meson.build b/t/meson.build\nindex 11376b9e25..cb8c2b4b30 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -1036,6 +1036,7 @@ integration_tests = [\n   't9303-fast-import-compression.sh',\n   't9304-fast-import-marks.sh',\n   't9305-fast-import-signatures.sh',\n+  't9306-fast-import-signed-tags.sh',\n   't9350-fast-export.sh',\n   't9351-fast-export-anonymize.sh',\n   't9400-git-cvsserver-server.sh',\ndiff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh\nnew file mode 100755\nindex 0000000000..363619e7d1\n--- /dev/null\n+++ b/t/t9306-fast-import-signed-tags.sh\n@@ -0,0 +1,80 @@\n+#!/bin/sh\n+\n+test_description='git fast-import --signed-tags=<mode>'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n+\n+test_expect_success 'set up unsigned initial commit and import repo' '\n+\ttest_commit first &&\n+\tgit init new\n+'\n+\n+test_expect_success 'import no signed tag with --signed-tags=abort' '\n+\tgit fast-export --signed-tags=verbatim >output &&\n+\tgit -C new fast-import --quiet --signed-tags=abort <output\n+'\n+\n+test_expect_success GPG 'set up OpenPGP signed tag' '\n+\tgit tag -s -m \"OpenPGP signed tag\" openpgp-signed first &&\n+\tOPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&\n+\tgit fast-export --signed-tags=verbatim openpgp-signed >output\n+'\n+\n+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '\n+\ttest_must_fail git -C new fast-import --quiet --signed-tags=abort <output\n+'\n+\n+test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '\n+\tgit -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&\n+\ttest $OPENPGP_SIGNED = $IMPORTED &&\n+\ttest_must_be_empty log\n+'\n+\n+test_expect_success GPGSM 'setup X.509 signed tag' '\n+\ttest_config gpg.format x509 &&\n+\ttest_config user.signingkey $GIT_COMMITTER_EMAIL &&\n+\n+\tgit tag -s -m \"X.509 signed tag\" x509-signed first &&\n+\tX509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&\n+\tgit fast-export --signed-tags=verbatim x509-signed >output\n+'\n+\n+test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '\n+\tgit -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&\n+\ttest_grep \"stripping a tag signature for tag '\\''x509-signed'\\''\" log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&\n+\ttest $X509_SIGNED != $IMPORTED &&\n+\tgit -C new cat-file -p x509-signed >out &&\n+\ttest_grep ! \"SIGNED MESSAGE\" out\n+'\n+\n+test_expect_success GPGSSH 'setup SSH signed tag' '\n+\ttest_config gpg.format ssh &&\n+\ttest_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n+\n+\tgit tag -s -m \"SSH signed tag\" ssh-signed first &&\n+\tSSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&\n+\tgit fast-export --signed-tags=verbatim ssh-signed >output\n+'\n+\n+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '\n+\tgit -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&\n+\ttest_grep \"importing a tag signature verbatim for tag '\\''ssh-signed'\\''\" log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n+\ttest $SSH_SIGNED = $IMPORTED\n+'\n+\n+test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '\n+\tgit -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&\n+\ttest_must_be_empty log &&\n+\tIMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n+\ttest $SSH_SIGNED != $IMPORTED &&\n+\tgit -C new cat-file -p ssh-signed >out &&\n+\ttest_grep ! \"SSH SIGNATURE\" out\n+'\n+\n+test_done\n-- \n2.51.0.438.g6987fc0bae\n\n"},{"id":"528604","messageId":"CAP8UFD0Jdb2Q5dpspUOnKBJuupikJ21mJmzRyYsNOhem-JP-9g@mail.gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-1-christian.couder@gmail.com","subject":"Re: [PATCH v3 0/5] fast-import: start controlling how tag signatures are handled","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-13T09:09:49Z","receivedAt":"2025-10-13T09:10:04Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Oct 13, 2025 at 10:49 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n\n>\n> Changes since v2\n> ----------------\n>\n> Thanks to Patrick Steinhardt, Todd Zullinger and Collin Funk who\n> reviewed or commented on the v1 and v2.\n>\n> There is a single change in the first patch (doc: git-tag: stop\n> focusing on GPG signed tags) where the description of the\n> `-v | --verify` option of `git tag` has been improved.\n\nSorry, this should have been sent in reply to the v2\n(https://lore.kernel.org/git/20251009122457.1273701-1-christian.couder@gmail.com/)\ninstead of the v1.\n"},{"id":"529546","messageId":"CABPp-BEhKH7goFVRJ=BvRi50StNbCDuE3VT=DOozPcxa=AsU6A@mail.gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-2-christian.couder@gmail.com","subject":"Re: [PATCH v3 1/5] doc: git-tag: stop focusing on GPG signed tags","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-10-24T02:03:08Z","receivedAt":"2025-10-24T02:03:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 13, 2025 at 4:49 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> It looks like the documentation of `git tag` is focused a bit too\n> much on GPG signed tags.\n>\n> This starts with the \"NAME\" section where the command is described\n> with:\n>\n> \"Create, list, delete or verify a tag object signed with GPG\"\n>\n> while for example `git branch` is described with simply:\n>\n> \"List, create, or delete branches\"\n>\n> This could give the false impression that `git tag` only works with\n> tag objects, not with lightweight tags, and that tag objects are\n> always GPG signed.\n>\n> In the \"DESCRIPTION\" section, it looks like only \"GnuPG signed tag\n> objects\" can be created by the `-s` and `-u <key-id>` options, and it\n> seems `gpg.program` can only specify a \"custom GnuPG binary\".\n>\n> This goes on in the \"OPTIONS\" section too, especially about the `-s`\n> and `-u <key-id>` options.\n>\n> The \"CONFIGURATION\" section also doesn't talk about how to configure\n> the command to work with X.509 and SSH signatures.\n>\n> Let's rework all that to make sure users have a more accurate and\n> balanced view of what the command can do.\n>\n> Helped-by: Patrick Steinhardt <ps@pks.im>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  Documentation/git-tag.adoc | 48 ++++++++++++++++++++++++++------------\n>  1 file changed, 33 insertions(+), 15 deletions(-)\n>\n> diff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc\n> index a4b1c0ec05..28d6fe4e1a 100644\n> --- a/Documentation/git-tag.adoc\n> +++ b/Documentation/git-tag.adoc\n> @@ -3,7 +3,7 @@ git-tag(1)\n>\n>  NAME\n>  ----\n> -git-tag - Create, list, delete or verify a tag object signed with GPG\n> +git-tag - Create, list, delete or verify tags\n>\n>\n>  SYNOPSIS\n> @@ -38,15 +38,17 @@ and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n>  Otherwise, a tag reference that points directly at the given object\n>  (i.e., a lightweight tag) is created.\n>\n> -A GnuPG signed tag object will be created when `-s` or `-u\n> -<key-id>` is used.  When `-u <key-id>` is not used, the\n> -committer identity for the current user is used to find the\n> -GnuPG key for signing.         The configuration variable `gpg.program`\n> -is used to specify custom GnuPG binary.\n> +A cryptographically signed tag object will be created when `-s` or\n> +`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is\n> +controlled by the `gpg.format` configuration variable, defaulting to\n> +OpenPGP. When `-u <key-id>` is not used, the committer identity for\n> +the current user is used to find the key for signing. The\n> +configuration variable `gpg.program` is used to specify a custom\n> +signing binary.\n>\n>  Tag objects (created with `-a`, `-s`, or `-u`) are called \"annotated\"\n>  tags; they contain a creation date, the tagger name and e-mail, a\n> -tagging message, and an optional GnuPG signature. Whereas a\n> +tagging message, and an optional cryptographic signature. Whereas a\n>  \"lightweight\" tag is simply a name for an object (usually a commit\n>  object).\n>\n> @@ -64,10 +66,12 @@ OPTIONS\n>\n>  -s::\n>  --sign::\n> -       Make a GPG-signed tag, using the default e-mail address's key.\n> -       The default behavior of tag GPG-signing is controlled by `tag.gpgSign`\n> -       configuration variable if it exists, or disabled otherwise.\n> -       See linkgit:git-config[1].\n> +       Make a cryptographically signed tag, using the default signing\n> +       key. The signing backend used depends on the `gpg.format`\n> +       configuration variable. The default key is determined by the\n> +       backend. For GPG, it's based on the committer's email address,\n> +       while for SSH it may be a specific key file or agent\n> +       identity. See linkgit:git-config[1].\n>\n>  --no-sign::\n>         Override `tag.gpgSign` configuration variable that is\n> @@ -75,7 +79,10 @@ OPTIONS\n>\n>  -u <key-id>::\n>  --local-user=<key-id>::\n> -       Make a GPG-signed tag, using the given key.\n> +       Make a cryptographically signed tag using the given key. The\n> +       format of the <key-id> and the backend used depend on the\n> +       `gpg.format` configuration variable. See\n> +       linkgit:git-config[1].\n>\n>  -f::\n>  --force::\n> @@ -87,7 +94,7 @@ OPTIONS\n>\n>  -v::\n>  --verify::\n> -       Verify the GPG signature of the given tag names.\n> +       Verify the cryptographic signature of the given tags.\n>\n>  -n<num>::\n>         <num> specifies how many lines from the annotation, if any,\n> @@ -236,12 +243,23 @@ it in the repository configuration as follows:\n>\n>  -------------------------------------\n>  [user]\n> -    signingKey = <gpg-key-id>\n> +    signingKey = <key-id>\n>  -------------------------------------\n>\n> +The signing backend can be chosen via the `gpg.format` configuration\n> +variable, which defaults to `openpgp`. See linkgit:git-config[1]\n> +for a list of other supported formats.\n> +\n> +The path to the program used for each signing backend can be specified\n> +with the `gpg.<format>.program` configuration variable. For the\n> +`openpgp` backend, `gpg.program` can be used as a synonym for\n> +`gpg.openpgp.program`. See linkgit:git-config[1] for details.\n> +\n>  `pager.tag` is only respected when listing tags, i.e., when `-l` is\n>  used or implied. The default is to use a pager.\n> -See linkgit:git-config[1].\n> +\n> +See linkgit:git-config[1] for more details and other configuration\n> +variables.\n>\n>  DISCUSSION\n>  ----------\n> --\n> 2.51.0.438.g6987fc0bae\n\nLooks like some nice clarifications & corrections.\n"},{"id":"529547","messageId":"CABPp-BFEUuVWZHCHfF89KPDr+=BRT1OyiJwB6hc8SW3o3o2MaA@mail.gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-4-christian.couder@gmail.com","subject":"Re: [PATCH v3 3/5] t9350: properly count annotated tags","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-10-24T02:03:15Z","receivedAt":"2025-10-24T02:03:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 13, 2025 at 4:49 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> In \"t9350-fast-export.sh\", these existing tests:\n>\n>   - 'fast-export | fast-import when main is tagged'\n>   - 'cope with tagger-less tags'\n>\n> are checking the number of annotated tags in the test repo by comparing\n> it with some hardcoded values.\n>\n> This could be an issue if some new tests that have some prerequisites\n> add new annotated tags to the repo before these existing tests. When\n> the prerequisites would be satisfied, the number of annotated tags\n> would be different from when some prerequisites would not be satisfied.\n>\n> As we are going to add new tests that add new annotated tags in a\n> following commit, let's properly count the number of annotated tag in\n> the repo by incrementing a counter each time a new annotated tag is\n> added, and then by comparing the number of annotated tags to the value\n> of the counter when checking the number of annotated tags.\n>\n> This is a bit ugly, but it makes it explicit that some tests are\n> interdependent. Alternative solutions, like moving the new tests to\n> the end of the script, were considered, but were rejected because they\n> would instead hide the technical debt and could confuse developers in\n> the future.\n>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  t/t9350-fast-export.sh | 12 ++++++++----\n>  1 file changed, 8 insertions(+), 4 deletions(-)\n>\n> diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> index 8f85c69d62..21ff26939c 100755\n> --- a/t/t9350-fast-export.sh\n> +++ b/t/t9350-fast-export.sh\n> @@ -35,6 +35,7 @@ test_expect_success 'setup' '\n>         git commit -m sitzt file2 &&\n>         test_tick &&\n>         git tag -a -m valentin muss &&\n> +       ANNOTATED_TAG_COUNT=1 &&\n>         git merge -s ours main\n>\n>  '\n> @@ -229,7 +230,8 @@ EOF\n>\n>  test_expect_success 'set up faked signed tag' '\n>\n> -       git fast-import <signed-tag-import\n> +       git fast-import <signed-tag-import &&\n> +       ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n>\n>  '\n>\n> @@ -491,8 +493,9 @@ test_expect_success 'fast-export -C -C | fast-import' '\n>  test_expect_success 'fast-export | fast-import when main is tagged' '\n>\n>         git tag -m msg last &&\n> +       ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n>         git fast-export -C -C --signed-tags=strip --all > output &&\n> -       test $(grep -c \"^tag \" output) = 3\n> +       test $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT\n>\n>  '\n>\n> @@ -506,12 +509,13 @@ test_expect_success 'cope with tagger-less tags' '\n>\n>         TAG=$(git hash-object --literally -t tag -w tag-content) &&\n>         git update-ref refs/tags/sonnenschein $TAG &&\n> +       ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&\n>         git fast-export -C -C --signed-tags=strip --all > output &&\n> -       test $(grep -c \"^tag \" output) = 4 &&\n> +       test $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n>         ! grep \"Unspecified Tagger\" output &&\n>         git fast-export -C -C --signed-tags=strip --all \\\n>                 --fake-missing-tagger > output &&\n> -       test $(grep -c \"^tag \" output) = 4 &&\n> +       test $(grep -c \"^tag \" output) = $ANNOTATED_TAG_COUNT &&\n>         grep \"Unspecified Tagger\" output\n>\n>  '\n> --\n> 2.51.0.438.g6987fc0bae\n\nWhen tests are not read-only, I tend to prefer either giving each test\nin a testfile its own repo, or doing hard resets with\ntest_when_finished.  Either way allows tests to be more independent,\nand allows new tests to be added or removed from the testsuite without\nadverse affects on other tests.  But, restructuring an existing\ntestfile is a significantly bigger change, so this seems like a\nreasonable path you've proposed for your series.\n"},{"id":"529548","messageId":"CABPp-BHMNQB8Dc-xq_JbL_73PQ+wUDwxWSKUQyjFGGYWUyiWvw@mail.gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-5-christian.couder@gmail.com","subject":"Re: [PATCH v3 4/5] fast-export: handle all kinds of tag signatures","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-10-24T02:03:30Z","receivedAt":"2025-10-24T02:03:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 13, 2025 at 4:49 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> Currently the handle_tag() function in \"builtin/fast-export.c\" searches\n> only for \"\\n-----BEGIN PGP SIGNATURE-----\\n\" in the tag message to find\n> a tag signature.\n>\n> This doesn't handle all kinds of OpenPGP signatures as some can start\n> with \"-----BEGIN PGP MESSAGE-----\" too, and this doesn't handle SSH and\n> X.509 signatures either as they use \"-----BEGIN SSH SIGNATURE-----\" and\n> \"-----BEGIN SIGNED MESSAGE-----\" respectively.\n>\n> To handle all these kinds of tag signatures supported by Git, let's use\n> the parse_signed_buffer() function to properly find signatures in tag\n> messages.\n>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  builtin/fast-export.c  |  7 +++----\n>  t/t9350-fast-export.sh | 36 ++++++++++++++++++++++++++++++++++++\n>  2 files changed, 39 insertions(+), 4 deletions(-)\n>\n> diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> index dc2486f9a8..7adbc55f0d 100644\n> --- a/builtin/fast-export.c\n> +++ b/builtin/fast-export.c\n> @@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)\n>\n>         /* handle signed tags */\n>         if (message) {\n> -               const char *signature = strstr(message,\n> -                                              \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n> -               if (signature)\n> +               size_t sig_offset = parse_signed_buffer(message, message_size);\n> +               if (sig_offset < message_size)\n\nAlways nice to remove a special hard-coded (and incomplete) additional\nparsing with a call to the official function we have to handle this.\n\n>                         switch (signed_tag_mode) {\n>                         case SIGN_ABORT:\n>                                 die(\"encountered signed tag %s; use \"\n> @@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)\n>                                         oid_to_hex(&tag->object.oid));\n>                                 /* fallthru */\n>                         case SIGN_STRIP:\n> -                               message_size = signature + 1 - message;\n> +                               message_size = sig_offset;\n>                                 break;\n>                         }\n>         }\n> diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> index 21ff26939c..3d153a4805 100755\n> --- a/t/t9350-fast-export.sh\n> +++ b/t/t9350-fast-export.sh\n> @@ -279,6 +279,42 @@ test_expect_success 'signed-tags=warn-strip' '\n>         test -s err\n>  '\n>\n> +test_expect_success GPGSM 'setup X.509 signed tag' '\n> +       test_config gpg.format x509 &&\n> +       test_config user.signingkey $GIT_COMMITTER_EMAIL &&\n> +\n> +       git tag -s -m \"X.509 signed tag\" x509-signed $(git rev-parse HEAD) &&\n> +       ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n> +'\n> +\n> +test_expect_success GPGSM 'signed-tags=verbatim with X.509' '\n> +       git fast-export --signed-tags=verbatim x509-signed > output &&\n> +       test_grep \"SIGNED MESSAGE\" output\n> +'\n> +\n> +test_expect_success GPGSM 'signed-tags=strip with X.509' '\n> +       git fast-export --signed-tags=strip x509-signed > output &&\n> +       test_grep ! \"SIGNED MESSAGE\" output\n> +'\n> +\n> +test_expect_success GPGSSH 'setup SSH signed tag' '\n> +       test_config gpg.format ssh &&\n> +       test_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n> +\n> +       git tag -s -m \"SSH signed tag\" ssh-signed $(git rev-parse HEAD) &&\n> +       ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))\n> +'\n> +\n> +test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '\n> +       git fast-export --signed-tags=verbatim ssh-signed > output &&\n> +       test_grep \"SSH SIGNATURE\" output\n> +'\n> +\n> +test_expect_success GPGSSH 'signed-tags=strip with SSH' '\n> +       git fast-export --signed-tags=strip ssh-signed > output &&\n> +       test_grep ! \"SSH SIGNATURE\" output\n> +'\n> +\n>  test_expect_success GPG 'set up signed commit' '\n>\n>         # Generate a commit with both \"gpgsig\" and \"encoding\" set, so\n> --\n> 2.51.0.438.g6987fc0bae\n\nLooks good.\n"},{"id":"529549","messageId":"CABPp-BGQ=3Tuik-PCerkaK4R0b1roSVLXLKs2-+E11vDrH6WaQ@mail.gmail.com","threadId":"64261","inReplyTo":"20251013084857.1646783-6-christian.couder@gmail.com","subject":"Re: [PATCH v3 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-10-24T02:03:48Z","receivedAt":"2025-10-24T02:04:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 13, 2025 at 4:49 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> Recently, eaaddf5791 (fast-import: add '--signed-commits=<mode>'\n> option, 2025-09-17) added support for controlling how signed commits\n> are handled by `git fast-import`, but there is no option yet to\n> decide about signed tags.\n>\n> To remediate that, let's add a '--signed-tags=<mode>' option to\n> `git fast-import` too.\n>\n> With this, both `git fast-export` and `git fast-import` have both\n> a '--signed-tags=<mode>' and a '--signed-commits=<mode>' supporting\n> the same <mode>s.\n>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  Documentation/git-fast-import.adoc |  5 ++\n>  builtin/fast-import.c              | 43 ++++++++++++++++\n>  t/meson.build                      |  1 +\n>  t/t9306-fast-import-signed-tags.sh | 80 ++++++++++++++++++++++++++++++\n>  4 files changed, 129 insertions(+)\n>  create mode 100755 t/t9306-fast-import-signed-tags.sh\n>\n> diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\n> index 85ed7a7270..b74179a6c8 100644\n> --- a/Documentation/git-fast-import.adoc\n> +++ b/Documentation/git-fast-import.adoc\n> @@ -66,6 +66,11 @@ 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> +       Specify how to handle signed tags.  Behaves in the same way\n> +       as the same option in linkgit:git-fast-export[1], except that\n> +       default is 'verbatim' (instead of 'abort').\n\nSorry for not catching this earlier with the --signed-commits series\n(was otherwise occupied), but this worries me.  If we ship with this\nas the default, people will come to depend upon it, and I think it's a\nbad long term default.  Long term, we'd want to check if the\nsignatures are valid and keep if so and do something else if not (e.g.\nre-sign or abort or strip).  Maybe verbatim is better than abort out\nof the options you've implemented so far, but I think setting the\ndefault now to verbatim means people start depending on it and we\ncannot change it later.  Could we change to 'abort', for both this and\n--signed-commits, before the 2.52 release, and then re-discuss once\nyou have the other options implemented?\n\n> +\n>  --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n>         Specify how to handle signed commits.  Behaves in the same way\n>         as the same option in linkgit:git-fast-export[1], except that\n> diff --git a/builtin/fast-import.c b/builtin/fast-import.c\n> index 2010e78475..60d6faa465 100644\n> --- a/builtin/fast-import.c\n> +++ b/builtin/fast-import.c\n> @@ -188,6 +188,7 @@ static int global_argc;\n>  static const char **global_argv;\n>  static const char *global_prefix;\n>\n> +static enum sign_mode signed_tag_mode = SIGN_VERBATIM;\n>  static enum sign_mode signed_commit_mode = SIGN_VERBATIM;\n\nHere's where you define the defaults, in case there is no\n--signed-{tag,commit} flags.  I think we should go with abort to allow\nus to later change to a better default once one is implemented.\n\n>  /* Memory pools */\n> @@ -2961,6 +2962,43 @@ static void parse_new_commit(const char *arg)\n>         b->last_commit = object_count_by_type[OBJ_COMMIT];\n>  }\n>\n> +static void handle_tag_signature(struct strbuf *msg, const char *name)\n> +{\n> +       size_t sig_offset = parse_signed_buffer(msg->buf, msg->len);\n> +\n> +       /* If there is no signature, there is nothing to do. */\n> +       if (sig_offset >= msg->len)\n> +               return;\n> +\n> +       switch (signed_tag_mode) {\n> +\n> +       /* First, modes that don't change anything */\n> +       case SIGN_ABORT:\n> +               die(_(\"encountered signed tag; use \"\n> +                     \"--signed-tags=<mode> to handle it\"));\n> +       case SIGN_WARN_VERBATIM:\n> +               warning(_(\"importing a tag signature verbatim for tag '%s'\"), name);\n> +               /* fallthru */\n> +       case SIGN_VERBATIM:\n> +               /* Nothing to do, the signature will be put into the imported tag. */\n> +               break;\n> +\n> +       /* Second, modes that remove the signature */\n> +       case SIGN_WARN_STRIP:\n> +               warning(_(\"stripping a tag signature for tag '%s'\"), name);\n> +               /* fallthru */\n> +       case SIGN_STRIP:\n> +               /* Truncate the buffer to remove the signature */\n> +               strbuf_setlen(msg, sig_offset);\n> +               break;\n> +\n> +       /* Third, BUG */\n> +       default:\n> +               BUG(\"invalid signed_tag_mode value %d from tag '%s'\",\n> +                   signed_tag_mode, name);\n> +       }\n\nMakes sense, you either keep the signature as is, or strip it, with\npotentially sending a warning (or error) beforehand.\n\n> +}\n> +\n>  static void parse_new_tag(const char *arg)\n>  {\n>         static struct strbuf msg = STRBUF_INIT;\n> @@ -3024,6 +3062,8 @@ static void parse_new_tag(const char *arg)\n>         /* tag payload/message */\n>         parse_data(&msg, 0, NULL);\n>\n> +       handle_tag_signature(&msg, t->name);\n> +\n>         /* build the tag object */\n>         strbuf_reset(&new_data);\n>\n> @@ -3544,6 +3584,9 @@ static int parse_one_option(const char *option)\n>         } else if (skip_prefix(option, \"signed-commits=\", &option)) {\n>                 if (parse_sign_mode(option, &signed_commit_mode))\n>                         usagef(_(\"unknown --signed-commits mode '%s'\"), option);\n> +       } else if (skip_prefix(option, \"signed-tags=\", &option)) {\n> +               if (parse_sign_mode(option, &signed_tag_mode))\n> +                       usagef(_(\"unknown --signed-tags mode '%s'\"), option);\n\nRe-using the parse_sign_mode() function previously introduced for\n--signed-commits...\n\n>         } else if (!strcmp(option, \"quiet\")) {\n>                 show_stats = 0;\n>                 quiet = 1;\n> diff --git a/t/meson.build b/t/meson.build\n> index 11376b9e25..cb8c2b4b30 100644\n> --- a/t/meson.build\n> +++ b/t/meson.build\n> @@ -1036,6 +1036,7 @@ integration_tests = [\n>    't9303-fast-import-compression.sh',\n>    't9304-fast-import-marks.sh',\n>    't9305-fast-import-signatures.sh',\n> +  't9306-fast-import-signed-tags.sh',\n>    't9350-fast-export.sh',\n>    't9351-fast-export-anonymize.sh',\n>    't9400-git-cvsserver-server.sh',\n> diff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh\n> new file mode 100755\n> index 0000000000..363619e7d1\n> --- /dev/null\n> +++ b/t/t9306-fast-import-signed-tags.sh\n> @@ -0,0 +1,80 @@\n> +#!/bin/sh\n> +\n> +test_description='git fast-import --signed-tags=<mode>'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +\n> +. ./test-lib.sh\n> +. \"$TEST_DIRECTORY/lib-gpg.sh\"\n> +\n> +test_expect_success 'set up unsigned initial commit and import repo' '\n> +       test_commit first &&\n> +       git init new\n> +'\n> +\n> +test_expect_success 'import no signed tag with --signed-tags=abort' '\n> +       git fast-export --signed-tags=verbatim >output &&\n> +       git -C new fast-import --quiet --signed-tags=abort <output\n> +'\n> +\n> +test_expect_success GPG 'set up OpenPGP signed tag' '\n> +       git tag -s -m \"OpenPGP signed tag\" openpgp-signed first &&\n> +       OPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&\n> +       git fast-export --signed-tags=verbatim openpgp-signed >output\n> +'\n> +\n> +test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '\n> +       test_must_fail git -C new fast-import --quiet --signed-tags=abort <output\n> +'\n> +\n> +test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '\n> +       git -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&\n> +       IMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&\n> +       test $OPENPGP_SIGNED = $IMPORTED &&\n> +       test_must_be_empty log\n> +'\n> +\n> +test_expect_success GPGSM 'setup X.509 signed tag' '\n> +       test_config gpg.format x509 &&\n> +       test_config user.signingkey $GIT_COMMITTER_EMAIL &&\n> +\n> +       git tag -s -m \"X.509 signed tag\" x509-signed first &&\n> +       X509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&\n> +       git fast-export --signed-tags=verbatim x509-signed >output\n> +'\n> +\n> +test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '\n> +       git -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&\n> +       test_grep \"stripping a tag signature for tag '\\''x509-signed'\\''\" log &&\n> +       IMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&\n> +       test $X509_SIGNED != $IMPORTED &&\n> +       git -C new cat-file -p x509-signed >out &&\n> +       test_grep ! \"SIGNED MESSAGE\" out\n> +'\n> +\n> +test_expect_success GPGSSH 'setup SSH signed tag' '\n> +       test_config gpg.format ssh &&\n> +       test_config user.signingkey \"${GPGSSH_KEY_PRIMARY}\" &&\n> +\n> +       git tag -s -m \"SSH signed tag\" ssh-signed first &&\n> +       SSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&\n> +       git fast-export --signed-tags=verbatim ssh-signed >output\n> +'\n> +\n> +test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '\n> +       git -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&\n> +       test_grep \"importing a tag signature verbatim for tag '\\''ssh-signed'\\''\" log &&\n> +       IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n> +       test $SSH_SIGNED = $IMPORTED\n> +'\n> +\n> +test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '\n> +       git -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&\n> +       test_must_be_empty log &&\n> +       IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&\n> +       test $SSH_SIGNED != $IMPORTED &&\n> +       git -C new cat-file -p ssh-signed >out &&\n> +       test_grep ! \"SSH SIGNATURE\" out\n> +'\n> +\n> +test_done\n> --\n> 2.51.0.438.g6987fc0bae\n\nThis all looks good to me, other than the default as noted above.\n"},{"id":"529550","messageId":"CABPp-BFgKSLQazjEwCn7rFd2BiQBGri2uNxxK7EPuEOWEAf1JQ@mail.gmail.com","threadId":"64261","inReplyTo":"CAP8UFD0Jdb2Q5dpspUOnKBJuupikJ21mJmzRyYsNOhem-JP-9g@mail.gmail.com","subject":"Re: [PATCH v3 0/5] fast-import: start controlling how tag signatures are handled","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-10-24T02:06:48Z","receivedAt":"2025-10-24T02:07:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 13, 2025 at 5:10 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Mon, Oct 13, 2025 at 10:49 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n>\n> >\n> > Changes since v2\n> > ----------------\n> >\n> > Thanks to Patrick Steinhardt, Todd Zullinger and Collin Funk who\n> > reviewed or commented on the v1 and v2.\n> >\n> > There is a single change in the first patch (doc: git-tag: stop\n> > focusing on GPG signed tags) where the description of the\n> > `-v | --verify` option of `git tag` has been improved.\n>\n> Sorry, this should have been sent in reply to the v2\n> (https://lore.kernel.org/git/20251009122457.1273701-1-christian.couder@gmail.com/)\n> instead of the v1.\n\nSorry for the delay in reviewing; the series looks really good, with\none minor exception: I'm worried about the default chosen in patch 5\n(and also the default chosen for --signed-commits in the recently\nmerged eaaddf579124 (fast-import: add '--signed-commits=<mode>'\noption, 2025-09-17)), as I commented on there.\n"},{"id":"529575","messageId":"CAP8UFD01-JDZisaqMUEGd7-WJ29r0eLcXuV3RjqeWNtoJ3-QmA@mail.gmail.com","threadId":"64261","inReplyTo":"CABPp-BGQ=3Tuik-PCerkaK4R0b1roSVLXLKs2-+E11vDrH6WaQ@mail.gmail.com","subject":"Re: [PATCH v3 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-10-24T09:27:45Z","receivedAt":"2025-10-24T09:27:59Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Oct 24, 2025 at 4:04 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Mon, Oct 13, 2025 at 4:49 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n\n> > +--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> > +       Specify how to handle signed tags.  Behaves in the same way\n> > +       as the same option in linkgit:git-fast-export[1], except that\n> > +       default is 'verbatim' (instead of 'abort').\n>\n> Sorry for not catching this earlier with the --signed-commits series\n> (was otherwise occupied), but this worries me.  If we ship with this\n> as the default, people will come to depend upon it, and I think it's a\n> bad long term default. Long term, we'd want to check if the\n> signatures are valid and keep if so and do something else if not (e.g.\n> re-sign or abort or strip).  Maybe verbatim is better than abort out\n> of the options you've implemented so far, but I think setting the\n> default now to verbatim means people start depending on it and we\n> cannot change it later.  Could we change to 'abort', for both this and\n> --signed-commits, before the 2.52 release, and then re-discuss once\n> you have the other options implemented?\n\n\"verbatim\" was already the default long before this patch series. Any\ntag signature was copied as-is, as part of the tag message. So it's\npossible that users have relied on this for a long time.\n\nFor the --signed-commits series, \"verbatim\" was also the default\nbefore the series. Even if importing commit signatures has been\nimplemented more recently and even if this is marked as experimental,\nit's the default in Git 2.51. So regular users could already rely on\nit.\n\nThe --signed-commits series has been merged to 'master' and this\nseries has recently been merged to 'next'. They aren't part of a\nrelease, but at this point I think we should send separate patches to\nchange the default if we want to do that.\n\nAs I plan to work soon on the new modes that would check signatures\nand do something based on that, and as you say that it would likely be\nbetter if such a new mode becomes the default, I am reluctant to\nchange the default mode right now, only to have to change it again\nhopefully in a few weeks or months. If you want to do it, then feel\nfree to send patches changing the default though.\n\n> This all looks good to me, other than the default as noted above.\n\nThanks for your review.\n"},{"id":"529606","messageId":"xmqqplacqr4q.fsf@gitster.g","threadId":"64261","inReplyTo":"CABPp-BGQ=3Tuik-PCerkaK4R0b1roSVLXLKs2-+E11vDrH6WaQ@mail.gmail.com","subject":"Re: [PATCH v3 5/5] fast-import: add '--signed-tags=<mode>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-24T15:03:01Z","receivedAt":"2025-10-24T15:03:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> +--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n>> +       Specify how to handle signed tags.  Behaves in the same way\n>> +       as the same option in linkgit:git-fast-export[1], except that\n>> +       default is 'verbatim' (instead of 'abort').\n>\n> Sorry for not catching this earlier with the --signed-commits series\n> (was otherwise occupied), but this worries me.  If we ship with this\n> as the default, people will come to depend upon it, and I think it's a\n> bad long term default.  Long term, we'd want to check if the\n> signatures are valid and keep if so and do something else if not (e.g.\n> re-sign or abort or strip).  Maybe verbatim is better than abort out\n> of the options you've implemented so far, but I think setting the\n> default now to verbatim means people start depending on it and we\n> cannot change it later.  Could we change to 'abort', for both this and\n> --signed-commits, before the 2.52 release, and then re-discuss once\n> you have the other options implemented?\n\nIsn't this series a response to the \"we only copy verbatim and there\nis no other choice\", which we had from the beginning of fast import\n& export?  If we knew better, we may have made it abort when we did\nthe fast import & export, but we cannot go back and change it, and\nwe cannot change the default with this series without disrupting the\nusers, so the next best thing is to make it configurable, which is\nthe point of this series (and the other one), no?\n"}]}