{"thread":{"id":"55538","subject":"[PATCH v2 0/3] fast-export, fast-import: implement signed-commits","startedAt":"2021-04-22T00:30:30Z","lastAt":"2025-03-10T22:36:33Z","messageCount":60,"participants":["Luke Shumaker","Eric Sunshine","Junio C Hamano","Elijah Newren","Christian Couder","Patrick Steinhardt","Phillip Wood"],"isPatch":true,"patchVersion":2,"patchTotal":3},"messages":[{"id":"422648","messageId":"20210422002749.2413359-1-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":null,"subject":"[PATCH v2 0/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T00:27:46Z","receivedAt":"2021-04-22T00:30:30Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export has an existing --signed-tags= flag that controls how to\nhandle tag signatures.  However, there is no equivalent for commit\nsignatures; it just silently strips the signature out of the commit\n(analogously to --signed-tags=strip).\n\nSo implement a --signed-commits= flag in fast-export, and implement\nthe receiving side of it in fast-import.\n\nI believe that this revision addresses all of the feedback so far,\nwith the exception that I have not implemented Elijah's suggestion to\nimplement a flag on fast-import to validate signatures.  While I agree\nthat this would be a useful feature, I consider it to be beyond the\nscope of this work.\n\nThis passes all of the GitHub actions CI checks, and passes all but\none of the Travis-CI checks; the failing Travis-CI check seems to be\nan unrelated 404 from `apt-get`.\nhttps://github.com/LukeShu/git/runs/2405123468\n\nLuke Shumaker (3):\n  git-fast-import.txt: add missing LF in the BNF\n    v2: no changes\n  fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n    v2:\n     - Reword commit message, based on feedback from Taylor.\n     - Fix copy-pasto in the test, noticed by Taylor.\n     - Add a comment to the tests.\n     - Fix whitespace in the tests.\n  fast-export, fast-import: implement signed-commits\n    v2:\n     - Remove erroneous remark about ordering from the commit message.\n     - Adjust the stream syntax to include the hash algorithm, as\n       suggested by brian.\n     - Add support for sha256 (based on lots of useful information from\n       brian).  It does not support multiply-signed commits.\n     - Shorten the documentation, based on feedback from Taylor.\n     - Add comments, based on feedback from Taylor.\n     - Change the default from `--signed-commits=strip` to\n       `--signed-commits=warn-strip`.  This shouldn't break anyone, and\n       means that users get useful feedback by default.\n\n Documentation/git-fast-export.txt |  11 ++-\n Documentation/git-fast-import.txt |  20 ++++-\n builtin/fast-export.c             | 123 ++++++++++++++++++++++++++----\n builtin/fast-import.c             |  23 ++++++\n t/t9350-fast-export.sh            |  88 +++++++++++++++++++++\n 5 files changed, 245 insertions(+), 20 deletions(-)\n\n-- \n2.31.1\n\nHappy hacking,\n~ Luke Shumaker\n"},{"id":"422649","messageId":"20210422002749.2413359-2-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210422002749.2413359-1-lukeshu@lukeshu.com","subject":"[PATCH v2 1/3] git-fast-import.txt: add missing LF in the BNF","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T00:27:47Z","receivedAt":"2021-04-22T00:32:40Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n Documentation/git-fast-import.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex 39cfa05b28..458af0a2d6 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -437,7 +437,7 @@ change to the project.\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n-\t('encoding' SP <encoding>)?\n+\t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n \t('merge' SP <commit-ish> LF)*\n-- \n2.31.1\n\n"},{"id":"422650","messageId":"20210422002749.2413359-3-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210422002749.2413359-1-lukeshu@lukeshu.com","subject":"[PATCH v2 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T00:27:48Z","receivedAt":"2021-04-22T00:34:52Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nThe --signed-tags= option takes one of five arguments specifying how to\nhandle signed tags during export.  Among these arguments, 'strip' is to\n'warn-strip' as 'verbatim' is to 'warn' (the unmentioned argument is\n'abort', which stops the fast-export process entirely).  That is,\nsignatures are either stripped or copied verbatim while exporting, with\nor without a warning.\n\nMatch the pattern and rename 'warn' to 'warn-verbatim' to make it clear\nthat it instructs fast-export to copy signatures verbatim.\n\nTo maintain backwards compatibility, 'warn' is still recognized as\nan undocumented alias.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\nv2:\n - Reword commit message based on feedback from Taylor.\n - Fix copy-pasto in the test, noticed by Taylor.\n - Add a comment to the tests.\n - Fix whitespace in the tests.\n\n Documentation/git-fast-export.txt |  6 +++---\n builtin/fast-export.c             |  2 +-\n t/t9350-fast-export.sh            | 18 ++++++++++++++++++\n 3 files changed, 22 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex 1978dbdc6a..d4a2bfe037 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -27,7 +27,7 @@ OPTIONS\n \tInsert 'progress' statements every <n> objects, to be shown by\n \t'git fast-import' during import.\n \n---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n+--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n \tafter the export can change the tag names (which can also happen\n \twhen excluding revisions) the signatures will not match.\n@@ -36,8 +36,8 @@ When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n-exported and with 'warn', they will be exported, but you will see a\n-warning.\n+exported and with 'warn-verbatim', they will be exported, but you will\n+see a warning.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 85a76e0ef8..d121dd2ee6 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -55,7 +55,7 @@ static int parse_opt_signed_tag_mode(const struct option *opt,\n \t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n \t\tsigned_tag_mode = VERBATIM;\n-\telse if (!strcmp(arg, \"warn\"))\n+\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n \t\tsigned_tag_mode = WARN;\n \telse if (!strcmp(arg, \"warn-strip\"))\n \t\tsigned_tag_mode = WARN_STRIP;\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 409b48e244..36b84eff36 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -253,6 +253,24 @@ test_expect_success 'signed-tags=verbatim' '\n \n '\n \n+test_expect_success 'signed-tags=warn-verbatim' '\n+\n+\tgit fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n+# 'warn' is an undocumented alias for 'warn-verbatim', for backward\n+# compatibility; test that it keeps working.\n+test_expect_success 'signed-tags=warn' '\n+\n+\tgit fast-export --signed-tags=warn sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n test_expect_success 'signed-tags=strip' '\n \n \tgit fast-export --signed-tags=strip sign-your-name > output &&\n-- \n2.31.1\n\n"},{"id":"422651","messageId":"20210422002749.2413359-4-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210422002749.2413359-1-lukeshu@lukeshu.com","subject":"[PATCH v2 3/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T00:27:49Z","receivedAt":"2021-04-22T00:37:00Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export has an existing --signed-tags= flag that controls how to\nhandle tag signatures.  However, there is no equivalent for commit\nsignatures; it just silently strips the signature out of the commit\n(analogously to --signed-tags=strip).\n\nWhile signatures are generally problematic for fast-export/fast-import\n(because hashes are likely to change), if they're going to support tag\nsignatures, there's no reason to not also support commit signatures.\n\nSo, implement signed-commits.\n\nOn the fast-export side, try to be as much like signed-tags as possible,\nin both implementation and in user-interface; with the exception that\nthe default should be `--signed-commits=warn-strip` (compared to the\ndefault `--signed-tags=abort`), in order to avoid breaking the\nhistorical behavior (it will now print a warning while doing that\nbehavior, though).\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\nv2:\n - Remove erroneous remark about ordering from the commit message.\n - Adjust the stream syntax to include the hash algorithm, as\n   suggested by brian.\n - Add support for sha256 (based on lots of useful information from\n   brian).  It does not support multiply-signed commits.\n - Shorten the documentation, based on feedback from Taylor.\n - Add comments, based on feedback from Taylor.\n - Change the default from `--signed-commits=strip` to\n   `--signed-commits=warn-strip`.  This shouldn't break anyone, and\n   means that users get useful feedback by default.\n\n Documentation/git-fast-export.txt |   5 ++\n Documentation/git-fast-import.txt |  18 +++++\n builtin/fast-export.c             | 121 ++++++++++++++++++++++++++----\n builtin/fast-import.c             |  23 ++++++\n t/t9350-fast-export.sh            |  70 +++++++++++++++++\n 5 files changed, 222 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex d4a2bfe037..2ff1719f8b 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -39,6 +39,11 @@ warning will be displayed, with 'verbatim', they will be silently\n exported and with 'warn-verbatim', they will be exported, but you will\n see a warning.\n \n+--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n+\tSpecify how to handle signed commits.  Behaves exactly as\n+\t--signed-tags (but for commits), except that the default is\n+\t'warn-strip' rather than 'abort'.\n+\n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n \tSince revisions and files to export can be limited by path,\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex 458af0a2d6..4955c94305 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -431,12 +431,21 @@ and control the current import process.  More detailed discussion\n Create or update a branch with a new commit, recording one logical\n change to the project.\n \n+////\n+Yes, it's intentional that the 'gpgsig' line doesn't have a trailing\n+`LF`; the the definition of `data` has a byte-count prefix, so it\n+doesn't need an `LF` to act as a terminator (and `data` also already\n+includes an optional trailing `LF?` just in case you want to include\n+one).\n+////\n+\n ....\n \t'commit' SP <ref> LF\n \tmark?\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n+\t('gpgsig' SP <alg> LF data)?\n \t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n@@ -505,6 +514,15 @@ that was selected by the --date-format=<fmt> command-line option.\n See ``Date Formats'' above for the set of supported formats, and\n their syntax.\n \n+`gpgsig`\n+^^^^^^^^\n+\n+The optional `gpgsig` command is used to include a PGP/GPG signature\n+that signs the commit data.\n+\n+Here <alg> specifies which hashing algorithm is used for this\n+signature, either `sha1` or `sha256`.\n+\n `encoding`\n ^^^^^^^^^^\n The optional `encoding` command indicates the encoding of the commit\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex d121dd2ee6..2b1101d104 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -30,8 +30,11 @@ static const char *fast_export_usage[] = {\n \tNULL\n };\n \n+enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_VERBATIM_WARN, SIGN_STRIP_WARN };\n+\n static int progress;\n-static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum sign_mode signed_tag_mode = SIGN_ABORT;\n+static enum sign_mode signed_commit_mode = SIGN_STRIP_WARN;\n static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -48,21 +51,24 @@ static int anonymize;\n static struct hashmap anonymized_seeds;\n static struct revision_sources revision_sources;\n \n-static int parse_opt_signed_tag_mode(const struct option *opt,\n+static int parse_opt_sign_mode(const struct option *opt,\n \t\t\t\t     const char *arg, int unset)\n {\n-\tif (unset || !strcmp(arg, \"abort\"))\n-\t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n+\tenum sign_mode *valptr = opt->value;\n+\tif (unset)\n+\t\treturn 0;\n+\telse if (!strcmp(arg, \"abort\"))\n+\t\t*valptr = SIGN_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n-\t\tsigned_tag_mode = VERBATIM;\n+\t\t*valptr = SIGN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n-\t\tsigned_tag_mode = WARN;\n+\t\t*valptr = SIGN_VERBATIM_WARN;\n \telse if (!strcmp(arg, \"warn-strip\"))\n-\t\tsigned_tag_mode = WARN_STRIP;\n+\t\t*valptr = SIGN_STRIP_WARN;\n \telse if (!strcmp(arg, \"strip\"))\n-\t\tsigned_tag_mode = STRIP;\n+\t\t*valptr = SIGN_STRIP;\n \telse\n-\t\treturn error(\"Unknown signed-tags mode: %s\", arg);\n+\t\treturn error(\"Unknown %s mode: %s\", opt->long_name, arg);\n \treturn 0;\n }\n \n@@ -499,6 +505,60 @@ static void show_filemodify(struct diff_queue_struct *q,\n \t}\n }\n \n+static const char *find_signature(const char *begin, const char *end, const char *key)\n+{\n+\tstatic struct strbuf needle = STRBUF_INIT;\n+\tchar *bod, *eod, *eol;\n+\n+\tstrbuf_reset(&needle);\n+\tstrbuf_addch(&needle, '\\n');\n+\tstrbuf_addstr(&needle, key);\n+\tstrbuf_addch(&needle, ' ');\n+\n+\tbod = memmem(begin, end ? end - begin : strlen(begin),\n+\t\t     needle.buf, needle.len);\n+\tif (!bod)\n+\t\treturn NULL;\n+\tbod += needle.len;\n+\n+\t/*\n+\t * In the commit object, multi-line header values are stored\n+\t * by prefixing continuation lines begin with a space.  So\n+\t * within the commit object, it looks like\n+\t *\n+\t *     \"gpgsig -----BEGIN PGP SIGNATURE-----\\n\"\n+\t *     \" Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n+\t *     \" \\n\"\n+\t *     \" base64_pem_here\\n\"\n+\t *     \" -----END PGP SIGNATURE-----\\n\"\n+\t *\n+\t * So we need to look for the first '\\n' that *isn't* followed\n+\t * by a ' ' (or the first '\\0', if no such '\\n' exists).\n+\t */\n+\teod = strchrnul(bod, '\\n');\n+\twhile (eod[0] == '\\n' && eod[1] == ' ') {\n+\t\teod = strchrnul(eod+1, '\\n');\n+\t}\n+\t*eod = '\\0';\n+\n+\t/*\n+\t * We now have the value as it's stored in the commit object.\n+\t * However, we want the raw value; we want to return\n+\t *\n+\t *     \"-----BEGIN PGP SIGNATURE-----\\n\"\n+\t *     \"Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n+\t *     \"\\n\"\n+\t *     \"base64_pem_here\\n\"\n+\t *     \"-----END PGP SIGNATURE-----\\n\"\n+\t *\n+\t * So now we need to strip out all of those extra spaces.\n+\t */\n+\twhile ((eol = strstr(bod, \"\\n \")))\n+\t\tmemmove(eol+1, eol+2, strlen(eol+1));\n+\n+\treturn bod;\n+}\n+\n static const char *find_encoding(const char *begin, const char *end)\n {\n \tconst char *needle = \"\\nencoding \";\n@@ -621,6 +681,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tint saved_output_format = rev->diffopt.output_format;\n \tconst char *commit_buffer;\n \tconst char *author, *author_end, *committer, *committer_end;\n+\tconst char *signature_alg = NULL, *signature;\n \tconst char *encoding, *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n@@ -644,6 +705,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tcommitter++;\n \tcommitter_end = strchrnul(committer, '\\n');\n \tmessage = strstr(committer_end, \"\\n\\n\");\n+\tif ((signature = find_signature(committer_end, message, \"gpgsig\")))\n+\t\tsignature_alg = \"sha1\";\n+\telse if ((signature = find_signature(committer_end, message, \"gpgsig-sha256\")))\n+\t\tsignature_alg = \"sha256\";\n \tencoding = find_encoding(committer_end, message);\n \tif (message)\n \t\tmessage += 2;\n@@ -703,6 +768,29 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tprintf(\"%.*s\\n%.*s\\n\",\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n+\tif (signature)\n+\t\tswitch(signed_commit_mode) {\n+\t\tcase SIGN_ABORT:\n+\t\t\tdie(\"encountered signed commit %s\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tcase SIGN_VERBATIM_WARN:\n+\t\t\twarning(\"exporting signed commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_VERBATIM:\n+\t\t\tprintf(\"gpgsig %s\\ndata %u\\n%s\",\n+\t\t\t       signature_alg,\n+\t\t\t       (unsigned)strlen(signature),\n+\t\t\t       signature);\n+\t\t\tbreak;\n+\t\tcase SIGN_STRIP_WARN:\n+\t\t\twarning(\"stripping signature from commit %s; use\"\n+\t\t\t\t\"--signed-commits=<mode> to handle it differently\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_STRIP:\n+\t\t\tbreak;\n+\t\t}\n \tif (!reencoded && encoding)\n \t\tprintf(\"encoding %s\\n\", encoding);\n \tprintf(\"data %u\\n%s\",\n@@ -830,21 +918,21 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n \t\tif (signature)\n \t\t\tswitch(signed_tag_mode) {\n-\t\t\tcase SIGNED_TAG_ABORT:\n+\t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN:\n+\t\t\tcase SIGN_VERBATIM_WARN:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase VERBATIM:\n+\t\t\tcase SIGN_VERBATIM:\n \t\t\t\tbreak;\n-\t\t\tcase WARN_STRIP:\n+\t\t\tcase SIGN_STRIP_WARN:\n \t\t\t\twarning(\"stripping signature from tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase STRIP:\n+\t\t\tcase SIGN_STRIP:\n \t\t\t\tmessage_size = signature + 1 - message;\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1197,7 +1285,10 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"show progress after <n> objects\")),\n \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of signed tags\"),\n-\t\t\t     parse_opt_signed_tag_mode),\n+\t\t\t     parse_opt_sign_mode),\n+\t\tOPT_CALLBACK(0, \"signed-commits\", &signed_commit_mode, N_(\"mode\"),\n+\t\t\t     N_(\"select handling of signed commits\"),\n+\t\t\t     parse_opt_sign_mode),\n \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n \t\t\t     parse_opt_tag_of_filtered_mode),\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 3afa81cf9a..ee7516dd38 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -2669,10 +2669,13 @@ static struct hash_list *parse_merge(unsigned int *count)\n \n static void parse_new_commit(const char *arg)\n {\n+\tstatic struct strbuf sig = STRBUF_INIT;\n \tstatic struct strbuf msg = STRBUF_INIT;\n+\tstruct string_list siglines = STRING_LIST_INIT_NODUP;\n \tstruct branch *b;\n \tchar *author = NULL;\n \tchar *committer = NULL;\n+\tchar *sig_alg = NULL;\n \tchar *encoding = NULL;\n \tstruct hash_list *merge_list = NULL;\n \tunsigned int merge_count;\n@@ -2696,6 +2699,13 @@ static void parse_new_commit(const char *arg)\n \t}\n \tif (!committer)\n \t\tdie(\"Expected committer but didn't get one\");\n+\tif (skip_prefix(command_buf.buf, \"gpgsig \", &v)) {\n+\t\tsig_alg = xstrdup(v);\n+\t\tread_next_command();\n+\t\tparse_data(&sig, 0, NULL);\n+\t\tread_next_command();\n+\t} else\n+\t\tstrbuf_setlen(&sig, 0);\n \tif (skip_prefix(command_buf.buf, \"encoding \", &v)) {\n \t\tencoding = xstrdup(v);\n \t\tread_next_command();\n@@ -2769,10 +2779,23 @@ static void parse_new_commit(const char *arg)\n \t\tstrbuf_addf(&new_data,\n \t\t\t\"encoding %s\\n\",\n \t\t\tencoding);\n+\tif (sig_alg) {\n+\t\tif (!strcmp(sig_alg, \"sha1\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig \");\n+\t\telse if (!strcmp(sig_alg, \"sha256\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig-sha256 \");\n+\t\telse\n+\t\t\tdie(\"Expected gpgsig algorithm sha1 or sha256, got %s\", sig_alg);\n+\t\tstring_list_split_in_place(&siglines, sig.buf, '\\n', -1);\n+\t\tstrbuf_add_separated_string_list(&new_data, \"\\n \", &siglines);\n+\t\tstrbuf_addch(&new_data, '\\n');\n+\t}\n \tstrbuf_addch(&new_data, '\\n');\n \tstrbuf_addbuf(&new_data, &msg);\n+\tstring_list_clear(&siglines, 1);\n \tfree(author);\n \tfree(committer);\n+\tfree(sig_alg);\n \tfree(encoding);\n \n \tif (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 36b84eff36..45952358ca 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -8,6 +8,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n test_expect_success 'setup' '\n \n@@ -284,9 +285,78 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\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+\t# that we can test that fast-import gets the ordering correct\n+\t# between the two.\n+\ttest_config i18n.commitEncoding ISO-8859-1 &&\n+\tgit checkout -f -b commit-signing main &&\n+\techo Sign your name > file-sign &&\n+\tgit add file-sign &&\n+\tgit commit -S -m \"signed commit\" &&\n+\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=abort' '\n+\n+\ttest_must_fail git fast-export --signed-commits=abort commit-signing\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=verbatim' '\n+\n+\tgit fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\t(cd new &&\n+\t git fast-import &&\n+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-verbatim' '\n+\n+\tgit fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\t(cd new &&\n+\t git fast-import &&\n+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=strip' '\n+\n+\tgit fast-export --signed-commits=strip --reencode=no commit-signing >output &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n+\t\t(cd new &&\n+\t\t git fast-import &&\n+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-strip' '\n+\n+\tgit fast-export --signed-commits=warn-strip --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n+\t\t(cd new &&\n+\t\t git fast-import &&\n+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n+\n+'\n+\n test_expect_success 'setup submodule' '\n \n \tgit checkout -f main &&\n+\t{ git update-ref -d refs/heads/commit-signing || true; } &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n-- \n2.31.1\n\n"},{"id":"422660","messageId":"CAPig+cRk_0sevG6iVbCdMPKV7XRwkrCAvEC_YRGvPkxwZoXUuw@mail.gmail.com","threadId":"55538","inReplyTo":"20210422002749.2413359-3-lukeshu@lukeshu.com","subject":"Re: [PATCH v2 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-04-22T03:59:04Z","receivedAt":"2021-04-22T03:59:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Apr 21, 2021 at 8:34 PM Luke Shumaker <lukeshu@lukeshu.com> wrote:\n> The --signed-tags= option takes one of five arguments specifying how to\n> handle signed tags during export.  Among these arguments, 'strip' is to\n> 'warn-strip' as 'verbatim' is to 'warn' (the unmentioned argument is\n> 'abort', which stops the fast-export process entirely).  That is,\n> signatures are either stripped or copied verbatim while exporting, with\n> or without a warning.\n>\n> Match the pattern and rename 'warn' to 'warn-verbatim' to make it clear\n> that it instructs fast-export to copy signatures verbatim.\n>\n> To maintain backwards compatibility, 'warn' is still recognized as\n> an undocumented alias.\n\nMaintaining backward compatibility is good; making it undocumented is\nperhaps less than good. I understand the motivation for not wanting to\ndocument it: if it's not documented, people won't discover it, thus\nwon't use it. However, we also should take into consideration that\nthere may be scripts and documentation in the wild which use `warn`.\nIf someone comes across one of those and wants to learn what it means,\nthey won't be able to if the documentation doesn't mention it at all;\nthey'll either have to consult the source code to find out its purpose\nor post a question somewhere, hoping that someone knows the answer.\n\nSo, rather than removing it from the documentation altogether, it's\nprobably better to mention that it is a deprecated alias of\n`warn-verbatim`. As precedent, for instance, see the `dimmed-zebra`\noption in Documentation/diff-options.txt which still mentions its\n`dimmed_zebra` synonym:\n\n    dimmed-zebra::\n        Similar to 'zebra', but additional dimming of uninteresting\n        parts of moved code is performed. The bordering lines of two\n        adjacent blocks are considered interesting, the rest is\n        uninteresting. `dimmed_zebra` is a deprecated synonym.\n\n> Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n> ---\n> diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\n> @@ -27,7 +27,7 @@ OPTIONS\n> ---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n> +--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> @@ -36,8 +36,8 @@ When asking to 'abort' (which is the default), this program will die\n> -exported and with 'warn', they will be exported, but you will see a\n> -warning.\n> +exported and with 'warn-verbatim', they will be exported, but you will\n> +see a warning.\n\nSo, perhaps this could end with:\n\n    `warn` is a deprecated synonym of `warn-verbatim`.\n"},{"id":"422661","messageId":"878s5b2akb.wl-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"CAPig+cRk_0sevG6iVbCdMPKV7XRwkrCAvEC_YRGvPkxwZoXUuw@mail.gmail.com","subject":"Re: [PATCH v2 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T04:43:48Z","receivedAt":"2021-04-22T04:44:05Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"On Wed, 21 Apr 2021 21:59:04 -0600,\nEric Sunshine wrote:\n> On Wed, Apr 21, 2021 at 8:34 PM Luke Shumaker <lukeshu@lukeshu.com> wrote:\n> > The --signed-tags= option takes one of five arguments specifying how to\n> > handle signed tags during export.  Among these arguments, 'strip' is to\n> > 'warn-strip' as 'verbatim' is to 'warn' (the unmentioned argument is\n> > 'abort', which stops the fast-export process entirely).  That is,\n> > signatures are either stripped or copied verbatim while exporting, with\n> > or without a warning.\n> >\n> > Match the pattern and rename 'warn' to 'warn-verbatim' to make it clear\n> > that it instructs fast-export to copy signatures verbatim.\n> >\n> > To maintain backwards compatibility, 'warn' is still recognized as\n> > an undocumented alias.\n> \n> Maintaining backward compatibility is good; making it undocumented is\n> perhaps less than good. I understand the motivation for not wanting to\n> document it: if it's not documented, people won't discover it, thus\n> won't use it. However, we also should take into consideration that\n> there may be scripts and documentation in the wild which use `warn`.\n> If someone comes across one of those and wants to learn what it means,\n> they won't be able to if the documentation doesn't mention it at all;\n> they'll either have to consult the source code to find out its purpose\n> or post a question somewhere, hoping that someone knows the answer.\n\nFair enough.  My precedent was ee4bc3715f (fast-export: rename the\nsigned tag mode 'ignore' to 'verbatim', 2007-12-03).\n\nI guess I'll document --signed-tags=ignore too, while I'm at it.\n\n-- \nHappy hacking,\n~ Luke Shumaker\n"},{"id":"422662","messageId":"877dku3otq.wl-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"878s5b2akb.wl-lukeshu@lukeshu.com","subject":"Re: [PATCH v2 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T04:50:25Z","receivedAt":"2021-04-22T04:50:29Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"On Wed, 21 Apr 2021 22:43:48 -0600,\nLuke Shumaker wrote:\n> I guess I'll document --signed-tags=ignore too, while I'm at it.\n\nEh, nevermind, --signed-tags=ignore was only a thing for a month in\n2007, compared to 13 years that --signed-tags=warn has been the way of\ndoing things.\n\n-- \nHappy hacking,\n~ Luke Shumaker\n"},{"id":"422765","messageId":"20210423164118.693197-1-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210422002749.2413359-1-lukeshu@lukeshu.com","subject":"[PATCH v3 0/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-23T16:41:15Z","receivedAt":"2021-04-23T16:41:48Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\n(First of all, my apologies for neglecting to set the In-Reply-To on\nthe v2 patcheset.)\n\nfast-export has an existing --signed-tags= flag that controls how to\nhandle tag signatures.  However, there is no equivalent for commit\nsignatures; it just silently strips the signature out of the commit\n(analogously to --signed-tags=strip).\n\nSo implement a --signed-commits= flag in fast-export, and implement\nthe receiving side of it in fast-import.\n\nI believe that this revision addresses all of the feedback so far,\nwith the exception that I have not implemented Elijah's suggestion to\nimplement a flag on fast-import to validate signatures.  While I agree\nthat this would be a useful feature, I consider it to be beyond the\nscope of this work.\n\nThis passes all of the GitHub Actions CI checks, and passes all but\none of the Travis-CI checks; the failing Travis-CI check seems to be\nan unrelated 404 from `apt-get`.\nhttps://github.com/LukeShu/git/runs/2405123468\n\nLuke Shumaker (3):\n  git-fast-import.txt: add missing LF in the BNF\n    v2: no changes\n    v3: no changes\n  fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n    v2:\n     - Reword commit message, based on feedback from Taylor.\n     - Fix copy-pasto in the test, noticed by Taylor.\n     - Add a comment to the tests.\n     - Fix whitespace in the tests.\n    v3:\n     - Document that --signed-tags='warn' is a deprecated synonym for\n       --signed-tags='warn-verbatim', rather than leaving it\n       undocumented, based on feedback from Eric.\n  fast-export, fast-import: implement signed-commits\n    v2:\n     - Remove erroneous remark about ordering from the commit message.\n     - Adjust the stream syntax to include the hash algorithm, as\n       suggested by brian.\n     - Add support for sha256 (based on lots of useful information from\n       brian).  It does not support multiply-signed commits.\n     - Shorten the documentation, based on feedback from Taylor.\n     - Add comments, based on feedback from Taylor.\n     - Change the default from `--signed-commits=strip` to\n       `--signed-commits=warn-strip`.  This shouldn't break anyone, and\n       means that users get useful feedback by default.\n    v3: no changes\n\n Documentation/git-fast-export.txt |  13 +++-\n Documentation/git-fast-import.txt |  20 ++++-\n builtin/fast-export.c             | 123 ++++++++++++++++++++++++++----\n builtin/fast-import.c             |  23 ++++++\n t/t9350-fast-export.sh            |  88 +++++++++++++++++++++\n 5 files changed, 247 insertions(+), 20 deletions(-)\n\n-- \n2.31.1\n\nHappy hacking,\n~ Luke Shumaker\n"},{"id":"422766","messageId":"20210423164118.693197-2-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210423164118.693197-1-lukeshu@lukeshu.com","subject":"[PATCH v3 1/3] git-fast-import.txt: add missing LF in the BNF","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-23T16:41:16Z","receivedAt":"2021-04-23T16:41:56Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\nv2: no changes\nv3: no changes\n\n Documentation/git-fast-import.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex 39cfa05b28..458af0a2d6 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -437,7 +437,7 @@ change to the project.\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n-\t('encoding' SP <encoding>)?\n+\t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n \t('merge' SP <commit-ish> LF)*\n-- \n2.31.1\n\n"},{"id":"422767","messageId":"20210423164118.693197-3-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210423164118.693197-1-lukeshu@lukeshu.com","subject":"[PATCH v3 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-23T16:41:17Z","receivedAt":"2021-04-23T16:41:59Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nThe --signed-tags= option takes one of five arguments specifying how to\nhandle signed tags during export.  Among these arguments, 'strip' is to\n'warn-strip' as 'verbatim' is to 'warn' (the unmentioned argument is\n'abort', which stops the fast-export process entirely).  That is,\nsignatures are either stripped or copied verbatim while exporting, with\nor without a warning.\n\nMatch the pattern and rename 'warn' to 'warn-verbatim' to make it clear\nthat it instructs fast-export to copy signatures verbatim.\n\nTo maintain backwards compatibility, 'warn' is still recognized as\ndeprecated synonym of 'warn-verbatim'.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\nv2:\n - Reword commit message based on feedback from Taylor.\n - Fix copy-pasto in the test, noticed by Taylor.\n - Add a comment to the tests.\n - Fix whitespace in the tests.\nv3:\n - Document that --signed-tags='warn' is a deprecated synonym for\n   --signed-tags='warn-verbatim', rather than leaving it\n   undocumented, based on feedback from Eric.\n\n Documentation/git-fast-export.txt |  8 +++++---\n builtin/fast-export.c             |  2 +-\n t/t9350-fast-export.sh            | 18 ++++++++++++++++++\n 3 files changed, 24 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex 1978dbdc6a..c307839e81 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -27,7 +27,7 @@ OPTIONS\n \tInsert 'progress' statements every <n> objects, to be shown by\n \t'git fast-import' during import.\n \n---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n+--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n \tafter the export can change the tag names (which can also happen\n \twhen excluding revisions) the signatures will not match.\n@@ -36,8 +36,10 @@ When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n-exported and with 'warn', they will be exported, but you will see a\n-warning.\n+exported and with 'warn-verbatim', they will be exported, but you will\n+see a warning.\n++\n+`warn` is a deprecated synonym of `warn-verbatim`.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 85a76e0ef8..d121dd2ee6 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -55,7 +55,7 @@ static int parse_opt_signed_tag_mode(const struct option *opt,\n \t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n \t\tsigned_tag_mode = VERBATIM;\n-\telse if (!strcmp(arg, \"warn\"))\n+\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n \t\tsigned_tag_mode = WARN;\n \telse if (!strcmp(arg, \"warn-strip\"))\n \t\tsigned_tag_mode = WARN_STRIP;\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 409b48e244..892737439b 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -253,6 +253,24 @@ test_expect_success 'signed-tags=verbatim' '\n \n '\n \n+test_expect_success 'signed-tags=warn-verbatim' '\n+\n+\tgit fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n+# 'warn' is an backward-compatibility alias for 'warn-verbatim'; test\n+# that it keeps working.\n+test_expect_success 'signed-tags=warn' '\n+\n+\tgit fast-export --signed-tags=warn sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n test_expect_success 'signed-tags=strip' '\n \n \tgit fast-export --signed-tags=strip sign-your-name > output &&\n-- \n2.31.1\n\n"},{"id":"422768","messageId":"20210423164118.693197-4-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210423164118.693197-1-lukeshu@lukeshu.com","subject":"[PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-23T16:41:18Z","receivedAt":"2021-04-23T16:42:07Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export has an existing --signed-tags= flag that controls how to\nhandle tag signatures.  However, there is no equivalent for commit\nsignatures; it just silently strips the signature out of the commit\n(analogously to --signed-tags=strip).\n\nWhile signatures are generally problematic for fast-export/fast-import\n(because hashes are likely to change), if they're going to support tag\nsignatures, there's no reason to not also support commit signatures.\n\nSo, implement signed-commits.\n\nOn the fast-export side, try to be as much like signed-tags as possible,\nin both implementation and in user-interface; with the exception that\nthe default should be `--signed-commits=warn-strip` (compared to the\ndefault `--signed-tags=abort`), in order to avoid breaking the\nhistorical behavior (it will now print a warning while doing that\nbehavior, though).\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\nv2:\n - Remove erroneous remark about ordering from the commit message.\n - Adjust the stream syntax to include the hash algorithm, as\n   suggested by brian.\n - Add support for sha256 (based on lots of useful information from\n   brian).  It does not support multiply-signed commits.\n - Shorten the documentation, based on feedback from Taylor.\n - Add comments, based on feedback from Taylor.\n - Change the default from `--signed-commits=strip` to\n   `--signed-commits=warn-strip`.  This shouldn't break anyone, and\n   means that users get useful feedback by default.\nv3: no changes\n\n Documentation/git-fast-export.txt |   5 ++\n Documentation/git-fast-import.txt |  18 +++++\n builtin/fast-export.c             | 121 ++++++++++++++++++++++++++----\n builtin/fast-import.c             |  23 ++++++\n t/t9350-fast-export.sh            |  70 +++++++++++++++++\n 5 files changed, 222 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex c307839e81..b651fa993b 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -41,6 +41,11 @@ see a warning.\n +\n `warn` is a deprecated synonym of `warn-verbatim`.\n \n+--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n+\tSpecify how to handle signed commits.  Behaves exactly as\n+\t--signed-tags (but for commits), except that the default is\n+\t'warn-strip' rather than 'abort'.\n+\n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n \tSince revisions and files to export can be limited by path,\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex 458af0a2d6..4955c94305 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -431,12 +431,21 @@ and control the current import process.  More detailed discussion\n Create or update a branch with a new commit, recording one logical\n change to the project.\n \n+////\n+Yes, it's intentional that the 'gpgsig' line doesn't have a trailing\n+`LF`; the the definition of `data` has a byte-count prefix, so it\n+doesn't need an `LF` to act as a terminator (and `data` also already\n+includes an optional trailing `LF?` just in case you want to include\n+one).\n+////\n+\n ....\n \t'commit' SP <ref> LF\n \tmark?\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n+\t('gpgsig' SP <alg> LF data)?\n \t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n@@ -505,6 +514,15 @@ that was selected by the --date-format=<fmt> command-line option.\n See ``Date Formats'' above for the set of supported formats, and\n their syntax.\n \n+`gpgsig`\n+^^^^^^^^\n+\n+The optional `gpgsig` command is used to include a PGP/GPG signature\n+that signs the commit data.\n+\n+Here <alg> specifies which hashing algorithm is used for this\n+signature, either `sha1` or `sha256`.\n+\n `encoding`\n ^^^^^^^^^^\n The optional `encoding` command indicates the encoding of the commit\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex d121dd2ee6..2b1101d104 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -30,8 +30,11 @@ static const char *fast_export_usage[] = {\n \tNULL\n };\n \n+enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_VERBATIM_WARN, SIGN_STRIP_WARN };\n+\n static int progress;\n-static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum sign_mode signed_tag_mode = SIGN_ABORT;\n+static enum sign_mode signed_commit_mode = SIGN_STRIP_WARN;\n static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -48,21 +51,24 @@ static int anonymize;\n static struct hashmap anonymized_seeds;\n static struct revision_sources revision_sources;\n \n-static int parse_opt_signed_tag_mode(const struct option *opt,\n+static int parse_opt_sign_mode(const struct option *opt,\n \t\t\t\t     const char *arg, int unset)\n {\n-\tif (unset || !strcmp(arg, \"abort\"))\n-\t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n+\tenum sign_mode *valptr = opt->value;\n+\tif (unset)\n+\t\treturn 0;\n+\telse if (!strcmp(arg, \"abort\"))\n+\t\t*valptr = SIGN_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n-\t\tsigned_tag_mode = VERBATIM;\n+\t\t*valptr = SIGN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n-\t\tsigned_tag_mode = WARN;\n+\t\t*valptr = SIGN_VERBATIM_WARN;\n \telse if (!strcmp(arg, \"warn-strip\"))\n-\t\tsigned_tag_mode = WARN_STRIP;\n+\t\t*valptr = SIGN_STRIP_WARN;\n \telse if (!strcmp(arg, \"strip\"))\n-\t\tsigned_tag_mode = STRIP;\n+\t\t*valptr = SIGN_STRIP;\n \telse\n-\t\treturn error(\"Unknown signed-tags mode: %s\", arg);\n+\t\treturn error(\"Unknown %s mode: %s\", opt->long_name, arg);\n \treturn 0;\n }\n \n@@ -499,6 +505,60 @@ static void show_filemodify(struct diff_queue_struct *q,\n \t}\n }\n \n+static const char *find_signature(const char *begin, const char *end, const char *key)\n+{\n+\tstatic struct strbuf needle = STRBUF_INIT;\n+\tchar *bod, *eod, *eol;\n+\n+\tstrbuf_reset(&needle);\n+\tstrbuf_addch(&needle, '\\n');\n+\tstrbuf_addstr(&needle, key);\n+\tstrbuf_addch(&needle, ' ');\n+\n+\tbod = memmem(begin, end ? end - begin : strlen(begin),\n+\t\t     needle.buf, needle.len);\n+\tif (!bod)\n+\t\treturn NULL;\n+\tbod += needle.len;\n+\n+\t/*\n+\t * In the commit object, multi-line header values are stored\n+\t * by prefixing continuation lines begin with a space.  So\n+\t * within the commit object, it looks like\n+\t *\n+\t *     \"gpgsig -----BEGIN PGP SIGNATURE-----\\n\"\n+\t *     \" Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n+\t *     \" \\n\"\n+\t *     \" base64_pem_here\\n\"\n+\t *     \" -----END PGP SIGNATURE-----\\n\"\n+\t *\n+\t * So we need to look for the first '\\n' that *isn't* followed\n+\t * by a ' ' (or the first '\\0', if no such '\\n' exists).\n+\t */\n+\teod = strchrnul(bod, '\\n');\n+\twhile (eod[0] == '\\n' && eod[1] == ' ') {\n+\t\teod = strchrnul(eod+1, '\\n');\n+\t}\n+\t*eod = '\\0';\n+\n+\t/*\n+\t * We now have the value as it's stored in the commit object.\n+\t * However, we want the raw value; we want to return\n+\t *\n+\t *     \"-----BEGIN PGP SIGNATURE-----\\n\"\n+\t *     \"Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n+\t *     \"\\n\"\n+\t *     \"base64_pem_here\\n\"\n+\t *     \"-----END PGP SIGNATURE-----\\n\"\n+\t *\n+\t * So now we need to strip out all of those extra spaces.\n+\t */\n+\twhile ((eol = strstr(bod, \"\\n \")))\n+\t\tmemmove(eol+1, eol+2, strlen(eol+1));\n+\n+\treturn bod;\n+}\n+\n static const char *find_encoding(const char *begin, const char *end)\n {\n \tconst char *needle = \"\\nencoding \";\n@@ -621,6 +681,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tint saved_output_format = rev->diffopt.output_format;\n \tconst char *commit_buffer;\n \tconst char *author, *author_end, *committer, *committer_end;\n+\tconst char *signature_alg = NULL, *signature;\n \tconst char *encoding, *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n@@ -644,6 +705,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tcommitter++;\n \tcommitter_end = strchrnul(committer, '\\n');\n \tmessage = strstr(committer_end, \"\\n\\n\");\n+\tif ((signature = find_signature(committer_end, message, \"gpgsig\")))\n+\t\tsignature_alg = \"sha1\";\n+\telse if ((signature = find_signature(committer_end, message, \"gpgsig-sha256\")))\n+\t\tsignature_alg = \"sha256\";\n \tencoding = find_encoding(committer_end, message);\n \tif (message)\n \t\tmessage += 2;\n@@ -703,6 +768,29 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tprintf(\"%.*s\\n%.*s\\n\",\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n+\tif (signature)\n+\t\tswitch(signed_commit_mode) {\n+\t\tcase SIGN_ABORT:\n+\t\t\tdie(\"encountered signed commit %s\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tcase SIGN_VERBATIM_WARN:\n+\t\t\twarning(\"exporting signed commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_VERBATIM:\n+\t\t\tprintf(\"gpgsig %s\\ndata %u\\n%s\",\n+\t\t\t       signature_alg,\n+\t\t\t       (unsigned)strlen(signature),\n+\t\t\t       signature);\n+\t\t\tbreak;\n+\t\tcase SIGN_STRIP_WARN:\n+\t\t\twarning(\"stripping signature from commit %s; use\"\n+\t\t\t\t\"--signed-commits=<mode> to handle it differently\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_STRIP:\n+\t\t\tbreak;\n+\t\t}\n \tif (!reencoded && encoding)\n \t\tprintf(\"encoding %s\\n\", encoding);\n \tprintf(\"data %u\\n%s\",\n@@ -830,21 +918,21 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n \t\tif (signature)\n \t\t\tswitch(signed_tag_mode) {\n-\t\t\tcase SIGNED_TAG_ABORT:\n+\t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN:\n+\t\t\tcase SIGN_VERBATIM_WARN:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase VERBATIM:\n+\t\t\tcase SIGN_VERBATIM:\n \t\t\t\tbreak;\n-\t\t\tcase WARN_STRIP:\n+\t\t\tcase SIGN_STRIP_WARN:\n \t\t\t\twarning(\"stripping signature from tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase STRIP:\n+\t\t\tcase SIGN_STRIP:\n \t\t\t\tmessage_size = signature + 1 - message;\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1197,7 +1285,10 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"show progress after <n> objects\")),\n \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of signed tags\"),\n-\t\t\t     parse_opt_signed_tag_mode),\n+\t\t\t     parse_opt_sign_mode),\n+\t\tOPT_CALLBACK(0, \"signed-commits\", &signed_commit_mode, N_(\"mode\"),\n+\t\t\t     N_(\"select handling of signed commits\"),\n+\t\t\t     parse_opt_sign_mode),\n \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n \t\t\t     parse_opt_tag_of_filtered_mode),\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 3afa81cf9a..ee7516dd38 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -2669,10 +2669,13 @@ static struct hash_list *parse_merge(unsigned int *count)\n \n static void parse_new_commit(const char *arg)\n {\n+\tstatic struct strbuf sig = STRBUF_INIT;\n \tstatic struct strbuf msg = STRBUF_INIT;\n+\tstruct string_list siglines = STRING_LIST_INIT_NODUP;\n \tstruct branch *b;\n \tchar *author = NULL;\n \tchar *committer = NULL;\n+\tchar *sig_alg = NULL;\n \tchar *encoding = NULL;\n \tstruct hash_list *merge_list = NULL;\n \tunsigned int merge_count;\n@@ -2696,6 +2699,13 @@ static void parse_new_commit(const char *arg)\n \t}\n \tif (!committer)\n \t\tdie(\"Expected committer but didn't get one\");\n+\tif (skip_prefix(command_buf.buf, \"gpgsig \", &v)) {\n+\t\tsig_alg = xstrdup(v);\n+\t\tread_next_command();\n+\t\tparse_data(&sig, 0, NULL);\n+\t\tread_next_command();\n+\t} else\n+\t\tstrbuf_setlen(&sig, 0);\n \tif (skip_prefix(command_buf.buf, \"encoding \", &v)) {\n \t\tencoding = xstrdup(v);\n \t\tread_next_command();\n@@ -2769,10 +2779,23 @@ static void parse_new_commit(const char *arg)\n \t\tstrbuf_addf(&new_data,\n \t\t\t\"encoding %s\\n\",\n \t\t\tencoding);\n+\tif (sig_alg) {\n+\t\tif (!strcmp(sig_alg, \"sha1\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig \");\n+\t\telse if (!strcmp(sig_alg, \"sha256\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig-sha256 \");\n+\t\telse\n+\t\t\tdie(\"Expected gpgsig algorithm sha1 or sha256, got %s\", sig_alg);\n+\t\tstring_list_split_in_place(&siglines, sig.buf, '\\n', -1);\n+\t\tstrbuf_add_separated_string_list(&new_data, \"\\n \", &siglines);\n+\t\tstrbuf_addch(&new_data, '\\n');\n+\t}\n \tstrbuf_addch(&new_data, '\\n');\n \tstrbuf_addbuf(&new_data, &msg);\n+\tstring_list_clear(&siglines, 1);\n \tfree(author);\n \tfree(committer);\n+\tfree(sig_alg);\n \tfree(encoding);\n \n \tif (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 892737439b..e686c3b894 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -8,6 +8,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n test_expect_success 'setup' '\n \n@@ -284,9 +285,78 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\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+\t# that we can test that fast-import gets the ordering correct\n+\t# between the two.\n+\ttest_config i18n.commitEncoding ISO-8859-1 &&\n+\tgit checkout -f -b commit-signing main &&\n+\techo Sign your name > file-sign &&\n+\tgit add file-sign &&\n+\tgit commit -S -m \"signed commit\" &&\n+\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=abort' '\n+\n+\ttest_must_fail git fast-export --signed-commits=abort commit-signing\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=verbatim' '\n+\n+\tgit fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\t(cd new &&\n+\t git fast-import &&\n+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-verbatim' '\n+\n+\tgit fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\t(cd new &&\n+\t git fast-import &&\n+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=strip' '\n+\n+\tgit fast-export --signed-commits=strip --reencode=no commit-signing >output &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n+\t\t(cd new &&\n+\t\t git fast-import &&\n+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-strip' '\n+\n+\tgit fast-export --signed-commits=warn-strip --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n+\t\t(cd new &&\n+\t\t git fast-import &&\n+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n+\n+'\n+\n test_expect_success 'setup submodule' '\n \n \tgit checkout -f main &&\n+\t{ git update-ref -d refs/heads/commit-signing || true; } &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n-- \n2.31.1\n\n"},{"id":"423167","messageId":"xmqqpmyfccjb.fsf@gitster.g","threadId":"55538","inReplyTo":"20210423164118.693197-3-lukeshu@lukeshu.com","subject":"Re: [PATCH v3 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-28T03:29:12Z","receivedAt":"2021-04-28T03:29:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luke Shumaker <lukeshu@lukeshu.com> writes:\n\n> ---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n> +--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n>  \tSpecify how to handle signed tags.  Since any transformation\n>  \tafter the export can change the tag names (which can also happen\n>  \twhen excluding revisions) the signatures will not match.\n> @@ -36,8 +36,10 @@ When asking to 'abort' (which is the default), this program will die\n>  when encountering a signed tag.  With 'strip', the tags will silently\n>  be made unsigned, with 'warn-strip' they will be made unsigned but a\n>  warning will be displayed, with 'verbatim', they will be silently\n> -exported and with 'warn', they will be exported, but you will see a\n> -warning.\n> +exported and with 'warn-verbatim', they will be exported, but you will\n> +see a warning.\n> ++\n> +`warn` is a deprecated synonym of `warn-verbatim`.\n\nTwo minor points\n\n - Is it obvious to everybody what is the implication of using\n   \"verbatim\" (which in turn would bring the readers to realize why\n   it often deserves a warning)?  If not, would it make sense to\n   explain why \"verbatim\" may (may not) be a good idea in different\n   situations?\n\n - I am not sure a deprecated synonym deserves a separate paragraph.\n\n   ... silently exported, and with 'warn-verbatim' (or `warn`, a\n   deprecated synonym), they will be exported with a warning.\n\n   may be less irritating to the eyes, perhaps?\n\n> diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> index 85a76e0ef8..d121dd2ee6 100644\n> --- a/builtin/fast-export.c\n> +++ b/builtin/fast-export.c\n> @@ -55,7 +55,7 @@ static int parse_opt_signed_tag_mode(const struct option *opt,\n>  \t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n>  \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n>  \t\tsigned_tag_mode = VERBATIM;\n> -\telse if (!strcmp(arg, \"warn\"))\n> +\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n>  \t\tsigned_tag_mode = WARN;\n>  \telse if (!strcmp(arg, \"warn-strip\"))\n>  \t\tsigned_tag_mode = WARN_STRIP;\n\nIt would be preferrable to do s/WARN/WARN_VERBATIM/ at this step, as\nthe plan is to deprecate \"warn\", even if you are going to redo the\nenums in later steps.  May want to consider doing so as a clean-up\niff this topic need rerolling for other reasons.\n\n> diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> index 409b48e244..892737439b 100755\n> --- a/t/t9350-fast-export.sh\n> +++ b/t/t9350-fast-export.sh\n> @@ -253,6 +253,24 @@ test_expect_success 'signed-tags=verbatim' '\n>  \n>  '\n>  \n> +test_expect_success 'signed-tags=warn-verbatim' '\n> +\n> +\tgit fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err &&\n> +\tgrep PGP output &&\n> +\ttest -s err\n\nI didn't look at the surrounding existing tests, but in general\n\"test -s err\" is not a good ingredient in any test.  The feature you\nhappen to care about today may not stay to be be the only thing that\nwrites to the standard error stream.\n\n"},{"id":"423168","messageId":"xmqqfszbcazc.fsf@gitster.g","threadId":"55538","inReplyTo":"20210423164118.693197-4-lukeshu@lukeshu.com","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-28T04:02:47Z","receivedAt":"2021-04-28T04:03:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luke Shumaker <lukeshu@lukeshu.com> writes:\n\n> From: Luke Shumaker <lukeshu@datawire.io>\n>\n> fast-export has an existing --signed-tags= flag that controls how to\n\nDon't call a command line option \"a flag\", especially when it is not\na boolean.\n\n\"has an existing\" feels redundantly repeticious.\n\n> handle tag signatures.  However, there is no equivalent for commit\n> signatures; it just silently strips the signature out of the commit\n> (analogously to --signed-tags=strip).\n>\n> While signatures are generally problematic for fast-export/fast-import\n> (because hashes are likely to change), if they're going to support tag\n> signatures, there's no reason to not also support commit signatures.\n>\n> So, implement signed-commits.\n\nThat's misleading.  You are not inventing \"git commit --signed\"\nhere.\n\n    So implement `--signed-commits=<disposition>` that mirrors the\n    `--signed-tags=<disposition>` option.\n\n> +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> +\tSpecify how to handle signed commits.  Behaves exactly as\n> +\t--signed-tags (but for commits), except that the default is\n> +\t'warn-strip' rather than 'abort'.\n\nWhy deliberate inconsistency?  I am not sure \"historically we did a\nwrong thing\" is a good reason (if we view that silently stripping\nwas a disservice to the users, aborting would be a bugfix).\n\n> diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\n> index 458af0a2d6..4955c94305 100644\n> --- a/Documentation/git-fast-import.txt\n> +++ b/Documentation/git-fast-import.txt\n> diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> index d121dd2ee6..2b1101d104 100644\n> --- a/builtin/fast-export.c\n> +++ b/builtin/fast-export.c\n> @@ -30,8 +30,11 @@ static const char *fast_export_usage[] = {\n>  \tNULL\n>  };\n>  \n> +enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_VERBATIM_WARN, SIGN_STRIP_WARN };\n> +\n>  static int progress;\n> -static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n\nGiving the enum values consistent prefix \"SIGN_\" is a great\nimprovement.  On the other hand, swapping the word order,\ne.g. WARN_STRIP to SIGN_STRIP_WARN, is unwarranted.\n\n> +static enum sign_mode signed_tag_mode = SIGN_ABORT;\n> +static enum sign_mode signed_commit_mode = SIGN_STRIP_WARN;\n\nI think it is safer to abort for both and sell it as a bugfix\n(\"silently stripping commit signatures was wrong. we should abort\nthe same way by default when encountering a signed tag\").\n\n> -static int parse_opt_signed_tag_mode(const struct option *opt,\n> +static int parse_opt_sign_mode(const struct option *opt,\n>  \t\t\t\t     const char *arg, int unset)\n>  {\n> -\tif (unset || !strcmp(arg, \"abort\"))\n> -\t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n> +\tenum sign_mode *valptr = opt->value;\n> +\tif (unset)\n> +\t\treturn 0;\n> +\telse if (!strcmp(arg, \"abort\"))\n> +\t\t*valptr = SIGN_ABORT;\n>  \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n> -\t\tsigned_tag_mode = VERBATIM;\n> +\t\t*valptr = SIGN_VERBATIM;\n\nInteresting and not a new issue at all, but \"ignore\" is a confusing\nsymonym to \"verbatim\"---I would have expected \"ignore\", if accepted\nas a choice, would strip the signature.  Not documenting it is\nprobably good, but perhaps we would eventually remove it?\n\n> @@ -499,6 +505,60 @@ static void show_filemodify(struct diff_queue_struct *q,\n>  \t}\n>  }\n>  \n> +static const char *find_signature(const char *begin, const char *end, const char *key)\n\nThis is only for in-header signature used in commit objects, and not\nfor the traditional \"attached to the end\" signature used in tag\nobjects, right?\n\nThe name of this function should be designed to answer the above\nquestion, but find_signature() that does not say either commit or\ntag implies it can accept both (which would be a horrible interface,\nthough).  If this is only for in-header signature, rename it to make\nsure that the fact is readable out of its name?\n\n> +{\n> +\tstatic struct strbuf needle = STRBUF_INIT;\n> +\tchar *bod, *eod, *eol;\n> +\n> +\tstrbuf_reset(&needle);\n> +\tstrbuf_addch(&needle, '\\n');\n> +\tstrbuf_addstr(&needle, key);\n> +\tstrbuf_addch(&needle, ' ');\n\nstrbuf_addf(), perhaps?\n\n> +\tbod = memmem(begin, end ? end - begin : strlen(begin),\n> +\t\t     needle.buf, needle.len);\n> +\tif (!bod)\n> +\t\treturn NULL;\n> +\tbod += needle.len;\n> +\n> +\t/*\n> +\t * In the commit object, multi-line header values are stored\n> +\t * by prefixing continuation lines begin with a space.  So\n\n\"by prefixig continuation lines with a space\"\n\n> +\t * within the commit object, it looks like\n> +\t *\n> +\t *     \"gpgsig -----BEGIN PGP SIGNATURE-----\\n\"\n> +\t *     \" Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n> +\t *     \" \\n\"\n> +\t *     \" base64_pem_here\\n\"\n> +\t *     \" -----END PGP SIGNATURE-----\\n\"\n> +\t *\n> +\t * So we need to look for the first '\\n' that *isn't* followed\n> +\t * by a ' ' (or the first '\\0', if no such '\\n' exists).\n> +\t */\n\n> +\teod = strchrnul(bod, '\\n');\n> +\twhile (eod[0] == '\\n' && eod[1] == ' ') {\n> +\t\teod = strchrnul(eod+1, '\\n');\n> +\t}\n\nSP on both sides of '+'; no {} around a block that consists of a\nsingle statement.\n\n> +\t*eod = '\\0';\n\nThe begin and end pointers pointed to a piece of memory that is\nsupposed to be read-only, but this pointer points into that region\nof memory and then updates a byte?  The function signature is\nmisleading---if you intend to muck with the string, accept them as\nmutable pointers.\n\nBetter yet, don't butcher the region of memory pointed by the\n\"message\" variable the caller uses to keep reading from the\nremainder of the commit object buffer with this and memmove()\nbelow.  Perhaps have the caller pass a strbuf to fill in the\nsignature found by this helper as another parameter, and then return\na bool \"Yes, I found a sig\" as its return value?\n\n> +\n> +\t/*\n> +\t * We now have the value as it's stored in the commit object.\n> +\t * However, we want the raw value; we want to return\n> +\t *\n> +\t *     \"-----BEGIN PGP SIGNATURE-----\\n\"\n> +\t *     \"Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n> +\t *     \"\\n\"\n> +\t *     \"base64_pem_here\\n\"\n> +\t *     \"-----END PGP SIGNATURE-----\\n\"\n> +\t *\n> +\t * So now we need to strip out all of those extra spaces.\n> +\t */\n> +\twhile ((eol = strstr(bod, \"\\n \")))\n> +\t\tmemmove(eol+1, eol+2, strlen(eol+1));\n\nBesides, this is O(n^2), isn't it, as it always starts scanning at\nbod while there are lines in the signature block to be processed, it\nneeds to skip over the lines that the loop already has processed.\n\nI'd stop here for now, as there should be enough to polish.\n\nThanks.\n"},{"id":"423290","messageId":"87pmycq5h7.wl-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"xmqqpmyfccjb.fsf@gitster.g","subject":"Re: [PATCH v3 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-29T19:02:28Z","receivedAt":"2021-04-29T19:02:42Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"On Tue, 27 Apr 2021 21:29:12 -0600,\nJunio C Hamano wrote:\n> Luke Shumaker <lukeshu@lukeshu.com> writes:\n> \n> > ---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n> > +--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> >  \tSpecify how to handle signed tags.  Since any transformation\n> >  \tafter the export can change the tag names (which can also happen\n> >  \twhen excluding revisions) the signatures will not match.\n> > @@ -36,8 +36,10 @@ When asking to 'abort' (which is the default), this program will die\n> >  when encountering a signed tag.  With 'strip', the tags will silently\n> >  be made unsigned, with 'warn-strip' they will be made unsigned but a\n> >  warning will be displayed, with 'verbatim', they will be silently\n> > -exported and with 'warn', they will be exported, but you will see a\n> > -warning.\n> > +exported and with 'warn-verbatim', they will be exported, but you will\n> > +see a warning.\n> > ++\n> > +`warn` is a deprecated synonym of `warn-verbatim`.\n> \n> Two minor points\n> \n>  - Is it obvious to everybody what is the implication of using\n>    \"verbatim\" (which in turn would bring the readers to realize why\n>    it often deserves a warning)?  If not, would it make sense to\n>    explain why \"verbatim\" may (may not) be a good idea in different\n>    situations?\n\nI had assumed that the above paragraph\n\n|\tSpecify how to handle signed tags.  Since any transformation\n|\tafter the export can change the tag names (which can also happen\n|\twhen excluding revisions) the signatures will not match.\n\nwas adaquate for that purpose, but we can maybe do better?\n\n>  - I am not sure a deprecated synonym deserves a separate paragraph.\n\nFair enough.  My thinking was to keep the deprecation separate from\nthe main \"happy path\" text.\n\nHow about:\n\n| Specify how to handle signed tags.  Since any transformation after the\n| export (or during the export, such as excluding revisions) can change\n| the hashes being signed, the signatures may not match.\n|\n| When asking to 'abort' (which is the default), this program will die\n| when encountering a signed tag.  With 'strip', the tags will silently\n| be made unsigned, with 'warn-strip' they will be made unsigned but a\n| warning will be displayed, with 'verbatim', they will be silently\n| exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n| they will be exported, but you will see a warning.  'verbatim' should\n| not be used unless you know that no transformations affecting tags\n| will be performed, or unless you do not care that the resulting tag\n| will have an invalid signature.\n\n?\n\n> > diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> > index 85a76e0ef8..d121dd2ee6 100644\n> > --- a/builtin/fast-export.c\n> > +++ b/builtin/fast-export.c\n> > @@ -55,7 +55,7 @@ static int parse_opt_signed_tag_mode(const struct option *opt,\n> >  \t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n> >  \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n> >  \t\tsigned_tag_mode = VERBATIM;\n> > -\telse if (!strcmp(arg, \"warn\"))\n> > +\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n> >  \t\tsigned_tag_mode = WARN;\n> >  \telse if (!strcmp(arg, \"warn-strip\"))\n> >  \t\tsigned_tag_mode = WARN_STRIP;\n> \n> It would be preferrable to do s/WARN/WARN_VERBATIM/ at this step, as\n> the plan is to deprecate \"warn\", even if you are going to redo the\n> enums in later steps.  May want to consider doing so as a clean-up\n> iff this topic need rerolling for other reasons.\n\nAck.\n\n> > diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> > index 409b48e244..892737439b 100755\n> > --- a/t/t9350-fast-export.sh\n> > +++ b/t/t9350-fast-export.sh\n> > @@ -253,6 +253,24 @@ test_expect_success 'signed-tags=verbatim' '\n> >  \n> >  '\n> >  \n> > +test_expect_success 'signed-tags=warn-verbatim' '\n> > +\n> > +\tgit fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err &&\n> > +\tgrep PGP output &&\n> > +\ttest -s err\n> \n> I didn't look at the surrounding existing tests, but in general\n> \"test -s err\" is not a good ingredient in any test.  The feature you\n> happen to care about today may not stay to be be the only thing that\n> writes to the standard error stream.\n\nYeah, that line made me nervous, but I figured if it was good enough\nfor the existing 'warn-strip' test, then it was good enough for\n'warn-verbatim' too.\n\n-- \nHappy hacking,\n~ Luke Shumaker\n"},{"id":"423296","messageId":"87o8dwq2hv.wl-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"xmqqfszbcazc.fsf@gitster.g","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-29T20:06:52Z","receivedAt":"2021-04-29T20:06:56Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"On Tue, 27 Apr 2021 22:02:47 -0600,\nJunio C Hamano wrote:\n> \n> Luke Shumaker <lukeshu@lukeshu.com> writes:\n> \n> > fast-export has an existing --signed-tags= flag that controls how to\n> \n> Don't call a command line option \"a flag\", especially when it is not\n> a boolean.\n\nGood to know, perhaps this should be mentioned in CodingGuidelines or\nSubmittingPatches.txt?  I see lots of instances in the docs of \"flag\"\nbeing used.\n\n> \"has an existing\" feels redundantly repeticious.\n\nI guess I did this to make it clearer that that paragraph is\ndescribing the state of things before the patch, rather than after the\npatch.  This is of course the required way of writing messages for\ngit.git, but I worded it that way to make it clearer to reviewers that\nI'm following that requirement (especially since I haven't gotten a\ncommit landed in git.git before).\n\n> > So, implement signed-commits.\n> \n> That's misleading.  You are not inventing \"git commit --signed\"\n> here.\n> \n>     So implement `--signed-commits=<disposition>` that mirrors the\n>     `--signed-tags=<disposition>` option.\n\nAck.\n\n> > +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> > +\tSpecify how to handle signed commits.  Behaves exactly as\n> > +\t--signed-tags (but for commits), except that the default is\n> > +\t'warn-strip' rather than 'abort'.\n> \n> Why deliberate inconsistency?  I am not sure \"historically we did a\n> wrong thing\" is a good reason (if we view that silently stripping\n> was a disservice to the users, aborting would be a bugfix).\n\nI *almost* agree.  I agree in principle, but disagree in practice\nbecause I know that it would break a bunch of existing tooling,\nincluding git-filter-repo.\n\n> >  \n> > +enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_VERBATIM_WARN, SIGN_STRIP_WARN };\n> > +\n> >  static int progress;\n> > -static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n> \n> Giving the enum values consistent prefix \"SIGN_\" is a great\n> improvement.  On the other hand, swapping the word order,\n> e.g. WARN_STRIP to SIGN_STRIP_WARN, is unwarranted.\n\nFlipping it around made the switch statements read better, I thought.\nBut I can change it back.\n\n> > +static enum sign_mode signed_tag_mode = SIGN_ABORT;\n> > +static enum sign_mode signed_commit_mode = SIGN_STRIP_WARN;\n> \n> I think it is safer to abort for both and sell it as a bugfix\n> (\"silently stripping commit signatures was wrong. we should abort\n> the same way by default when encountering a signed tag\").\n> \n> > -static int parse_opt_signed_tag_mode(const struct option *opt,\n> > +static int parse_opt_sign_mode(const struct option *opt,\n> >  \t\t\t\t     const char *arg, int unset)\n> >  {\n> > -\tif (unset || !strcmp(arg, \"abort\"))\n> > -\t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n> > +\tenum sign_mode *valptr = opt->value;\n> > +\tif (unset)\n> > +\t\treturn 0;\n> > +\telse if (!strcmp(arg, \"abort\"))\n> > +\t\t*valptr = SIGN_ABORT;\n> >  \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n> > -\t\tsigned_tag_mode = VERBATIM;\n> > +\t\t*valptr = SIGN_VERBATIM;\n> \n> Interesting and not a new issue at all, but \"ignore\" is a confusing\n> symonym to \"verbatim\"---I would have expected \"ignore\", if accepted\n> as a choice, would strip the signature.  Not documenting it is\n> probably good, but perhaps we would eventually remove it?\n\nIndeed, it was renamed from \"ignore\" to \"verbatim\" because \"ignore\"\nwas such a confusing name.  It was renamed (in ee4bc3715f\n(fast-export: rename the signed tag mode 'ignore' to 'verbatim',\n2007-12-03)) by the original fast-export author, pretty much\nimmediately after fast-export was originally introduced.  There was\nnever a released version of Git that had fast-export but didn't have\nthe 'ignore'->'verbatim' rename (fast-export was first released in\nv1.5.4, and the rename was already present then).\n\n> > @@ -499,6 +505,60 @@ static void show_filemodify(struct diff_queue_struct *q,\n> >  \t}\n> >  }\n> >  \n> > +static const char *find_signature(const char *begin, const char *end, const char *key)\n> \n> This is only for in-header signature used in commit objects, and not\n> for the traditional \"attached to the end\" signature used in tag\n> objects, right?\n> \n> The name of this function should be designed to answer the above\n> question, but find_signature() that does not say either commit or\n> tag implies it can accept both (which would be a horrible interface,\n> though).  If this is only for in-header signature, rename it to make\n> sure that the fact is readable out of its name?\n> \n> > +{\n> > +\tstatic struct strbuf needle = STRBUF_INIT;\n> > +\tchar *bod, *eod, *eol;\n> > +\n> > +\tstrbuf_reset(&needle);\n> > +\tstrbuf_addch(&needle, '\\n');\n> > +\tstrbuf_addstr(&needle, key);\n> > +\tstrbuf_addch(&needle, ' ');\n> \n> strbuf_addf(), perhaps?\n\nCurrently, strbuf_addf is only used by fast-export.c in the\n\"anonymize_\" functions.  I took that to mean \"avoid strbuf_addf if you\ncan\", figuring that fast-export and fast-import seem to go reasonably\nfar out of their way to avoid dynamic things (like printf) in the\nhappy-path.\n\nBut I can change if it I'm mis-reading fast-export's paranoia.\n\n> > +\tbod = memmem(begin, end ? end - begin : strlen(begin),\n> > +\t\t     needle.buf, needle.len);\n> > +\tif (!bod)\n> > +\t\treturn NULL;\n> > +\tbod += needle.len;\n> > +\n> > +\t/*\n> > +\t * In the commit object, multi-line header values are stored\n> > +\t * by prefixing continuation lines begin with a space.  So\n> \n> \"by prefixig continuation lines with a space\"\n\nOops.\n\n> > +\t * within the commit object, it looks like\n> > +\t *\n> > +\t *     \"gpgsig -----BEGIN PGP SIGNATURE-----\\n\"\n> > +\t *     \" Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n> > +\t *     \" \\n\"\n> > +\t *     \" base64_pem_here\\n\"\n> > +\t *     \" -----END PGP SIGNATURE-----\\n\"\n> > +\t *\n> > +\t * So we need to look for the first '\\n' that *isn't* followed\n> > +\t * by a ' ' (or the first '\\0', if no such '\\n' exists).\n> > +\t */\n> \n> > +\teod = strchrnul(bod, '\\n');\n> > +\twhile (eod[0] == '\\n' && eod[1] == ' ') {\n> > +\t\teod = strchrnul(eod+1, '\\n');\n> > +\t}\n> \n> SP on both sides of '+'; no {} around a block that consists of a\n> single statement.\n\nAck.\n\n> > +\t*eod = '\\0';\n> \n> The begin and end pointers pointed to a piece of memory that is\n> supposed to be read-only, but this pointer points into that region\n> of memory and then updates a byte?  The function signature is\n> misleading---if you intend to muck with the string, accept them as\n> mutable pointers.\n> \n> Better yet, don't butcher the region of memory pointed by the\n> \"message\" variable the caller uses to keep reading from the\n> remainder of the commit object buffer with this and memmove()\n> below.  Perhaps have the caller pass a strbuf to fill in the\n> signature found by this helper as another parameter, and then return\n> a bool \"Yes, I found a sig\" as its return value?\n\nThat all sounds very sane, but I was mimicking the existing\n`find_encoding`.\n\nYou aren't supposed to modify the memory from get_commit_buffer, but\nfast-export does anyway.  I assume that Johannes knew what he was\ndoing when he wrote it (that it's safe because fast-export never\ntraverses the same object twice?) and that he did it as an\nallocation-avoiding optimization.\n\nPart of me thinks that it would be better to just use the standard\nfunctions for this, like read_commit_extra_headers or\nfind_commit_header?\n\n> > +\n> > +\t/*\n> > +\t * We now have the value as it's stored in the commit object.\n> > +\t * However, we want the raw value; we want to return\n> > +\t *\n> > +\t *     \"-----BEGIN PGP SIGNATURE-----\\n\"\n> > +\t *     \"Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n> > +\t *     \"\\n\"\n> > +\t *     \"base64_pem_here\\n\"\n> > +\t *     \"-----END PGP SIGNATURE-----\\n\"\n> > +\t *\n> > +\t * So now we need to strip out all of those extra spaces.\n> > +\t */\n> > +\twhile ((eol = strstr(bod, \"\\n \")))\n> > +\t\tmemmove(eol+1, eol+2, strlen(eol+1));\n> \n> Besides, this is O(n^2), isn't it, as it always starts scanning at\n> bod while there are lines in the signature block to be processed, it\n> needs to skip over the lines that the loop already has processed.\n\nIndeed, I'm embarrassed that made it in to something I submitted.\n\n-- \nHappy hacking,\n~ Luke Shumaker\n"},{"id":"423301","messageId":"CABPp-BHhfT3b=UyWOXACrBb6nw86n74thNAx7DUDF0YNOcA-yA@mail.gmail.com","threadId":"55538","inReplyTo":"87o8dwq2hv.wl-lukeshu@lukeshu.com","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-04-29T22:38:57Z","receivedAt":"2021-04-29T22:39:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Apr 29, 2021 at 1:06 PM Luke Shumaker <lukeshu@lukeshu.com> wrote:\n>\n> On Tue, 27 Apr 2021 22:02:47 -0600,\n> Junio C Hamano wrote:\n> >\n> > Luke Shumaker <lukeshu@lukeshu.com> writes:\n> >\n\n> > > +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> > > +   Specify how to handle signed commits.  Behaves exactly as\n> > > +   --signed-tags (but for commits), except that the default is\n> > > +   'warn-strip' rather than 'abort'.\n> >\n> > Why deliberate inconsistency?  I am not sure \"historically we did a\n> > wrong thing\" is a good reason (if we view that silently stripping\n> > was a disservice to the users, aborting would be a bugfix).\n>\n> I *almost* agree.  I agree in principle, but disagree in practice\n> because I know that it would break a bunch of existing tooling,\n> including git-filter-repo.\n\nI understand that fast-export's behavior in the past matched what\n--signed-commits=warn-strip would now do, and thus you wanted to\nselect it for backward compatibility.  But throwing an error and\nmaking the user choose when they are potentially losing data seems\nlike a safer choice to me.\n\nI do get that we might have to use warn-strip as the default anyway\njust because some existing tools might rely on it, but do you have any\nexamples outside of git-filter-repo?  Given the filter-repo bug\nreports I've gotten with users being surprised at commit signatures\nbeing stripped (despite the fact that this is documented -- users\ndon't always read the documentation), I'd argue that changing to\n--signed-commits=abort as the default is probably a good bugfix for\nboth fast-export and for filter-repo.\n\nClearly, it'd probably make sense for filter-repo to also add an\noption for the user to select to: (0) abort if commit signatures are\nfound, (1) strip commit signatures, (2) retain commit signatures even\nif they are invalid, or (3) only retain commit signatures if they are\nvalid.  In the past, we could only reasonably do (1).  Your series\nmakes (0) and (2) possible.  More work in fast-import would be needed\nto make (3) a possibility, so I wouldn't be able to add it to\nfilter-repo yet, but I could add the other options.\n"},{"id":"423303","messageId":"xmqqim44fyjj.fsf@gitster.g","threadId":"55538","inReplyTo":"CABPp-BHhfT3b=UyWOXACrBb6nw86n74thNAx7DUDF0YNOcA-yA@mail.gmail.com","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-29T23:42:24Z","receivedAt":"2021-04-29T23:42:32Z","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> I do get that we might have to use warn-strip as the default anyway\n> just because some existing tools might rely on it, but do you have any\n> examples outside of git-filter-repo?  Given the filter-repo bug\n> reports I've gotten with users being surprised at commit signatures\n> being stripped (despite the fact that this is documented -- users\n> don't always read the documentation), I'd argue that changing to\n> --signed-commits=abort as the default is probably a good bugfix for\n> both fast-export and for filter-repo.\n\nThanks.  The \"filter-repo already gets bug reports from the users\"\nis a valuable input when deciding if it is reasonable to sell the\nbehaviour change as a bugfix to our users.\n\nPerhaps teaching fast-export to pay attention to two environment\nvariables that say \"when no --signed-{tag,commit}=<disposition>\"\ncommand line option is given, use this behaviour\" would be a good\nenough escape hatch for existing tools and their users, while they\nare waiting for their tools to get updated with the new option you\nare planning to add?\n\nAlso, I am glad that you brought up another possible behaviour that\nLuke's patch did not add.  Exporting existing signatures that may\nbecome invalid and deciding what to do with them on the receiving\nend would be a good option to have.  And that would most likely have\nto be done at \"fast-import\" end, as a commit that \"fast-export\"\nexpected to retain its object name if its export stream were applied\nas-is may not retain the object name when the export stream gets\npreprocessed before being fed to \"fast-import\".\n\n\n"},{"id":"423304","messageId":"xmqqeeesfxl2.fsf@gitster.g","threadId":"55538","inReplyTo":"87pmycq5h7.wl-lukeshu@lukeshu.com","subject":"Re: [PATCH v3 2/3] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-30T00:03:05Z","receivedAt":"2021-04-30T00:03:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luke Shumaker <lukeshu@lukeshu.com> writes:\n\n> How about:\n>\n> | Specify how to handle signed tags.  Since any transformation after the\n> | export (or during the export, such as excluding revisions) can change\n> | the hashes being signed, the signatures may not match.\n\nI find it a bit worrying that it is unclear what the signature may\nnot match.  Knowing Git, I know the answer is \"contents that is\nsigned\", and I want to make sure it is clear for all readers.\n\nWould \"may become invalid\" be better?  I dunno.\n\n> | When asking to 'abort' (which is the default), this program will die\n> | when encountering a signed tag.  With 'strip', the tags will silently\n> | be made unsigned, with 'warn-strip' they will be made unsigned but a\n> | warning will be displayed, with 'verbatim', they will be silently\n> | exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n> | they will be exported, but you will see a warning.  'verbatim' should\n> | not be used unless you know that no transformations affecting tags\n> | will be performed, or unless you do not care that the resulting tag\n> | will have an invalid signature.\n\nOK.\n\nAs the current version of \"fast-import\" has no way to specify what\nis done to incoming signed tags, it may be the best we can do to\ndiscourage 'verbatim'.  But if it learns \"--signed-tags=<disposition>\",\nI think the resulting ecosystem would become much better.\n\nIn the ideal world, I would imagine that we want to encourage to\nalways write out the original signatures to the export stream, let\nany intermediary filters process the stream, and at the very end\nstage at fast-import, have the --signed-commit/tag option to control\nwhat is done to such signatures.  The set of plausible options are\nwhat you invented for the export side in this series, plus \"if the\nsignature still matches, keep it, otherwise strip with warning\".\n\nIf we want to get closer to such an ideal world (you can point out I\nam wrong and why such a world is not ideal, of course, though), we\nprobably do not want to add \"--signed-commit\" to \"fast-export\", as\nit will have to get deprecated when the ideal world happens.\nRather, the future would deprecate the existing \"--signed-tags\"\noption from \"fast-export\" instead.\n"},{"id":"423312","messageId":"CABPp-BGUrOtHcu-o2xq-3xc3f=9wy2oxcL_4-ays+ejCg8i+sA@mail.gmail.com","threadId":"55538","inReplyTo":"xmqqim44fyjj.fsf@gitster.g","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-04-30T02:23:13Z","receivedAt":"2021-04-30T02:23:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Apr 29, 2021 at 4:42 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > I do get that we might have to use warn-strip as the default anyway\n> > just because some existing tools might rely on it, but do you have any\n> > examples outside of git-filter-repo?  Given the filter-repo bug\n> > reports I've gotten with users being surprised at commit signatures\n> > being stripped (despite the fact that this is documented -- users\n> > don't always read the documentation), I'd argue that changing to\n> > --signed-commits=abort as the default is probably a good bugfix for\n> > both fast-export and for filter-repo.\n>\n> Thanks.  The \"filter-repo already gets bug reports from the users\"\n> is a valuable input when deciding if it is reasonable to sell the\n> behaviour change as a bugfix to our users.\n>\n> Perhaps teaching fast-export to pay attention to two environment\n> variables that say \"when no --signed-{tag,commit}=<disposition>\"\n> command line option is given, use this behaviour\" would be a good\n> enough escape hatch for existing tools and their users, while they\n> are waiting for their tools to get updated with the new option you\n> are planning to add?\n\nAs far as git filter-repo is concerned, you can immediately introduce\n--signed-commit and give it a default value of abort with no\ndeprecation period.  filter-repo already has to check git versions for\navailable command line options, so one more wouldn't hurt.  And a\ndefault of \"abort\" seems more user friendly, as it gives users a\nchance to be aware of and handle their data appropriately.\n\nOf course, there are a few factors that make filter-repo more tolerant\nof upstream changes: I don't expect people to user filter-repo often\n(it's a once-in-a-blue-moon rewrite), I don't expect them to use it in\nautomated processes, I tend to make releases that coincide in timing\nwith git releases (so I'll just release a git-filter-repo 2.32.0 the\nday you release git 2.32, and it'll come with an option to handle this\nnew default), and filter-repo includes the following disclaimer in its\ndocumentation:\n\n\"\"\"\nI assume that people use filter-repo for one-shot conversions, not\nongoing data transfers. I explicitly reserve the right to change any\nAPI in filter-repo based on this presumption (and a comment to this\neffect is found in multiple places in the code and examples). You have\nbeen warned.\n\"\"\"\n\nSo, if it's just for filter-repo, then I'd say just change\nfast-export's default now.  If you're concerned with\n--signed-commit=abort being a changed default being too drastic for\nother users or tools, then the environment variable escape hatch\nsounds reasonable to me.\n\nPersonally, I'm worried users are seeing \"lost\" data (though they\ndon't notice it until weeks or months later) and are being surprised\nby it, which feels like a bigger issue to me than \"my automated script\nisn't running anymore on this one repo, now I have to figure out what\nflag to use in order to choose whether I care about that data from\nthat special repo being tossed or not\".  So I would bias towards\nthrowing an error so users get a chance to handle it.\n\n> Also, I am glad that you brought up another possible behaviour that\n> Luke's patch did not add.  Exporting existing signatures that may\n> become invalid and deciding what to do with them on the receiving\n> end would be a good option to have.  And that would most likely have\n> to be done at \"fast-import\" end, as a commit that \"fast-export\"\n> expected to retain its object name if its export stream were applied\n> as-is may not retain the object name when the export stream gets\n> preprocessed before being fed to \"fast-import\".\n\nRight, but I'd go a step further: Even if the fast-export stream is\nnot pre-processed before feeding to fast-import, you still cannot\nalways expect to get the same object names when importing the stream.\n\nTo see why, note that the fast-export stream has no way to encode tree\ninformation.  So if trees in the original history deviated from\n\"normal\" in some fashion, such as not-quite-sorted entries, or non\nstandard modes, then sending those objects through fast-export and\nfast-import will necessarily result in different object names.\nfast-export also may have modified other objects to normalize them,\neither because of default re-encoding of commit messages into UTF-8,\nbecause of stripping any unrecognized commit headers, or perhaps even\nbecause it'd truncate commit messages with an embedded NUL character.\n\nCombine all these \"normalizations\" that fast-export/fast-import do\nwith the ability for users to process the stream from fast-export to\nfast-import and it becomes clear that the only stage in the pipeline\nthat can check the validity of the gpg signatures for the imported\nhistory is the fast-import step.\n"},{"id":"423315","messageId":"xmqq7dkke9wa.fsf@gitster.g","threadId":"55538","inReplyTo":"CABPp-BGUrOtHcu-o2xq-3xc3f=9wy2oxcL_4-ays+ejCg8i+sA@mail.gmail.com","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-30T03:20:05Z","receivedAt":"2021-04-30T03:20:54Z","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> So, if it's just for filter-repo, then I'd say just change\n> fast-export's default now.  If you're concerned with\n> --signed-commit=abort being a changed default being too drastic for\n> other users or tools, then the environment variable escape hatch\n> sounds reasonable to me.\n\nI wasn't specifically worried about any single tool.  It is largely\nthird-party's business, and my job is to make sure it won't be too\nhard for them to adjust to our changes.\n\nEven existing users of filter-repo would probably need such an\nescape hatch, as it may not necessarily be possible to update\nfilter-repo at the same time they update Git.\n\nUnless filter-repo refuses to work with a version of Git that is\nnewer than what it knows about (which is not quite how I would\nprepare a tool for external change, though), that is.\n\n> Combine all these \"normalizations\" that fast-export/fast-import do\n> with the ability for users to process the stream from fast-export to\n> fast-import and it becomes clear that the only stage in the pipeline\n> that can check the validity of the gpg signatures for the imported\n> history is the fast-import step.\n\nYup.  So I guess we two are in agreement wrt the \"ideal world\".\n"},{"id":"423359","messageId":"87eeerpupk.wl-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"xmqqim44fyjj.fsf@gitster.g","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T17:07:19Z","receivedAt":"2021-04-30T17:07:26Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"On Thu, 29 Apr 2021 17:42:24 -0600,\nJunio C Hamano wrote:\n> \n> Elijah Newren <newren@gmail.com> writes:\n> \n> > I do get that we might have to use warn-strip as the default anyway\n> > just because some existing tools might rely on it, but do you have any\n> > examples outside of git-filter-repo?  Given the filter-repo bug\n> > reports I've gotten with users being surprised at commit signatures\n> > being stripped (despite the fact that this is documented -- users\n> > don't always read the documentation), I'd argue that changing to\n> > --signed-commits=abort as the default is probably a good bugfix for\n> > both fast-export and for filter-repo.\n> \n> Thanks.  The \"filter-repo already gets bug reports from the users\"\n> is a valuable input when deciding if it is reasonable to sell the\n> behaviour change as a bugfix to our users.\n> \n> Perhaps teaching fast-export to pay attention to two environment\n> variables that say \"when no --signed-{tag,commit}=<disposition>\"\n> command line option is given, use this behaviour\" would be a good\n> enough escape hatch for existing tools and their users, while they\n> are waiting for their tools to get updated with the new option you\n> are planning to add?\n\nBetween Elijah being on-board with changing the default, and the\nsuggested env-var escape hatch, you've won me over.\n\nI'll change the default to 'abort' and implement an env-var escape\nhatch.  Any suggestions on how to name it?\n`FAST_EXPORT_SIGNED_COMMITS`?  Should I give it a `GIT_` prefix?\n`FILTER_BRANCH_SQUELCH_WARNING` doesn't have a `GIT_` prefix...\n\n> Also, I am glad that you brought up another possible behaviour that\n> Luke's patch did not add.  Exporting existing signatures that may\n> become invalid and deciding what to do with them on the receiving\n> end would be a good option to have.  And that would most likely have\n> to be done at \"fast-import\" end, as a commit that \"fast-export\"\n> expected to retain its object name if its export stream were applied\n> as-is may not retain the object name when the export stream gets\n> preprocessed before being fed to \"fast-import\".\n\nElijah suggested that on an earlier version of the patchset too.  I\nagree that it's a splendid idea, but I'm not willing to be the one to\ndo the work of implementing it... at least not in the next few months.\n\n-- \nHappy hacking,\n~ Luke Shumaker\n"},{"id":"423368","messageId":"87a6pfpnvt.wl-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"xmqqfszbcazc.fsf@gitster.g","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T19:34:46Z","receivedAt":"2021-04-30T19:35:03Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"On Tue, 27 Apr 2021 22:02:47 -0600,\nJunio C Hamano wrote:\n> Better yet, don't butcher the region of memory pointed by the\n> \"message\" variable the caller uses to keep reading from the\n> remainder of the commit object buffer with this and memmove()\n> below.  Perhaps have the caller pass a strbuf to fill in the\n> signature found by this helper as another parameter, and then return\n> a bool \"Yes, I found a sig\" as its return value?\n\nStupid question: is there a better way to append a region of bytes to\na strbuf than\n\n    strbuf_addf(&buf, \"%.*s\", (int)(str_end - str_beg), str);\n\n?\n\nIt seems weird to me to invoke the printf machinery for something so\nsimple, but I don't see anything alternatives in strbuf.h.  Am I\nmissing something?\n\n-- \nHappy hacking,\n~ Luke Shumaker\n"},{"id":"423369","messageId":"CABPp-BGrYYO93V0-cNT_OOfeJpk1aHE39Nf1oLBYE-73Ly1QgQ@mail.gmail.com","threadId":"55538","inReplyTo":"87a6pfpnvt.wl-lukeshu@lukeshu.com","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-04-30T19:59:43Z","receivedAt":"2021-04-30T19:59:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Apr 30, 2021 at 12:34 PM Luke Shumaker <lukeshu@lukeshu.com> wrote:\n>\n> On Tue, 27 Apr 2021 22:02:47 -0600,\n> Junio C Hamano wrote:\n> > Better yet, don't butcher the region of memory pointed by the\n> > \"message\" variable the caller uses to keep reading from the\n> > remainder of the commit object buffer with this and memmove()\n> > below.  Perhaps have the caller pass a strbuf to fill in the\n> > signature found by this helper as another parameter, and then return\n> > a bool \"Yes, I found a sig\" as its return value?\n>\n> Stupid question: is there a better way to append a region of bytes to\n> a strbuf than\n>\n>     strbuf_addf(&buf, \"%.*s\", (int)(str_end - str_beg), str);\n>\n> ?\n>\n> It seems weird to me to invoke the printf machinery for something so\n> simple, but I don't see anything alternatives in strbuf.h.  Am I\n> missing something?\n\nI struggled to find it some time ago as well; I wonder if some\nreorganization of strbuf.[ch] might make it more clear.\n\nAnyway, strbuf_add() if you have the number of bytes already handy,\nstrbuf_addstr() if you don't have the number of bytes handy but the\nstring is NUL-delimited.\n"},{"id":"423386","messageId":"878s4zpg5v.wl-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"CABPp-BGrYYO93V0-cNT_OOfeJpk1aHE39Nf1oLBYE-73Ly1QgQ@mail.gmail.com","subject":"Re: [PATCH v3 3/3] fast-export, fast-import: implement signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T22:21:32Z","receivedAt":"2021-04-30T22:21:40Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"On Fri, 30 Apr 2021 13:59:43 -0600,\nElijah Newren wrote:\n> \n> On Fri, Apr 30, 2021 at 12:34 PM Luke Shumaker <lukeshu@lukeshu.com> wrote:\n> >\n> > On Tue, 27 Apr 2021 22:02:47 -0600,\n> > Junio C Hamano wrote:\n> > > Better yet, don't butcher the region of memory pointed by the\n> > > \"message\" variable the caller uses to keep reading from the\n> > > remainder of the commit object buffer with this and memmove()\n> > > below.  Perhaps have the caller pass a strbuf to fill in the\n> > > signature found by this helper as another parameter, and then return\n> > > a bool \"Yes, I found a sig\" as its return value?\n> >\n> > Stupid question: is there a better way to append a region of bytes to\n> > a strbuf than\n> >\n> >     strbuf_addf(&buf, \"%.*s\", (int)(str_end - str_beg), str);\n> >\n> > ?\n> >\n> > It seems weird to me to invoke the printf machinery for something so\n> > simple, but I don't see anything alternatives in strbuf.h.  Am I\n> > missing something?\n> \n> I struggled to find it some time ago as well; I wonder if some\n> reorganization of strbuf.[ch] might make it more clear.\n> \n> Anyway, strbuf_add() if you have the number of bytes already handy,\n> strbuf_addstr() if you don't have the number of bytes handy but the\n> string is NUL-delimited.\n\nAh!  I was looking for `char *`, but strbuf_add takes a `void *`,\nthat's why I didn't find it.\n\nThank you!\n\n-- \nHappy hacking,\n~ Luke Shumaker\n"},{"id":"423387","messageId":"20210430232537.1131641-1-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210423164118.693197-1-lukeshu@lukeshu.com","subject":"[PATCH v4 0/5] fast-export, fast-import: add support for signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T23:25:32Z","receivedAt":"2021-04-30T23:25:51Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export has an existing --signed-tags= option that controls how to\nhandle tag signatures.  However, there is no equivalent for commit\nsignatures; it just silently strips the signature out of the commit\n(analogously to --signed-tags=strip).\n\nSo implement a --signed-commits= flag in fast-export, and implement\nthe receiving side of it in fast-import.\n\nI believe that this revision addresses all of the feedback so far,\nwith the exceptions that: (1) I have not implemented Elijah's\nsuggestion to implement a flag on fast-import to validate signatures.\nWhile I agree that this would be a useful feature, I consider it to be\nbeyond the scope of this work. (2) The added tests still use `test -s\nerr`, as that's what's used by the other existing tests.\n\nNotable changes in v4 include adjusting fast-export to not butcher\nmemory from get_commit_buffer (both adding a new commit to fix\nexisting butchery, and adjusting the code added in the final commit),\nand changing the default to --signed-commits=abort, but adding a\n`FAST_EXPORT_SIGNED_COMMITS_NOABORT=1` environment variable.\n\nLuke Shumaker (5):\n  git-fast-import.txt: add missing LF in the BNF\n  fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n  git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n  fast-export: do not modify memory from get_commit_buffer\n  fast-export, fast-import: add support for signed-commits\n\n Documentation/git-fast-export.txt |  25 ++++-\n Documentation/git-fast-import.txt |  20 +++-\n builtin/fast-export.c             | 181 ++++++++++++++++++++++--------\n builtin/fast-import.c             |  23 ++++\n t/t9350-fast-export.sh            | 104 +++++++++++++++++\n 5 files changed, 303 insertions(+), 50 deletions(-)\n\nRange-diff against v3:\n1:  ee767f3a8f ! 1:  3116d531ab git-fast-import.txt: add missing LF in the BNF\n    @@ Commit message\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n     \n    +\n    + ## Notes ##\n    +    v2: no changes\n    +    v3: no changes\n    +    v4: no changes\n    +\n      ## Documentation/git-fast-import.txt ##\n     @@ Documentation/git-fast-import.txt: change to the project.\n      \toriginal-oid?\n2:  4612dbcdd5 ! 2:  b035fae93c fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n    @@ Commit message\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n     \n    +\n    + ## Notes ##\n    +    v2:\n    +     - Reword commit message based on feedback from Taylor.\n    +     - Fix copy-pasto in the test, noticed by Taylor.\n    +     - Add a comment to the tests.\n    +     - Fix whitespace in the tests.\n    +    v3:\n    +     - Document that --signed-tags='warn' is a deprecated synonym for\n    +       --signed-tags='warn-verbatim', rather than leaving it\n    +       undocumented, based on feedback from Eric.\n    +    v4:\n    +     - Don't give the \"deprecated synonym\" mention in the docs its own\n    +       paragraph.\n    +     - Don't just rename the user-facing string, also rename the internal\n    +       enum item from WARN to WARN_VERBATIM.\n    +\n      ## Documentation/git-fast-export.txt ##\n     @@ Documentation/git-fast-export.txt: OPTIONS\n      \tInsert 'progress' statements every <n> objects, to be shown by\n    @@ Documentation/git-fast-export.txt: When asking to 'abort' (which is the default)\n      warning will be displayed, with 'verbatim', they will be silently\n     -exported and with 'warn', they will be exported, but you will see a\n     -warning.\n    -+exported and with 'warn-verbatim', they will be exported, but you will\n    -+see a warning.\n    -++\n    -+`warn` is a deprecated synonym of `warn-verbatim`.\n    ++exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n    ++they will be exported, but you will see a warning.\n      \n      --tag-of-filtered-object=(abort|drop|rewrite)::\n      \tSpecify how to handle tags whose tagged object is filtered out.\n     \n      ## builtin/fast-export.c ##\n    +@@ builtin/fast-export.c: static const char *fast_export_usage[] = {\n    + };\n    + \n    + static int progress;\n    +-static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    ++static enum { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    + static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n    + static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n    + static int fake_missing_tagger;\n     @@ builtin/fast-export.c: static int parse_opt_signed_tag_mode(const struct option *opt,\n      \t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n      \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n      \t\tsigned_tag_mode = VERBATIM;\n     -\telse if (!strcmp(arg, \"warn\"))\n    +-\t\tsigned_tag_mode = WARN;\n     +\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n    - \t\tsigned_tag_mode = WARN;\n    ++\t\tsigned_tag_mode = WARN_VERBATIM;\n      \telse if (!strcmp(arg, \"warn-strip\"))\n      \t\tsigned_tag_mode = WARN_STRIP;\n    + \telse if (!strcmp(arg, \"strip\"))\n    +@@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n    + \t\t\t\tdie(\"encountered signed tag %s; use \"\n    + \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n    + \t\t\t\t    oid_to_hex(&tag->object.oid));\n    +-\t\t\tcase WARN:\n    ++\t\t\tcase WARN_VERBATIM:\n    + \t\t\t\twarning(\"exporting signed tag %s\",\n    + \t\t\t\t\toid_to_hex(&tag->object.oid));\n    + \t\t\t\t/* fallthru */\n     \n      ## t/t9350-fast-export.sh ##\n     @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=verbatim' '\n-:  ---------- > 3:  38b1ea78fd git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n-:  ---------- > 4:  1c34b843fb fast-export: do not modify memory from get_commit_buffer\n3:  e57f82e443 ! 5:  788542f669 fast-export, fast-import: implement signed-commits\n    @@ Metadata\n     Author: Luke Shumaker <lukeshu@datawire.io>\n     \n      ## Commit message ##\n    -    fast-export, fast-import: implement signed-commits\n    +    fast-export, fast-import: add support for signed-commits\n     \n    -    fast-export has an existing --signed-tags= flag that controls how to\n    -    handle tag signatures.  However, there is no equivalent for commit\n    -    signatures; it just silently strips the signature out of the commit\n    -    (analogously to --signed-tags=strip).\n    +    fast-export has a --signed-tags= option that controls how to handle tag\n    +    signatures.  However, there is no equivalent for commit signatures; it\n    +    just silently strips the signature out of the commit (analogously to\n    +    --signed-tags=strip).\n     \n         While signatures are generally problematic for fast-export/fast-import\n         (because hashes are likely to change), if they're going to support tag\n         signatures, there's no reason to not also support commit signatures.\n     \n    -    So, implement signed-commits.\n    +    So, implement a --signed-commits= option that mirrors the --signed-tags=\n    +    option.\n     \n         On the fast-export side, try to be as much like signed-tags as possible,\n    -    in both implementation and in user-interface; with the exception that\n    -    the default should be `--signed-commits=warn-strip` (compared to the\n    -    default `--signed-tags=abort`), in order to avoid breaking the\n    -    historical behavior (it will now print a warning while doing that\n    -    behavior, though).\n    +    in both implementation and in user-interface.  This will changes the\n    +    default behavior to '--signed-commits=abort' from what is now\n    +    '--signed-commits=strip'.  In order to provide an escape hatch for users\n    +    of third-party tools that call fast-export and do not yet know of the\n    +    --signed-commits= option, add an environment variable\n    +    'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' that changes the default to\n    +    '--signed-commits=warn-strip'.\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n     \n    +\n    + ## Notes ##\n    +    v2:\n    +     - Remove erroneous remark about ordering from the commit message.\n    +     - Adjust the stream syntax to include the hash algorithm, as\n    +       suggested by brian.\n    +     - Add support for sha256 (based on lots of useful information from\n    +       brian).  It does not support multiply-signed commits.\n    +     - Shorten the documentation, based on feedback from Taylor.\n    +     - Add comments, based on feedback from Taylor.\n    +     - Change the default from `--signed-commits=strip` to\n    +       `--signed-commits=warn-strip`.  This shouldn't break anyone, and\n    +       means that users get useful feedback by default.\n    +    v3: no changes\n    +    v4:\n    +     - Reword the commit message based on feedback from Junio.\n    +     - v1-v3 renamed enum items to SIGN_VERBATIM_WARN and SIGN_STRIP_WARN,\n    +       rename them to SIGN_WARN_VERBATIM and SIGN_WARN_STRIP instead.\n    +     - Rewrite find_signature() as find_commit_multiline_header().  Don't\n    +       have it butcher the memory that we pass to it; have it return its\n    +       own buffer.\n    +     - Change the default from `--signed-commits=warn-strip` to\n    +       `--signed-commits=abort`, to match `--signed-tags`.\n    +     - Add a FAST_EXPORT_SIGNED_COMMITS_NOABORT=1 env-var to change the\n    +       default to `--signed-commits=warn-strip`.\n    +\n      ## Documentation/git-fast-export.txt ##\n    -@@ Documentation/git-fast-export.txt: see a warning.\n    - +\n    - `warn` is a deprecated synonym of `warn-verbatim`.\n    +@@ Documentation/git-fast-export.txt: they will be exported, but you will see a warning.  'verbatim' and\n    + transformations affecting tags will be performed, or if you do not\n    + care that the resulting tag will have an invalid signature.\n      \n     +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n     +\tSpecify how to handle signed commits.  Behaves exactly as\n    -+\t--signed-tags (but for commits), except that the default is\n    -+\t'warn-strip' rather than 'abort'.\n    ++\t'--signed-tags', but for commits.\n    +++\n    ++Earlier versions this command that did not have '--signed-commits'\n    ++behaved as if '--signed-commits=strip'.  As an escape hatch for users\n    ++of tools that call 'git fast-export' but do not yet support\n    ++'--signed-commits', you may set the environment variable\n    ++'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default\n    ++from 'abort' to 'warn-strip'.\n     +\n      --tag-of-filtered-object=(abort|drop|rewrite)::\n      \tSpecify how to handle tags whose tagged object is filtered out.\n    @@ builtin/fast-export.c: static const char *fast_export_usage[] = {\n      \tNULL\n      };\n      \n    -+enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_VERBATIM_WARN, SIGN_STRIP_WARN };\n    ++enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN_WARN_STRIP };\n     +\n      static int progress;\n    --static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    +-static enum { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n     +static enum sign_mode signed_tag_mode = SIGN_ABORT;\n    -+static enum sign_mode signed_commit_mode = SIGN_STRIP_WARN;\n    ++static enum sign_mode signed_commit_mode = SIGN_ABORT;\n      static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n      static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n      static int fake_missing_tagger;\n    @@ builtin/fast-export.c: static int anonymize;\n     -\t\tsigned_tag_mode = VERBATIM;\n     +\t\t*valptr = SIGN_VERBATIM;\n      \telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n    --\t\tsigned_tag_mode = WARN;\n    -+\t\t*valptr = SIGN_VERBATIM_WARN;\n    +-\t\tsigned_tag_mode = WARN_VERBATIM;\n    ++\t\t*valptr = SIGN_WARN_VERBATIM;\n      \telse if (!strcmp(arg, \"warn-strip\"))\n     -\t\tsigned_tag_mode = WARN_STRIP;\n    -+\t\t*valptr = SIGN_STRIP_WARN;\n    ++\t\t*valptr = SIGN_WARN_STRIP;\n      \telse if (!strcmp(arg, \"strip\"))\n     -\t\tsigned_tag_mode = STRIP;\n     +\t\t*valptr = SIGN_STRIP;\n    @@ builtin/fast-export.c: static int anonymize;\n      \treturn 0;\n      }\n      \n    -@@ builtin/fast-export.c: static void show_filemodify(struct diff_queue_struct *q,\n    - \t}\n    +@@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const char **end)\n    + \t*end = out->buf + out->len;\n      }\n      \n    -+static const char *find_signature(const char *begin, const char *end, const char *key)\n    ++/*\n    ++ * find_commit_multiline_header is similar to find_commit_header,\n    ++ * except that it handles multi-line headers, rathar than simply\n    ++ * returning the first line of the header.\n    ++ *\n    ++ * The returned string has had the ' ' line continuation markers\n    ++ * removed, and points to staticly allocated memory (not to memory\n    ++ * within 'msg'), so it is only valid until the next call to\n    ++ * find_commit_multiline_header.\n    ++ *\n    ++ * If the header is found, then *end is set to point at the '\\n' in\n    ++ * msg that immediately follows the header value.\n    ++ */\n    ++static const char *find_commit_multiline_header(const char *msg,\n    ++\t\t\t\t\t\tconst char *key,\n    ++\t\t\t\t\t\tconst char **end)\n     +{\n    -+\tstatic struct strbuf needle = STRBUF_INIT;\n    -+\tchar *bod, *eod, *eol;\n    ++\tstatic struct strbuf val = STRBUF_INIT;\n    ++\tconst char *bol, *eol;\n    ++\tsize_t len;\n     +\n    -+\tstrbuf_reset(&needle);\n    -+\tstrbuf_addch(&needle, '\\n');\n    -+\tstrbuf_addstr(&needle, key);\n    -+\tstrbuf_addch(&needle, ' ');\n    ++\tstrbuf_reset(&val);\n     +\n    -+\tbod = memmem(begin, end ? end - begin : strlen(begin),\n    -+\t\t     needle.buf, needle.len);\n    -+\tif (!bod)\n    ++\tbol = find_commit_header(msg, key, &len);\n    ++\tif (!bol)\n     +\t\treturn NULL;\n    -+\tbod += needle.len;\n    -+\n    -+\t/*\n    -+\t * In the commit object, multi-line header values are stored\n    -+\t * by prefixing continuation lines begin with a space.  So\n    -+\t * within the commit object, it looks like\n    -+\t *\n    -+\t *     \"gpgsig -----BEGIN PGP SIGNATURE-----\\n\"\n    -+\t *     \" Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n    -+\t *     \" \\n\"\n    -+\t *     \" base64_pem_here\\n\"\n    -+\t *     \" -----END PGP SIGNATURE-----\\n\"\n    -+\t *\n    -+\t * So we need to look for the first '\\n' that *isn't* followed\n    -+\t * by a ' ' (or the first '\\0', if no such '\\n' exists).\n    -+\t */\n    -+\teod = strchrnul(bod, '\\n');\n    -+\twhile (eod[0] == '\\n' && eod[1] == ' ') {\n    -+\t\teod = strchrnul(eod+1, '\\n');\n    ++\teol = bol + len;\n    ++\tstrbuf_add(&val, bol, len);\n    ++\n    ++\twhile (eol[0] == '\\n' && eol[1] == ' ') {\n    ++\t\tbol = eol + 2;\n    ++\t\teol = strchrnul(bol, '\\n');\n    ++\t\tstrbuf_addch(&val, '\\n');\n    ++\t\tstrbuf_add(&val, bol, eol - bol);\n     +\t}\n    -+\t*eod = '\\0';\n    -+\n    -+\t/*\n    -+\t * We now have the value as it's stored in the commit object.\n    -+\t * However, we want the raw value; we want to return\n    -+\t *\n    -+\t *     \"-----BEGIN PGP SIGNATURE-----\\n\"\n    -+\t *     \"Version: GnuPG v1.4.5 (GNU/Linux)\\n\"\n    -+\t *     \"\\n\"\n    -+\t *     \"base64_pem_here\\n\"\n    -+\t *     \"-----END PGP SIGNATURE-----\\n\"\n    -+\t *\n    -+\t * So now we need to strip out all of those extra spaces.\n    -+\t */\n    -+\twhile ((eol = strstr(bod, \"\\n \")))\n    -+\t\tmemmove(eol+1, eol+2, strlen(eol+1));\n    -+\n    -+\treturn bod;\n    ++\n    ++\t*end = eol;\n    ++\treturn val.buf;\n     +}\n     +\n    - static const char *find_encoding(const char *begin, const char *end)\n    + static char *reencode_message(const char *in_msg,\n    + \t\t\t      const char *in_encoding, size_t in_encoding_len)\n      {\n    - \tconst char *needle = \"\\nencoding \";\n     @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n    - \tint saved_output_format = rev->diffopt.output_format;\n    - \tconst char *commit_buffer;\n      \tconst char *author, *author_end, *committer, *committer_end;\n    + \tconst char *encoding;\n    + \tsize_t encoding_len;\n     +\tconst char *signature_alg = NULL, *signature;\n    - \tconst char *encoding, *message;\n    + \tconst char *message;\n      \tchar *reencoded = NULL;\n      \tstruct commit_list *p;\n     @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n      \tcommitter++;\n    - \tcommitter_end = strchrnul(committer, '\\n');\n    - \tmessage = strstr(committer_end, \"\\n\\n\");\n    -+\tif ((signature = find_signature(committer_end, message, \"gpgsig\")))\n    + \tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n    + \n    +-\t/* find_commit_header() gets a `+ 1` because\n    +-\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n    +-\t * of the previous line, but find_commit_header() wants a\n    ++\t/* find_commit_header() and find_commit_multiline_header() get\n    ++\t * a `+ 1` because commit_buffer_cursor points at the trailing\n    ++\t * \"\\n\" at the end of the previous line, but they want a\n    + \t * pointer to the beginning of the next line. */\n    ++\n    + \tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n    + \tif (encoding)\n    + \t\tcommit_buffer_cursor = encoding + encoding_len;\n    + \n    ++\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n     +\t\tsignature_alg = \"sha1\";\n    -+\telse if ((signature = find_signature(committer_end, message, \"gpgsig-sha256\")))\n    ++\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n     +\t\tsignature_alg = \"sha256\";\n    - \tencoding = find_encoding(committer_end, message);\n    ++\n    + \tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n      \tif (message)\n      \t\tmessage += 2;\n     @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n    @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n     +\tif (signature)\n     +\t\tswitch(signed_commit_mode) {\n     +\t\tcase SIGN_ABORT:\n    -+\t\t\tdie(\"encountered signed commit %s\",\n    ++\t\t\tdie(\"encountered signed commit %s; use \"\n    ++\t\t\t    \"--signed-commits=<mode> to handle it\",\n     +\t\t\t    oid_to_hex(&commit->object.oid));\n    -+\t\tcase SIGN_VERBATIM_WARN:\n    ++\t\tcase SIGN_WARN_VERBATIM:\n     +\t\t\twarning(\"exporting signed commit %s\",\n     +\t\t\t\toid_to_hex(&commit->object.oid));\n     +\t\t\t/* fallthru */\n    @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n     +\t\t\t       (unsigned)strlen(signature),\n     +\t\t\t       signature);\n     +\t\t\tbreak;\n    -+\t\tcase SIGN_STRIP_WARN:\n    -+\t\t\twarning(\"stripping signature from commit %s; use\"\n    -+\t\t\t\t\"--signed-commits=<mode> to handle it differently\",\n    ++\t\tcase SIGN_WARN_STRIP:\n    ++\t\t\twarning(\"stripping signature from commit %s\",\n     +\t\t\t\toid_to_hex(&commit->object.oid));\n     +\t\t\t/* fallthru */\n     +\t\tcase SIGN_STRIP:\n     +\t\t\tbreak;\n     +\t\t}\n      \tif (!reencoded && encoding)\n    - \t\tprintf(\"encoding %s\\n\", encoding);\n    + \t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n      \tprintf(\"data %u\\n%s\",\n     @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n      \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n    @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n      \t\t\t\tdie(\"encountered signed tag %s; use \"\n      \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n      \t\t\t\t    oid_to_hex(&tag->object.oid));\n    --\t\t\tcase WARN:\n    -+\t\t\tcase SIGN_VERBATIM_WARN:\n    +-\t\t\tcase WARN_VERBATIM:\n    ++\t\t\tcase SIGN_WARN_VERBATIM:\n      \t\t\t\twarning(\"exporting signed tag %s\",\n      \t\t\t\t\toid_to_hex(&tag->object.oid));\n      \t\t\t\t/* fallthru */\n    @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n     +\t\t\tcase SIGN_VERBATIM:\n      \t\t\t\tbreak;\n     -\t\t\tcase WARN_STRIP:\n    -+\t\t\tcase SIGN_STRIP_WARN:\n    ++\t\t\tcase SIGN_WARN_STRIP:\n      \t\t\t\twarning(\"stripping signature from tag %s\",\n      \t\t\t\t\toid_to_hex(&tag->object.oid));\n      \t\t\t\t/* fallthru */\n    @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n      \t\t\t\tmessage_size = signature + 1 - message;\n      \t\t\t\tbreak;\n      \t\t\t}\n    +@@ builtin/fast-export.c: static int parse_opt_anonymize_map(const struct option *opt,\n    + \n    + int cmd_fast_export(int argc, const char **argv, const char *prefix)\n    + {\n    ++\tconst char *env_signed_commits_noabort;\n    + \tstruct rev_info revs;\n    + \tstruct object_array commits = OBJECT_ARRAY_INIT;\n    + \tstruct commit *commit;\n     @@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const char *prefix)\n      \t\t\t    N_(\"show progress after <n> objects\")),\n      \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n    @@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const ch\n      \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n      \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n      \t\t\t     parse_opt_tag_of_filtered_mode),\n    +@@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const char *prefix)\n    + \tif (argc == 1)\n    + \t\tusage_with_options (fast_export_usage, options);\n    + \n    ++\tenv_signed_commits_noabort = getenv(\"FAST_EXPORT_SIGNED_COMMITS_NOABORT\");\n    ++\tif (env_signed_commits_noabort && *env_signed_commits_noabort)\n    ++\t\tsigned_commit_mode = SIGN_WARN_STRIP;\n    ++\n    + \t/* we handle encodings */\n    + \tgit_config(git_default_config, NULL);\n    + \n     \n      ## builtin/fast-import.c ##\n     @@ builtin/fast-import.c: static struct hash_list *parse_merge(unsigned int *count)\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n     +\n     +'\n     +\n    ++test_expect_success GPG 'signed-commits default' '\n    ++\n    ++\tunset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n    ++\ttest_must_fail git fast-export --reencode=no commit-signing &&\n    ++\n    ++\tFAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err &&\n    ++\t! grep ^gpgsig output &&\n    ++\tgrep \"^encoding ISO-8859-1\" output &&\n    ++\ttest -s err &&\n    ++\tsed \"s/commit-signing/commit-strip-signing/\" output |\n    ++\t\t(cd new &&\n    ++\t\t git fast-import &&\n    ++\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n    ++\n    ++'\n    ++\n     +test_expect_success GPG 'signed-commits=abort' '\n     +\n     +\ttest_must_fail git fast-export --signed-commits=abort commit-signing\n-- \n2.31.1\n\nHappy hacking,\n~ Luke Shumaker\n"},{"id":"423388","messageId":"20210430232537.1131641-2-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210430232537.1131641-1-lukeshu@lukeshu.com","subject":"[PATCH v4 1/5] git-fast-import.txt: add missing LF in the BNF","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T23:25:33Z","receivedAt":"2021-04-30T23:25:58Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n\nNotes:\n    v2: no changes\n    v3: no changes\n    v4: no changes\n\n Documentation/git-fast-import.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex 39cfa05b28..458af0a2d6 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -437,7 +437,7 @@ change to the project.\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n-\t('encoding' SP <encoding>)?\n+\t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n \t('merge' SP <commit-ish> LF)*\n-- \n2.31.1\n\n"},{"id":"423389","messageId":"20210430232537.1131641-3-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210430232537.1131641-1-lukeshu@lukeshu.com","subject":"[PATCH v4 2/5] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T23:25:34Z","receivedAt":"2021-04-30T23:26:02Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nThe --signed-tags= option takes one of five arguments specifying how to\nhandle signed tags during export.  Among these arguments, 'strip' is to\n'warn-strip' as 'verbatim' is to 'warn' (the unmentioned argument is\n'abort', which stops the fast-export process entirely).  That is,\nsignatures are either stripped or copied verbatim while exporting, with\nor without a warning.\n\nMatch the pattern and rename 'warn' to 'warn-verbatim' to make it clear\nthat it instructs fast-export to copy signatures verbatim.\n\nTo maintain backwards compatibility, 'warn' is still recognized as\ndeprecated synonym of 'warn-verbatim'.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n\nNotes:\n    v2:\n     - Reword commit message based on feedback from Taylor.\n     - Fix copy-pasto in the test, noticed by Taylor.\n     - Add a comment to the tests.\n     - Fix whitespace in the tests.\n    v3:\n     - Document that --signed-tags='warn' is a deprecated synonym for\n       --signed-tags='warn-verbatim', rather than leaving it\n       undocumented, based on feedback from Eric.\n    v4:\n     - Don't give the \"deprecated synonym\" mention in the docs its own\n       paragraph.\n     - Don't just rename the user-facing string, also rename the internal\n       enum item from WARN to WARN_VERBATIM.\n\n Documentation/git-fast-export.txt |  6 +++---\n builtin/fast-export.c             |  8 ++++----\n t/t9350-fast-export.sh            | 18 ++++++++++++++++++\n 3 files changed, 25 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex 1978dbdc6a..593be7e9a2 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -27,7 +27,7 @@ OPTIONS\n \tInsert 'progress' statements every <n> objects, to be shown by\n \t'git fast-import' during import.\n \n---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n+--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n \tafter the export can change the tag names (which can also happen\n \twhen excluding revisions) the signatures will not match.\n@@ -36,8 +36,8 @@ When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n-exported and with 'warn', they will be exported, but you will see a\n-warning.\n+exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n+they will be exported, but you will see a warning.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 85a76e0ef8..d1cb8a3183 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -31,7 +31,7 @@ static const char *fast_export_usage[] = {\n };\n \n static int progress;\n-static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -55,8 +55,8 @@ static int parse_opt_signed_tag_mode(const struct option *opt,\n \t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n \t\tsigned_tag_mode = VERBATIM;\n-\telse if (!strcmp(arg, \"warn\"))\n-\t\tsigned_tag_mode = WARN;\n+\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n+\t\tsigned_tag_mode = WARN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-strip\"))\n \t\tsigned_tag_mode = WARN_STRIP;\n \telse if (!strcmp(arg, \"strip\"))\n@@ -834,7 +834,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN:\n+\t\t\tcase WARN_VERBATIM:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 409b48e244..892737439b 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -253,6 +253,24 @@ test_expect_success 'signed-tags=verbatim' '\n \n '\n \n+test_expect_success 'signed-tags=warn-verbatim' '\n+\n+\tgit fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n+# 'warn' is an backward-compatibility alias for 'warn-verbatim'; test\n+# that it keeps working.\n+test_expect_success 'signed-tags=warn' '\n+\n+\tgit fast-export --signed-tags=warn sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n test_expect_success 'signed-tags=strip' '\n \n \tgit fast-export --signed-tags=strip sign-your-name > output &&\n-- \n2.31.1\n\n"},{"id":"423390","messageId":"20210430232537.1131641-4-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210430232537.1131641-1-lukeshu@lukeshu.com","subject":"[PATCH v4 3/5] git-fast-export.txt: clarify why 'verbatim' may not be a good idea","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T23:25:35Z","receivedAt":"2021-04-30T23:26:03Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n\nNotes:\n    v4: This commit is new in v4.\n\n Documentation/git-fast-export.txt | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex 593be7e9a2..a364812d9f 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -29,15 +29,19 @@ OPTIONS\n \n --signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n-\tafter the export can change the tag names (which can also happen\n-\twhen excluding revisions) the signatures will not match.\n+\tafter the export (or during the export, such as excluding\n+\trevisions) can change the hashes being signed, the signatures\n+\tmay become invalid.\n +\n When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n-they will be exported, but you will see a warning.\n+they will be exported, but you will see a warning.  'verbatim' and\n+'warn-verbatim' should only be used if you know that no\n+transformations affecting tags will be performed, or if you do not\n+care that the resulting tag will have an invalid signature.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n-- \n2.31.1\n\n"},{"id":"423391","messageId":"20210430232537.1131641-5-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210430232537.1131641-1-lukeshu@lukeshu.com","subject":"[PATCH v4 4/5] fast-export: do not modify memory from get_commit_buffer","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T23:25:36Z","receivedAt":"2021-04-30T23:26:06Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export's helper function find_encoding() takes a `const char *`, but\nmodifies that memory despite the `const`.  Ultimately, this memory came\nfrom get_commit_buffer(), and you're not supposed to modify the memory\nthat you get from get_commit_buffer().\n\nSo, get rid of find_encoding() in favor of commit.h:find_commit_header(),\nwhich gives back a string length, rather than mutating the memory to\ninsert a '\\0' terminator.\n\nBecause find_commit_header() detects the \"\\n\\n\" string that separates the\nheaders and the commit message, move the call to be above the\n`message = strstr(..., \"\\n\\n\")` call.  This helps readability, and allows\nfor the value of `encoding` to be used for a better value of \"...\" so that\nthe same memory doesn't need to be checked twice.  Introduce a\n`commit_buffer_cursor` variable to avoid writing an awkward\n`encoding ? encoding + encoding_len : committer_end` expression.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n\nNotes:\n    v4: This commit is new in v4.\n\n builtin/fast-export.c | 65 ++++++++++++++++++++++++-------------------\n 1 file changed, 37 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex d1cb8a3183..81f3fb1f05 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -499,21 +499,6 @@ static void show_filemodify(struct diff_queue_struct *q,\n \t}\n }\n \n-static const char *find_encoding(const char *begin, const char *end)\n-{\n-\tconst char *needle = \"\\nencoding \";\n-\tchar *bol, *eol;\n-\n-\tbol = memmem(begin, end ? end - begin : strlen(begin),\n-\t\t     needle, strlen(needle));\n-\tif (!bol)\n-\t\treturn NULL;\n-\tbol += strlen(needle);\n-\teol = strchrnul(bol, '\\n');\n-\t*eol = '\\0';\n-\treturn bol;\n-}\n-\n static char *anonymize_ref_component(void *data)\n {\n \tstatic int counter;\n@@ -615,13 +600,26 @@ static void anonymize_ident_line(const char **beg, const char **end)\n \t*end = out->buf + out->len;\n }\n \n+static char *reencode_message(const char *in_msg,\n+\t\t\t      const char *in_encoding, size_t in_encoding_len)\n+{\n+\tstatic struct strbuf in_encoding_buf = STRBUF_INIT;\n+\n+\tstrbuf_reset(&in_encoding_buf);\n+\tstrbuf_add(&in_encoding_buf, in_encoding, in_encoding_len);\n+\n+\treturn reencode_string(in_msg, \"UTF-8\", in_encoding_buf.buf);\n+}\n+\n static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t\t\t  struct string_list *paths_of_changed_objects)\n {\n \tint saved_output_format = rev->diffopt.output_format;\n-\tconst char *commit_buffer;\n+\tconst char *commit_buffer, *commit_buffer_cursor;\n \tconst char *author, *author_end, *committer, *committer_end;\n-\tconst char *encoding, *message;\n+\tconst char *encoding;\n+\tsize_t encoding_len;\n+\tconst char *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n \tconst char *refname;\n@@ -630,21 +628,31 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \trev->diffopt.output_format = DIFF_FORMAT_CALLBACK;\n \n \tparse_commit_or_die(commit);\n-\tcommit_buffer = get_commit_buffer(commit, NULL);\n-\tauthor = strstr(commit_buffer, \"\\nauthor \");\n+\tcommit_buffer_cursor = commit_buffer = get_commit_buffer(commit, NULL);\n+\n+\tauthor = strstr(commit_buffer_cursor, \"\\nauthor \");\n \tif (!author)\n \t\tdie(\"could not find author in commit %s\",\n \t\t    oid_to_hex(&commit->object.oid));\n \tauthor++;\n-\tauthor_end = strchrnul(author, '\\n');\n-\tcommitter = strstr(author_end, \"\\ncommitter \");\n+\tcommit_buffer_cursor = author_end = strchrnul(author, '\\n');\n+\n+\tcommitter = strstr(commit_buffer_cursor, \"\\ncommitter \");\n \tif (!committer)\n \t\tdie(\"could not find committer in commit %s\",\n \t\t    oid_to_hex(&commit->object.oid));\n \tcommitter++;\n-\tcommitter_end = strchrnul(committer, '\\n');\n-\tmessage = strstr(committer_end, \"\\n\\n\");\n-\tencoding = find_encoding(committer_end, message);\n+\tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n+\n+\t/* find_commit_header() gets a `+ 1` because\n+\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n+\t * of the previous line, but find_commit_header() wants a\n+\t * pointer to the beginning of the next line. */\n+\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n+\tif (encoding)\n+\t\tcommit_buffer_cursor = encoding + encoding_len;\n+\n+\tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n \tif (message)\n \t\tmessage += 2;\n \n@@ -685,14 +693,15 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t} else if (encoding) {\n \t\tswitch(reencode_mode) {\n \t\tcase REENCODE_YES:\n-\t\t\treencoded = reencode_string(message, \"UTF-8\", encoding);\n+\t\t\treencoded = reencode_message(message, encoding, encoding_len);\n \t\t\tbreak;\n \t\tcase REENCODE_NO:\n \t\t\tbreak;\n \t\tcase REENCODE_ABORT:\n-\t\t\tdie(\"Encountered commit-specific encoding %s in commit \"\n+\t\t\tdie(\"Encountered commit-specific encoding %.*s in commit \"\n \t\t\t    \"%s; use --reencode=[yes|no] to handle it\",\n-\t\t\t    encoding, oid_to_hex(&commit->object.oid));\n+\t\t\t    (int)encoding_len, encoding,\n+\t\t\t    oid_to_hex(&commit->object.oid));\n \t\t}\n \t}\n \tif (!commit->parents)\n@@ -704,7 +713,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n \tif (!reencoded && encoding)\n-\t\tprintf(\"encoding %s\\n\", encoding);\n+\t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n \tprintf(\"data %u\\n%s\",\n \t       (unsigned)(reencoded\n \t\t\t  ? strlen(reencoded) : message\n-- \n2.31.1\n\n"},{"id":"423392","messageId":"20210430232537.1131641-6-lukeshu@lukeshu.com","threadId":"55538","inReplyTo":"20210430232537.1131641-1-lukeshu@lukeshu.com","subject":"[PATCH v4 5/5] fast-export, fast-import: add support for signed-commits","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-30T23:25:37Z","receivedAt":"2021-04-30T23:26:07Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export has a --signed-tags= option that controls how to handle tag\nsignatures.  However, there is no equivalent for commit signatures; it\njust silently strips the signature out of the commit (analogously to\n--signed-tags=strip).\n\nWhile signatures are generally problematic for fast-export/fast-import\n(because hashes are likely to change), if they're going to support tag\nsignatures, there's no reason to not also support commit signatures.\n\nSo, implement a --signed-commits= option that mirrors the --signed-tags=\noption.\n\nOn the fast-export side, try to be as much like signed-tags as possible,\nin both implementation and in user-interface.  This will changes the\ndefault behavior to '--signed-commits=abort' from what is now\n'--signed-commits=strip'.  In order to provide an escape hatch for users\nof third-party tools that call fast-export and do not yet know of the\n--signed-commits= option, add an environment variable\n'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' that changes the default to\n'--signed-commits=warn-strip'.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n\nNotes:\n    v2:\n     - Remove erroneous remark about ordering from the commit message.\n     - Adjust the stream syntax to include the hash algorithm, as\n       suggested by brian.\n     - Add support for sha256 (based on lots of useful information from\n       brian).  It does not support multiply-signed commits.\n     - Shorten the documentation, based on feedback from Taylor.\n     - Add comments, based on feedback from Taylor.\n     - Change the default from `--signed-commits=strip` to\n       `--signed-commits=warn-strip`.  This shouldn't break anyone, and\n       means that users get useful feedback by default.\n    v3: no changes\n    v4:\n     - Reword the commit message based on feedback from Junio.\n     - v1-v3 renamed enum items to SIGN_VERBATIM_WARN and SIGN_STRIP_WARN,\n       rename them to SIGN_WARN_VERBATIM and SIGN_WARN_STRIP instead.\n     - Rewrite find_signature() as find_commit_multiline_header().  Don't\n       have it butcher the memory that we pass to it; have it return its\n       own buffer.\n     - Change the default from `--signed-commits=warn-strip` to\n       `--signed-commits=abort`, to match `--signed-tags`.\n     - Add a FAST_EXPORT_SIGNED_COMMITS_NOABORT=1 env-var to change the\n       default to `--signed-commits=warn-strip`.\n\n Documentation/git-fast-export.txt |  11 +++\n Documentation/git-fast-import.txt |  18 +++++\n builtin/fast-export.c             | 120 +++++++++++++++++++++++++-----\n builtin/fast-import.c             |  23 ++++++\n t/t9350-fast-export.sh            |  86 +++++++++++++++++++++\n 5 files changed, 240 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex a364812d9f..7a946e2ede 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -43,6 +43,17 @@ they will be exported, but you will see a warning.  'verbatim' and\n transformations affecting tags will be performed, or if you do not\n care that the resulting tag will have an invalid signature.\n \n+--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n+\tSpecify how to handle signed commits.  Behaves exactly as\n+\t'--signed-tags', but for commits.\n++\n+Earlier versions this command that did not have '--signed-commits'\n+behaved as if '--signed-commits=strip'.  As an escape hatch for users\n+of tools that call 'git fast-export' but do not yet support\n+'--signed-commits', you may set the environment variable\n+'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default\n+from 'abort' to 'warn-strip'.\n+\n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n \tSince revisions and files to export can be limited by path,\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex 458af0a2d6..4955c94305 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -431,12 +431,21 @@ and control the current import process.  More detailed discussion\n Create or update a branch with a new commit, recording one logical\n change to the project.\n \n+////\n+Yes, it's intentional that the 'gpgsig' line doesn't have a trailing\n+`LF`; the the definition of `data` has a byte-count prefix, so it\n+doesn't need an `LF` to act as a terminator (and `data` also already\n+includes an optional trailing `LF?` just in case you want to include\n+one).\n+////\n+\n ....\n \t'commit' SP <ref> LF\n \tmark?\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n+\t('gpgsig' SP <alg> LF data)?\n \t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n@@ -505,6 +514,15 @@ that was selected by the --date-format=<fmt> command-line option.\n See ``Date Formats'' above for the set of supported formats, and\n their syntax.\n \n+`gpgsig`\n+^^^^^^^^\n+\n+The optional `gpgsig` command is used to include a PGP/GPG signature\n+that signs the commit data.\n+\n+Here <alg> specifies which hashing algorithm is used for this\n+signature, either `sha1` or `sha256`.\n+\n `encoding`\n ^^^^^^^^^^\n The optional `encoding` command indicates the encoding of the commit\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 81f3fb1f05..075630f185 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -30,8 +30,11 @@ static const char *fast_export_usage[] = {\n \tNULL\n };\n \n+enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN_WARN_STRIP };\n+\n static int progress;\n-static enum { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum sign_mode signed_tag_mode = SIGN_ABORT;\n+static enum sign_mode signed_commit_mode = SIGN_ABORT;\n static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -48,21 +51,24 @@ static int anonymize;\n static struct hashmap anonymized_seeds;\n static struct revision_sources revision_sources;\n \n-static int parse_opt_signed_tag_mode(const struct option *opt,\n+static int parse_opt_sign_mode(const struct option *opt,\n \t\t\t\t     const char *arg, int unset)\n {\n-\tif (unset || !strcmp(arg, \"abort\"))\n-\t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n+\tenum sign_mode *valptr = opt->value;\n+\tif (unset)\n+\t\treturn 0;\n+\telse if (!strcmp(arg, \"abort\"))\n+\t\t*valptr = SIGN_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n-\t\tsigned_tag_mode = VERBATIM;\n+\t\t*valptr = SIGN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n-\t\tsigned_tag_mode = WARN_VERBATIM;\n+\t\t*valptr = SIGN_WARN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-strip\"))\n-\t\tsigned_tag_mode = WARN_STRIP;\n+\t\t*valptr = SIGN_WARN_STRIP;\n \telse if (!strcmp(arg, \"strip\"))\n-\t\tsigned_tag_mode = STRIP;\n+\t\t*valptr = SIGN_STRIP;\n \telse\n-\t\treturn error(\"Unknown signed-tags mode: %s\", arg);\n+\t\treturn error(\"Unknown %s mode: %s\", opt->long_name, arg);\n \treturn 0;\n }\n \n@@ -600,6 +606,46 @@ static void anonymize_ident_line(const char **beg, const char **end)\n \t*end = out->buf + out->len;\n }\n \n+/*\n+ * find_commit_multiline_header is similar to find_commit_header,\n+ * except that it handles multi-line headers, rathar than simply\n+ * returning the first line of the header.\n+ *\n+ * The returned string has had the ' ' line continuation markers\n+ * removed, and points to staticly allocated memory (not to memory\n+ * within 'msg'), so it is only valid until the next call to\n+ * find_commit_multiline_header.\n+ *\n+ * If the header is found, then *end is set to point at the '\\n' in\n+ * msg that immediately follows the header value.\n+ */\n+static const char *find_commit_multiline_header(const char *msg,\n+\t\t\t\t\t\tconst char *key,\n+\t\t\t\t\t\tconst char **end)\n+{\n+\tstatic struct strbuf val = STRBUF_INIT;\n+\tconst char *bol, *eol;\n+\tsize_t len;\n+\n+\tstrbuf_reset(&val);\n+\n+\tbol = find_commit_header(msg, key, &len);\n+\tif (!bol)\n+\t\treturn NULL;\n+\teol = bol + len;\n+\tstrbuf_add(&val, bol, len);\n+\n+\twhile (eol[0] == '\\n' && eol[1] == ' ') {\n+\t\tbol = eol + 2;\n+\t\teol = strchrnul(bol, '\\n');\n+\t\tstrbuf_addch(&val, '\\n');\n+\t\tstrbuf_add(&val, bol, eol - bol);\n+\t}\n+\n+\t*end = eol;\n+\treturn val.buf;\n+}\n+\n static char *reencode_message(const char *in_msg,\n \t\t\t      const char *in_encoding, size_t in_encoding_len)\n {\n@@ -619,6 +665,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tconst char *author, *author_end, *committer, *committer_end;\n \tconst char *encoding;\n \tsize_t encoding_len;\n+\tconst char *signature_alg = NULL, *signature;\n \tconst char *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n@@ -644,14 +691,20 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tcommitter++;\n \tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n \n-\t/* find_commit_header() gets a `+ 1` because\n-\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n-\t * of the previous line, but find_commit_header() wants a\n+\t/* find_commit_header() and find_commit_multiline_header() get\n+\t * a `+ 1` because commit_buffer_cursor points at the trailing\n+\t * \"\\n\" at the end of the previous line, but they want a\n \t * pointer to the beginning of the next line. */\n+\n \tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n \tif (encoding)\n \t\tcommit_buffer_cursor = encoding + encoding_len;\n \n+\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n+\t\tsignature_alg = \"sha1\";\n+\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n+\t\tsignature_alg = \"sha256\";\n+\n \tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n \tif (message)\n \t\tmessage += 2;\n@@ -712,6 +765,29 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tprintf(\"%.*s\\n%.*s\\n\",\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n+\tif (signature)\n+\t\tswitch(signed_commit_mode) {\n+\t\tcase SIGN_ABORT:\n+\t\t\tdie(\"encountered signed commit %s; use \"\n+\t\t\t    \"--signed-commits=<mode> to handle it\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tcase SIGN_WARN_VERBATIM:\n+\t\t\twarning(\"exporting signed commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_VERBATIM:\n+\t\t\tprintf(\"gpgsig %s\\ndata %u\\n%s\",\n+\t\t\t       signature_alg,\n+\t\t\t       (unsigned)strlen(signature),\n+\t\t\t       signature);\n+\t\t\tbreak;\n+\t\tcase SIGN_WARN_STRIP:\n+\t\t\twarning(\"stripping signature from commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_STRIP:\n+\t\t\tbreak;\n+\t\t}\n \tif (!reencoded && encoding)\n \t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n \tprintf(\"data %u\\n%s\",\n@@ -839,21 +915,21 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n \t\tif (signature)\n \t\t\tswitch(signed_tag_mode) {\n-\t\t\tcase SIGNED_TAG_ABORT:\n+\t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN_VERBATIM:\n+\t\t\tcase SIGN_WARN_VERBATIM:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase VERBATIM:\n+\t\t\tcase SIGN_VERBATIM:\n \t\t\t\tbreak;\n-\t\t\tcase WARN_STRIP:\n+\t\t\tcase SIGN_WARN_STRIP:\n \t\t\t\twarning(\"stripping signature from tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase STRIP:\n+\t\t\tcase SIGN_STRIP:\n \t\t\t\tmessage_size = signature + 1 - message;\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1192,6 +1268,7 @@ static int parse_opt_anonymize_map(const struct option *opt,\n \n int cmd_fast_export(int argc, const char **argv, const char *prefix)\n {\n+\tconst char *env_signed_commits_noabort;\n \tstruct rev_info revs;\n \tstruct object_array commits = OBJECT_ARRAY_INIT;\n \tstruct commit *commit;\n@@ -1206,7 +1283,10 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"show progress after <n> objects\")),\n \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of signed tags\"),\n-\t\t\t     parse_opt_signed_tag_mode),\n+\t\t\t     parse_opt_sign_mode),\n+\t\tOPT_CALLBACK(0, \"signed-commits\", &signed_commit_mode, N_(\"mode\"),\n+\t\t\t     N_(\"select handling of signed commits\"),\n+\t\t\t     parse_opt_sign_mode),\n \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n \t\t\t     parse_opt_tag_of_filtered_mode),\n@@ -1247,6 +1327,10 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \tif (argc == 1)\n \t\tusage_with_options (fast_export_usage, options);\n \n+\tenv_signed_commits_noabort = getenv(\"FAST_EXPORT_SIGNED_COMMITS_NOABORT\");\n+\tif (env_signed_commits_noabort && *env_signed_commits_noabort)\n+\t\tsigned_commit_mode = SIGN_WARN_STRIP;\n+\n \t/* we handle encodings */\n \tgit_config(git_default_config, NULL);\n \ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 3afa81cf9a..ee7516dd38 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -2669,10 +2669,13 @@ static struct hash_list *parse_merge(unsigned int *count)\n \n static void parse_new_commit(const char *arg)\n {\n+\tstatic struct strbuf sig = STRBUF_INIT;\n \tstatic struct strbuf msg = STRBUF_INIT;\n+\tstruct string_list siglines = STRING_LIST_INIT_NODUP;\n \tstruct branch *b;\n \tchar *author = NULL;\n \tchar *committer = NULL;\n+\tchar *sig_alg = NULL;\n \tchar *encoding = NULL;\n \tstruct hash_list *merge_list = NULL;\n \tunsigned int merge_count;\n@@ -2696,6 +2699,13 @@ static void parse_new_commit(const char *arg)\n \t}\n \tif (!committer)\n \t\tdie(\"Expected committer but didn't get one\");\n+\tif (skip_prefix(command_buf.buf, \"gpgsig \", &v)) {\n+\t\tsig_alg = xstrdup(v);\n+\t\tread_next_command();\n+\t\tparse_data(&sig, 0, NULL);\n+\t\tread_next_command();\n+\t} else\n+\t\tstrbuf_setlen(&sig, 0);\n \tif (skip_prefix(command_buf.buf, \"encoding \", &v)) {\n \t\tencoding = xstrdup(v);\n \t\tread_next_command();\n@@ -2769,10 +2779,23 @@ static void parse_new_commit(const char *arg)\n \t\tstrbuf_addf(&new_data,\n \t\t\t\"encoding %s\\n\",\n \t\t\tencoding);\n+\tif (sig_alg) {\n+\t\tif (!strcmp(sig_alg, \"sha1\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig \");\n+\t\telse if (!strcmp(sig_alg, \"sha256\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig-sha256 \");\n+\t\telse\n+\t\t\tdie(\"Expected gpgsig algorithm sha1 or sha256, got %s\", sig_alg);\n+\t\tstring_list_split_in_place(&siglines, sig.buf, '\\n', -1);\n+\t\tstrbuf_add_separated_string_list(&new_data, \"\\n \", &siglines);\n+\t\tstrbuf_addch(&new_data, '\\n');\n+\t}\n \tstrbuf_addch(&new_data, '\\n');\n \tstrbuf_addbuf(&new_data, &msg);\n+\tstring_list_clear(&siglines, 1);\n \tfree(author);\n \tfree(committer);\n+\tfree(sig_alg);\n \tfree(encoding);\n \n \tif (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 892737439b..cd51c78418 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -8,6 +8,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n test_expect_success 'setup' '\n \n@@ -284,9 +285,94 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\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+\t# that we can test that fast-import gets the ordering correct\n+\t# between the two.\n+\ttest_config i18n.commitEncoding ISO-8859-1 &&\n+\tgit checkout -f -b commit-signing main &&\n+\techo Sign your name > file-sign &&\n+\tgit add file-sign &&\n+\tgit commit -S -m \"signed commit\" &&\n+\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits default' '\n+\n+\tunset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n+\ttest_must_fail git fast-export --reencode=no commit-signing &&\n+\n+\tFAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n+\t\t(cd new &&\n+\t\t git fast-import &&\n+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=abort' '\n+\n+\ttest_must_fail git fast-export --signed-commits=abort commit-signing\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=verbatim' '\n+\n+\tgit fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\t(cd new &&\n+\t git fast-import &&\n+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-verbatim' '\n+\n+\tgit fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\t(cd new &&\n+\t git fast-import &&\n+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=strip' '\n+\n+\tgit fast-export --signed-commits=strip --reencode=no commit-signing >output &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n+\t\t(cd new &&\n+\t\t git fast-import &&\n+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-strip' '\n+\n+\tgit fast-export --signed-commits=warn-strip --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n+\t\t(cd new &&\n+\t\t git fast-import &&\n+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n+\n+'\n+\n test_expect_success 'setup submodule' '\n \n \tgit checkout -f main &&\n+\t{ git update-ref -d refs/heads/commit-signing || true; } &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n-- \n2.31.1\n\n"},{"id":"423461","messageId":"xmqqa6pca0pv.fsf@gitster.g","threadId":"55538","inReplyTo":"20210430232537.1131641-5-lukeshu@lukeshu.com","subject":"Re: [PATCH v4 4/5] fast-export: do not modify memory from get_commit_buffer","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-03T04:41:00Z","receivedAt":"2021-05-03T04:41:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luke Shumaker <lukeshu@lukeshu.com> writes:\n\n> +static char *reencode_message(const char *in_msg,\n> +\t\t\t      const char *in_encoding, size_t in_encoding_len)\n> +{\n> +\tstatic struct strbuf in_encoding_buf = STRBUF_INIT;\n> +\n> +\tstrbuf_reset(&in_encoding_buf);\n> +\tstrbuf_add(&in_encoding_buf, in_encoding, in_encoding_len);\n> +\n> +\treturn reencode_string(in_msg, \"UTF-8\", in_encoding_buf.buf);\n> +}\n\nThere is only a single caller of this, so making it caller's\nresponsibility to do the strbuf thing would allow us to make this\nthread-safe quite easily (and at that point we might not even have\nthis helper function).\n\n> +\tcommitter = strstr(commit_buffer_cursor, \"\\ncommitter \");\n>  \tif (!committer)\n>  \t\tdie(\"could not find committer in commit %s\",\n>  \t\t    oid_to_hex(&commit->object.oid));\n>  \tcommitter++;\n> -\tcommitter_end = strchrnul(committer, '\\n');\n> -\tmessage = strstr(committer_end, \"\\n\\n\");\n> -\tencoding = find_encoding(committer_end, message);\n> +\tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n> +\n> +\t/* find_commit_header() gets a `+ 1` because\n> +\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n> +\t * of the previous line, but find_commit_header() wants a\n> +\t * pointer to the beginning of the next line. */\n> +\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n\n\t/*\n\t * Our multi-line comments have opening and closing\n\t * slash-asterisk and asterisk-slash on their own\n\t * lines.\n\t */\n\nWhat if strchrnul() returned a pointer to the terminating NUL\ninstead of the LF at the end of the line?  +1 will run past the end\nof the buffer.\n\n> +\tif (encoding)\n> +\t\tcommit_buffer_cursor = encoding + encoding_len;\n> +\n> +\tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n\nGood.\n\n> @@ -685,14 +693,15 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n>  \t} else if (encoding) {\n>  \t\tswitch(reencode_mode) {\n>  \t\tcase REENCODE_YES:\n> -\t\t\treencoded = reencode_string(message, \"UTF-8\", encoding);\n> +\t\t\treencoded = reencode_message(message, encoding, encoding_len);\n>  \t\t\tbreak;\n\nHere is where we can do the temporary strbuf to hold encoding[0,\nencoding_len] and directly call reencode_string().\n\nOther than that, this step looks good to me.\n\nThanks.\n"},{"id":"423462","messageId":"xmqq1rao9zev.fsf@gitster.g","threadId":"55538","inReplyTo":"20210430232537.1131641-6-lukeshu@lukeshu.com","subject":"Re: [PATCH v4 5/5] fast-export, fast-import: add support for signed-commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-03T05:09:12Z","receivedAt":"2021-05-03T05:09:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luke Shumaker <lukeshu@lukeshu.com> writes:\n\n> From: Luke Shumaker <lukeshu@datawire.io>\n>\n> fast-export has a --signed-tags= option that controls how to handle tag\n> signatures.  However, there is no equivalent for commit signatures; it\n> just silently strips the signature out of the commit (analogously to\n> --signed-tags=strip).\n>\n> While signatures are generally problematic for fast-export/fast-import\n> (because hashes are likely to change), if they're going to support tag\n> signatures, there's no reason to not also support commit signatures.\n>\n> So, implement a --signed-commits= option that mirrors the --signed-tags=\n> option.\n>\n> On the fast-export side, try to be as much like signed-tags as possible,\n> in both implementation and in user-interface.  This will changes the\n\ns/changes/change/;\n\n> default behavior to '--signed-commits=abort' from what is now\n> '--signed-commits=strip'.  In order to provide an escape hatch for users\n> of third-party tools that call fast-export and do not yet know of the\n> --signed-commits= option, add an environment variable\n> 'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' that changes the default to\n> '--signed-commits=warn-strip'.\n\nNicely explained.\n\n> +static const char *find_commit_multiline_header(const char *msg,\n> +\t\t\t\t\t\tconst char *key,\n> +\t\t\t\t\t\tconst char **end)\n> +{\n> +\tstatic struct strbuf val = STRBUF_INIT;\n> +\tconst char *bol, *eol;\n> +\tsize_t len;\n> +\n> +\tstrbuf_reset(&val);\n> +\n> +\tbol = find_commit_header(msg, key, &len);\n> +\tif (!bol)\n> +\t\treturn NULL;\n> +\teol = bol + len;\n> +\tstrbuf_add(&val, bol, len);\n> +\n> +\twhile (eol[0] == '\\n' && eol[1] == ' ') {\n> +\t\tbol = eol + 2;\n> +\t\teol = strchrnul(bol, '\\n');\n> +\t\tstrbuf_addch(&val, '\\n');\n> +\t\tstrbuf_add(&val, bol, eol - bol);\n> +\t}\n> +\n> +\t*end = eol;\n> +\treturn val.buf;\n\nIt is not exactly wrong per se, but using non-static (on stack)\nstrbuf would make it easier to follow.  You can then lose the\nstrbuf_reset() upfront, and then this will call strbuf_detach().\n\n> diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\n> index 892737439b..cd51c78418 100755\n> --- a/t/t9350-fast-export.sh\n> +++ b/t/t9350-fast-export.sh\n> @@ -8,6 +8,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n>  export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n>  \n>  . ./test-lib.sh\n> +. \"$TEST_DIRECTORY/lib-gpg.sh\"\n>  \n>  test_expect_success 'setup' '\n>  \n> @@ -284,9 +285,94 @@ test_expect_success 'signed-tags=warn-strip' '\n>  \ttest -s err\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> +\t# that we can test that fast-import gets the ordering correct\n> +\t# between the two.\n> +\ttest_config i18n.commitEncoding ISO-8859-1 &&\n> +\tgit checkout -f -b commit-signing main &&\n> +\techo Sign your name > file-sign &&\n\nStyle.  >file-sign (lose SP between the redirection operator and its\noperand).\n\n> +\tgit add file-sign &&\n> +\tgit commit -S -m \"signed commit\" &&\n> +\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n> +\n> +'\n> +\n> +test_expect_success GPG 'signed-commits default' '\n> +\n> +\tunset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n\nsane_unset would be safer here.\n\n> +\ttest_must_fail git fast-export --reencode=no commit-signing &&\n> +\n> +\tFAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err &&\n> +\t! grep ^gpgsig output &&\n> +\tgrep \"^encoding ISO-8859-1\" output &&\n> +\ttest -s err &&\n> +\tsed \"s/commit-signing/commit-strip-signing/\" output |\n> +\t\t(cd new &&\n> +\t\t git fast-import &&\n> +\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n\nLet's not force readers to match nested parentheses visually\n(applies to multiple places in this patch):\n\n\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n\t\tcd new &&\n\t\tgit fast-import &&\n\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n\t\ttest $COMMIT_SIGNING != $STRIPPED\n\t)\n\n>  test_expect_success 'setup submodule' '\n>  \n>  \tgit checkout -f main &&\n> +\t{ git update-ref -d refs/heads/commit-signing || true; } &&\n\n\ttest_might_fail git update-ref -d refs/heads/commit-signing &&\n\n"},{"id":"512898","messageId":"20250224142744.279643-2-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"[PATCH v5 1/6] git-fast-import.adoc: add missing LF in the BNF","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-02-24T14:27:39Z","receivedAt":"2025-02-24T14:28:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-import.adoc | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 58a2eaa51a..8e0de618c0 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -437,7 +437,7 @@ change to the project.\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n-\t('encoding' SP <encoding>)?\n+\t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n \t('merge' SP <commit-ish> LF)*\n-- \n2.48.1.401.g48e0d4203c\n\n"},{"id":"512899","messageId":"20250224142744.279643-3-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"[PATCH v5 2/6] fast-export: fix missing whitespace after switch","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-02-24T14:27:40Z","receivedAt":"2025-02-24T14:28:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"\"Documentation/CodingGuidelines\" says that there should be whitespaces\naround operators like 'if', 'switch', 'for', etc.\n\nLet's fix this in \"builtin/fast-export.c\".\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/fast-export.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex a5c82eef1d..2bf787191a 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -694,7 +694,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tif (anonymize) {\n \t\treencoded = anonymize_commit_message();\n \t} else if (encoding) {\n-\t\tswitch(reencode_mode) {\n+\t\tswitch (reencode_mode) {\n \t\tcase REENCODE_YES:\n \t\t\treencoded = reencode_string(message, \"UTF-8\", encoding);\n \t\t\tbreak;\n@@ -828,7 +828,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\tconst char *signature = strstr(message,\n \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n \t\tif (signature)\n-\t\t\tswitch(signed_tag_mode) {\n+\t\t\tswitch (signed_tag_mode) {\n \t\t\tcase SIGNED_TAG_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n@@ -853,7 +853,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \ttagged = tag->tagged;\n \ttagged_mark = get_object_mark(tagged);\n \tif (!tagged_mark) {\n-\t\tswitch(tag_of_filtered_mode) {\n+\t\tswitch (tag_of_filtered_mode) {\n \t\tcase TAG_FILTERING_ABORT:\n \t\t\tdie(\"tag %s tags unexported object; use \"\n \t\t\t    \"--tag-of-filtered-object=<mode> to handle it\",\n@@ -965,7 +965,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\tcontinue;\n \t\t}\n \n-\t\tswitch(commit->object.type) {\n+\t\tswitch (commit->object.type) {\n \t\tcase OBJ_COMMIT:\n \t\t\tbreak;\n \t\tcase OBJ_BLOB:\n-- \n2.48.1.401.g48e0d4203c\n\n"},{"id":"512900","messageId":"20250224142744.279643-1-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20210430232537.1131641-1-lukeshu@lukeshu.com","subject":"[PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-02-24T14:27:38Z","receivedAt":"2025-02-24T14:28:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Luke Shumaker sent the first 4 versions of this series in April 2021,\nbut it looks like he stopped before it got merged. Let's finish\npolishing it.\n\nGoal\n~~~~\n\nfast-export has an existing --signed-tags= option that controls how to\nhandle tag signatures.  However, there is no equivalent for commit\nsignatures; it just silently strips the signature out of the commit\n(analogously to --signed-tags=strip).\n\nSo implement a --signed-commits= flag in fast-export, and implement\nthe receiving side of it in fast-import.\n\nOverview of the changes since v4\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis revision addresses all the feedback from v4 and has a few small\nimprovements that I came accross when rebasing it on top of current\nmaster and working on it.\n\nThis doesn't address the following (that weren't addressed either by\nearlier versions of this series) though:\n\n  - Elijah's suggestion to implement a flag on fast-import to validate\n    signatures.  This could be a useful feature, but Luke considered\n    it was beyond the scope of this work, and I agree with him.\n\n  - The added tests still use `test -s err`, as that's still what's\n    used by the other existing tests. I would be fine with adding a\n    preparatory patch to address this, if people think it would be\n    worth it. On the other hand, it could be part of a small separate\n    series to modernize the whole \"t/t9350-fast-export.sh\".\n\nDetails of the changes since v4\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n  - Rebased on top of b838bf1938 (Merge branch 'master' of\n    https://github.com/j6t/gitk, 2025-02-20) to fix a few conflicts\n    and avoid issues with file name changes, as v4 was based on\n    v2.31.0 which is very old.\n\n  - Added patch 2/6 (fast-export: fix missing whitespace after switch)\n    as a preparatory patch to fix a style issue related to 'switch'\n    statements in \"builtin/fast-export.c\".\n\n  - In patch 3/6 (fast-export: rename --signed-tags='warn' to\n    'warn-verbatim'), fixed an \"a\" vs \"an\" typo in\n    \"t9350-fast-export.sh\".\n\n  - In patch 4/6 (git-fast-export.txt: clarify why 'verbatim' may not\n    be a good idea), fixed a \"transformation\" vs \"transformations\"\n    typo in \"git-fast-export.adoc\".\n\n  - In patch 5/6 (fast-export: do not modify memory from\n    get_commit_buffer) there are a few small changes in\n    \"builtin/fast-export.c\" that were suggested by Junio:\n\n    * removed reencode_message() and instead put the encoding code in\n      the `case REENCODE_YES:` in handle_commit(),\n\n    * fixed multi-line comment style,\n\n    * fixed potential past end of buffer read by surrounding some code\n      with `if (*commit_buffer_cursor == '\\n') { ... }`.\n\n  - In patch 6/6 (fast-export, fast-import: add support for\n    signed-commits) there are a number of small changes:\n\n    * some typo fixes:\n\n      - \"changes\" vs \"change\" in the commit message,\n      - \"the the\" vs \"the\" in \"git-fast-import.adoc\",\n      - \"staticly\" vs \"statically\" in a comment in \"builtin/fast-export.c\",\n\n    * some code changes, all suggested by Junio except the last one,\n      in \"builtin/fast-export.c\":\n\n      - made a 'strbuf' non-static in anonymize_ident_line(),\n      - fixed potential past end of buffer read by surrounding some code\n        with `if (*commit_buffer_cursor == '\\n') { ... }`,\n      - added a `free((char *)signature)` call to avoid a memory leak\n        found by the CI tests,\n\n    * some test improvements suggested by Junio in\n      \"t/t9350-fast-export.sh\":\n\n      - removed whitespace between \">\" and \"file-sign\",\n      - replaced `unset` with `sane_unset`,\n      - better indented lines where a `( ... )` subshell is used,\n      - replaced `{ ... || true }` with `test_might_fail`.\n\nCI tests\n~~~~~~~~\n\nAll the CI tests passed, see:\n\nhttps://github.com/chriscool/git/actions/runs/13496792476\n\nRange diff compared to version 4\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n1:  53d8dd60ce ! 1:  f97247e17d git-fast-import.txt: add missing LF in the BNF\n    @@ Metadata\n     Author: Luke Shumaker <lukeshu@datawire.io>\n     \n      ## Commit message ##\n    -    git-fast-import.txt: add missing LF in the BNF\n    +    git-fast-import.adoc: add missing LF in the BNF\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n    +    Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n    - ## Documentation/git-fast-import.txt ##\n    -@@ Documentation/git-fast-import.txt: change to the project.\n    + ## Documentation/git-fast-import.adoc ##\n    +@@ Documentation/git-fast-import.adoc: change to the project.\n      \toriginal-oid?\n      \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n      \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n-:  ---------- > 2:  b71588563d fast-export: fix missing whitespace after switch\n2:  454a58a398 ! 3:  947bc267e6 fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n    @@ Commit message\n         deprecated synonym of 'warn-verbatim'.\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n    +    Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n    - ## Documentation/git-fast-export.txt ##\n    -@@ Documentation/git-fast-export.txt: OPTIONS\n    + ## Documentation/git-fast-export.adoc ##\n    +@@ Documentation/git-fast-export.adoc: OPTIONS\n      \tInsert 'progress' statements every <n> objects, to be shown by\n      \t'git fast-import' during import.\n      \n    @@ Documentation/git-fast-export.txt: OPTIONS\n      \tSpecify how to handle signed tags.  Since any transformation\n      \tafter the export can change the tag names (which can also happen\n      \twhen excluding revisions) the signatures will not match.\n    -@@ Documentation/git-fast-export.txt: When asking to 'abort' (which is the default), this program will die\n    +@@ Documentation/git-fast-export.adoc: When asking to 'abort' (which is the default), this program will die\n      when encountering a signed tag.  With 'strip', the tags will silently\n      be made unsigned, with 'warn-strip' they will be made unsigned but a\n      warning will be displayed, with 'verbatim', they will be silently\n    @@ builtin/fast-export.c: static const char *fast_export_usage[] = {\n      };\n      \n      static int progress;\n    --static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    -+static enum { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    - static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n    - static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n    +-static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    ++static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    + static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n    + static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n      static int fake_missing_tagger;\n     @@ builtin/fast-export.c: static int parse_opt_signed_tag_mode(const struct option *opt,\n    - \t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n    + \t\t*val = SIGNED_TAG_ABORT;\n      \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n    - \t\tsigned_tag_mode = VERBATIM;\n    + \t\t*val = VERBATIM;\n     -\telse if (!strcmp(arg, \"warn\"))\n    --\t\tsigned_tag_mode = WARN;\n    +-\t\t*val = WARN;\n     +\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n    -+\t\tsigned_tag_mode = WARN_VERBATIM;\n    ++\t\t*val = WARN_VERBATIM;\n      \telse if (!strcmp(arg, \"warn-strip\"))\n    - \t\tsigned_tag_mode = WARN_STRIP;\n    + \t\t*val = WARN_STRIP;\n      \telse if (!strcmp(arg, \"strip\"))\n     @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n      \t\t\t\tdie(\"encountered signed tag %s; use \"\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=verbatim' '\n     +\n     +'\n     +\n    -+# 'warn' is an backward-compatibility alias for 'warn-verbatim'; test\n    ++# 'warn' is a backward-compatibility alias for 'warn-verbatim'; test\n     +# that it keeps working.\n     +test_expect_success 'signed-tags=warn' '\n     +\n3:  ee0d84c34a ! 4:  45087db345 git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n    @@ Commit message\n         git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n    +    Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n    - ## Documentation/git-fast-export.txt ##\n    -@@ Documentation/git-fast-export.txt: OPTIONS\n    + ## Documentation/git-fast-export.adoc ##\n    +@@ Documentation/git-fast-export.adoc: OPTIONS\n      \n      --signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n      \tSpecify how to handle signed tags.  Since any transformation\n    @@ Documentation/git-fast-export.txt: OPTIONS\n     -they will be exported, but you will see a warning.\n     +they will be exported, but you will see a warning.  'verbatim' and\n     +'warn-verbatim' should only be used if you know that no\n    -+transformations affecting tags will be performed, or if you do not\n    ++transformation affecting tags will be performed, or if you do not\n     +care that the resulting tag will have an invalid signature.\n      \n      --tag-of-filtered-object=(abort|drop|rewrite)::\n4:  36463ee3a8 ! 5:  20f085a790 fast-export: do not modify memory from get_commit_buffer\n    @@ Commit message\n         `encoding ? encoding + encoding_len : committer_end` expression.\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n    +    Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## builtin/fast-export.c ##\n     @@ builtin/fast-export.c: static void show_filemodify(struct diff_queue_struct *q,\n    @@ builtin/fast-export.c: static void show_filemodify(struct diff_queue_struct *q,\n     -\treturn bol;\n     -}\n     -\n    - static char *anonymize_ref_component(void *data)\n    + static char *anonymize_ref_component(void)\n      {\n      \tstatic int counter;\n    -@@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const char **end)\n    - \t*end = out->buf + out->len;\n    - }\n    - \n    -+static char *reencode_message(const char *in_msg,\n    -+\t\t\t      const char *in_encoding, size_t in_encoding_len)\n    -+{\n    -+\tstatic struct strbuf in_encoding_buf = STRBUF_INIT;\n    -+\n    -+\tstrbuf_reset(&in_encoding_buf);\n    -+\tstrbuf_add(&in_encoding_buf, in_encoding, in_encoding_len);\n    -+\n    -+\treturn reencode_string(in_msg, \"UTF-8\", in_encoding_buf.buf);\n    -+}\n    -+\n    - static void handle_commit(struct commit *commit, struct rev_info *rev,\n    +@@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n      \t\t\t  struct string_list *paths_of_changed_objects)\n      {\n      \tint saved_output_format = rev->diffopt.output_format;\n    @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n     +\tconst char *commit_buffer, *commit_buffer_cursor;\n      \tconst char *author, *author_end, *committer, *committer_end;\n     -\tconst char *encoding, *message;\n    -+\tconst char *encoding;\n    ++\tconst char *encoding = NULL;\n     +\tsize_t encoding_len;\n     +\tconst char *message;\n      \tchar *reencoded = NULL;\n    @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n      \trev->diffopt.output_format = DIFF_FORMAT_CALLBACK;\n      \n      \tparse_commit_or_die(commit);\n    --\tcommit_buffer = get_commit_buffer(commit, NULL);\n    +-\tcommit_buffer = repo_get_commit_buffer(the_repository, commit, NULL);\n     -\tauthor = strstr(commit_buffer, \"\\nauthor \");\n    -+\tcommit_buffer_cursor = commit_buffer = get_commit_buffer(commit, NULL);\n    ++\tcommit_buffer_cursor = commit_buffer = repo_get_commit_buffer(the_repository, commit, NULL);\n     +\n     +\tauthor = strstr(commit_buffer_cursor, \"\\nauthor \");\n      \tif (!author)\n    @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n     -\tencoding = find_encoding(committer_end, message);\n     +\tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n     +\n    -+\t/* find_commit_header() gets a `+ 1` because\n    ++\t/*\n    ++\t * find_commit_header() gets a `+ 1` because\n     +\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n     +\t * of the previous line, but find_commit_header() wants a\n    -+\t * pointer to the beginning of the next line. */\n    -+\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n    -+\tif (encoding)\n    -+\t\tcommit_buffer_cursor = encoding + encoding_len;\n    ++\t * pointer to the beginning of the next line.\n    ++\t */\n    ++\tif (*commit_buffer_cursor == '\\n') {\n    ++\t\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n    ++\t\tif (encoding)\n    ++\t\t\tcommit_buffer_cursor = encoding + encoding_len;\n    ++\t}\n     +\n     +\tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n      \tif (message)\n      \t\tmessage += 2;\n      \n     @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n    + \tif (anonymize) {\n    + \t\treencoded = anonymize_commit_message();\n      \t} else if (encoding) {\n    - \t\tswitch(reencode_mode) {\n    ++\t\tchar *buf;\n    + \t\tswitch (reencode_mode) {\n      \t\tcase REENCODE_YES:\n     -\t\t\treencoded = reencode_string(message, \"UTF-8\", encoding);\n    -+\t\t\treencoded = reencode_message(message, encoding, encoding_len);\n    ++\t\t\tbuf = xstrfmt(\"%.*s\", (int)encoding_len, encoding);\n    ++\t\t\treencoded = reencode_string(message, \"UTF-8\", buf);\n    ++\t\t\tfree(buf);\n      \t\t\tbreak;\n      \t\tcase REENCODE_NO:\n      \t\t\tbreak;\n5:  8ff33e2e88 ! 6:  48e0d4203c fast-export, fast-import: add support for signed-commits\n    @@ Commit message\n         option.\n     \n         On the fast-export side, try to be as much like signed-tags as possible,\n    -    in both implementation and in user-interface.  This will changes the\n    +    in both implementation and in user-interface.  This will change the\n         default behavior to '--signed-commits=abort' from what is now\n         '--signed-commits=strip'.  In order to provide an escape hatch for users\n         of third-party tools that call fast-export and do not yet know of the\n    @@ Commit message\n         '--signed-commits=warn-strip'.\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n    +    Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n    - ## Documentation/git-fast-export.txt ##\n    -@@ Documentation/git-fast-export.txt: they will be exported, but you will see a warning.  'verbatim' and\n    - transformations affecting tags will be performed, or if you do not\n    + ## Documentation/git-fast-export.adoc ##\n    +@@ Documentation/git-fast-export.adoc: they will be exported, but you will see a warning.  'verbatim' and\n    + transformation affecting tags will be performed, or if you do not\n      care that the resulting tag will have an invalid signature.\n      \n     +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n    @@ Documentation/git-fast-export.txt: they will be exported, but you will see a war\n      \tSpecify how to handle tags whose tagged object is filtered out.\n      \tSince revisions and files to export can be limited by path,\n     \n    - ## Documentation/git-fast-import.txt ##\n    -@@ Documentation/git-fast-import.txt: and control the current import process.  More detailed discussion\n    + ## Documentation/git-fast-import.adoc ##\n    +@@ Documentation/git-fast-import.adoc: and control the current import process.  More detailed discussion\n      Create or update a branch with a new commit, recording one logical\n      change to the project.\n      \n     +////\n     +Yes, it's intentional that the 'gpgsig' line doesn't have a trailing\n    -+`LF`; the the definition of `data` has a byte-count prefix, so it\n    ++`LF`; the definition of `data` has a byte-count prefix, so it\n     +doesn't need an `LF` to act as a terminator (and `data` also already\n     +includes an optional trailing `LF?` just in case you want to include\n     +one).\n    @@ Documentation/git-fast-import.txt: and control the current import process.  More\n      \t('encoding' SP <encoding> LF)?\n      \tdata\n      \t('from' SP <commit-ish> LF)?\n    -@@ Documentation/git-fast-import.txt: that was selected by the --date-format=<fmt> command-line option.\n    +@@ Documentation/git-fast-import.adoc: that was selected by the --date-format=<fmt> command-line option.\n      See ``Date Formats'' above for the set of supported formats, and\n      their syntax.\n      \n    @@ builtin/fast-export.c: static const char *fast_export_usage[] = {\n     +enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN_WARN_STRIP };\n     +\n      static int progress;\n    --static enum { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n    +-static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n     +static enum sign_mode signed_tag_mode = SIGN_ABORT;\n     +static enum sign_mode signed_commit_mode = SIGN_ABORT;\n    - static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n    - static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n    + static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n    + static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n      static int fake_missing_tagger;\n     @@ builtin/fast-export.c: static int anonymize;\n      static struct hashmap anonymized_seeds;\n    @@ builtin/fast-export.c: static int anonymize;\n     +static int parse_opt_sign_mode(const struct option *opt,\n      \t\t\t\t     const char *arg, int unset)\n      {\n    +-\tenum signed_tag_mode *val = opt->value;\n    +-\n     -\tif (unset || !strcmp(arg, \"abort\"))\n    --\t\tsigned_tag_mode = SIGNED_TAG_ABORT;\n    -+\tenum sign_mode *valptr = opt->value;\n    +-\t\t*val = SIGNED_TAG_ABORT;\n    ++\tenum sign_mode *val = opt->value;\n     +\tif (unset)\n     +\t\treturn 0;\n     +\telse if (!strcmp(arg, \"abort\"))\n    -+\t\t*valptr = SIGN_ABORT;\n    ++\t\t*val = SIGN_ABORT;\n      \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n    --\t\tsigned_tag_mode = VERBATIM;\n    -+\t\t*valptr = SIGN_VERBATIM;\n    +-\t\t*val = VERBATIM;\n    ++\t\t*val = SIGN_VERBATIM;\n      \telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n    --\t\tsigned_tag_mode = WARN_VERBATIM;\n    -+\t\t*valptr = SIGN_WARN_VERBATIM;\n    +-\t\t*val = WARN_VERBATIM;\n    ++\t\t*val = SIGN_WARN_VERBATIM;\n      \telse if (!strcmp(arg, \"warn-strip\"))\n    --\t\tsigned_tag_mode = WARN_STRIP;\n    -+\t\t*valptr = SIGN_WARN_STRIP;\n    +-\t\t*val = WARN_STRIP;\n    ++\t\t*val = SIGN_WARN_STRIP;\n      \telse if (!strcmp(arg, \"strip\"))\n    --\t\tsigned_tag_mode = STRIP;\n    -+\t\t*valptr = SIGN_STRIP;\n    +-\t\t*val = STRIP;\n    ++\t\t*val = SIGN_STRIP;\n      \telse\n     -\t\treturn error(\"Unknown signed-tags mode: %s\", arg);\n     +\t\treturn error(\"Unknown %s mode: %s\", opt->long_name, arg);\n    @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n     + * returning the first line of the header.\n     + *\n     + * The returned string has had the ' ' line continuation markers\n    -+ * removed, and points to staticly allocated memory (not to memory\n    ++ * removed, and points to statically allocated memory (not to memory\n     + * within 'msg'), so it is only valid until the next call to\n     + * find_commit_multiline_header.\n     + *\n    @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n     +\t\t\t\t\t\tconst char *key,\n     +\t\t\t\t\t\tconst char **end)\n     +{\n    -+\tstatic struct strbuf val = STRBUF_INIT;\n    ++\tstruct strbuf val = STRBUF_INIT;\n     +\tconst char *bol, *eol;\n     +\tsize_t len;\n     +\n    -+\tstrbuf_reset(&val);\n    -+\n     +\tbol = find_commit_header(msg, key, &len);\n     +\tif (!bol)\n     +\t\treturn NULL;\n    @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n     +\t}\n     +\n     +\t*end = eol;\n    -+\treturn val.buf;\n    ++\treturn strbuf_detach(&val, NULL);\n     +}\n     +\n    - static char *reencode_message(const char *in_msg,\n    - \t\t\t      const char *in_encoding, size_t in_encoding_len)\n    + static void handle_commit(struct commit *commit, struct rev_info *rev,\n    + \t\t\t  struct string_list *paths_of_changed_objects)\n      {\n     @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n      \tconst char *author, *author_end, *committer, *committer_end;\n    - \tconst char *encoding;\n    + \tconst char *encoding = NULL;\n      \tsize_t encoding_len;\n    -+\tconst char *signature_alg = NULL, *signature;\n    ++\tconst char *signature_alg = NULL, *signature = NULL;\n      \tconst char *message;\n      \tchar *reencoded = NULL;\n      \tstruct commit_list *p;\n     @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n    - \tcommitter++;\n      \tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n      \n    --\t/* find_commit_header() gets a `+ 1` because\n    + \t/*\n    +-\t * find_commit_header() gets a `+ 1` because\n     -\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n     -\t * of the previous line, but find_commit_header() wants a\n    -+\t/* find_commit_header() and find_commit_multiline_header() get\n    ++\t * find_commit_header() and find_commit_multiline_header() get\n     +\t * a `+ 1` because commit_buffer_cursor points at the trailing\n     +\t * \"\\n\" at the end of the previous line, but they want a\n    - \t * pointer to the beginning of the next line. */\n    + \t * pointer to the beginning of the next line.\n    + \t */\n     +\n    - \tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n    - \tif (encoding)\n    - \t\tcommit_buffer_cursor = encoding + encoding_len;\n    + \tif (*commit_buffer_cursor == '\\n') {\n    + \t\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n    + \t\tif (encoding)\n    + \t\t\tcommit_buffer_cursor = encoding + encoding_len;\n    + \t}\n      \n    -+\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n    -+\t\tsignature_alg = \"sha1\";\n    -+\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n    -+\t\tsignature_alg = \"sha256\";\n    ++\tif (*commit_buffer_cursor == '\\n') {\n    ++\t\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n    ++\t\t\tsignature_alg = \"sha1\";\n    ++\t\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n    ++\t\t\tsignature_alg = \"sha256\";\n    ++\t}\n     +\n      \tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n      \tif (message)\n    @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n      \tprintf(\"%.*s\\n%.*s\\n\",\n      \t       (int)(author_end - author), author,\n      \t       (int)(committer_end - committer), committer);\n    -+\tif (signature)\n    -+\t\tswitch(signed_commit_mode) {\n    ++\tif (signature) {\n    ++\t\tswitch (signed_commit_mode) {\n     +\t\tcase SIGN_ABORT:\n     +\t\t\tdie(\"encountered signed commit %s; use \"\n     +\t\t\t    \"--signed-commits=<mode> to handle it\",\n    @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n     +\t\tcase SIGN_STRIP:\n     +\t\t\tbreak;\n     +\t\t}\n    ++\t\tfree((char *)signature);\n    ++\t}\n      \tif (!reencoded && encoding)\n      \t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n      \tprintf(\"data %u\\n%s\",\n     @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n      \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n      \t\tif (signature)\n    - \t\t\tswitch(signed_tag_mode) {\n    + \t\t\tswitch (signed_tag_mode) {\n     -\t\t\tcase SIGNED_TAG_ABORT:\n     +\t\t\tcase SIGN_ABORT:\n      \t\t\t\tdie(\"encountered signed tag %s; use \"\n    @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n      \t\t\t\tmessage_size = signature + 1 - message;\n      \t\t\t\tbreak;\n      \t\t\t}\n    -@@ builtin/fast-export.c: static int parse_opt_anonymize_map(const struct option *opt,\n    - \n    - int cmd_fast_export(int argc, const char **argv, const char *prefix)\n    +@@ builtin/fast-export.c: int cmd_fast_export(int argc,\n    + \t\t    const char *prefix,\n    + \t\t    struct repository *repo UNUSED)\n      {\n     +\tconst char *env_signed_commits_noabort;\n      \tstruct rev_info revs;\n    - \tstruct object_array commits = OBJECT_ARRAY_INIT;\n      \tstruct commit *commit;\n    -@@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const char *prefix)\n    + \tchar *export_filename = NULL,\n    +@@ builtin/fast-export.c: int cmd_fast_export(int argc,\n      \t\t\t    N_(\"show progress after <n> objects\")),\n      \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n      \t\t\t     N_(\"select handling of signed tags\"),\n    @@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const ch\n      \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n      \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n      \t\t\t     parse_opt_tag_of_filtered_mode),\n    -@@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const char *prefix)\n    +@@ builtin/fast-export.c: int cmd_fast_export(int argc,\n      \tif (argc == 1)\n      \t\tusage_with_options (fast_export_usage, options);\n      \n    @@ builtin/fast-import.c: static void parse_new_commit(const char *arg)\n     +\t\t\tstrbuf_addstr(&new_data, \"gpgsig-sha256 \");\n     +\t\telse\n     +\t\t\tdie(\"Expected gpgsig algorithm sha1 or sha256, got %s\", sig_alg);\n    -+\t\tstring_list_split_in_place(&siglines, sig.buf, '\\n', -1);\n    ++\t\tstring_list_split_in_place(&siglines, sig.buf, \"\\n\", -1);\n     +\t\tstrbuf_add_separated_string_list(&new_data, \"\\n \", &siglines);\n     +\t\tstrbuf_addch(&new_data, '\\n');\n     +\t}\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n     +\t# between the two.\n     +\ttest_config i18n.commitEncoding ISO-8859-1 &&\n     +\tgit checkout -f -b commit-signing main &&\n    -+\techo Sign your name > file-sign &&\n    ++\techo Sign your name >file-sign &&\n     +\tgit add file-sign &&\n     +\tgit commit -S -m \"signed commit\" &&\n     +\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n     +\n     +test_expect_success GPG 'signed-commits default' '\n     +\n    -+\tunset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n    ++\tsane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n     +\ttest_must_fail git fast-export --reencode=no commit-signing &&\n     +\n     +\tFAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err &&\n     +\t! grep ^gpgsig output &&\n     +\tgrep \"^encoding ISO-8859-1\" output &&\n     +\ttest -s err &&\n    -+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n    -+\t\t(cd new &&\n    -+\t\t git fast-import &&\n    -+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n    ++\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n    ++\t\tcd new &&\n    ++\t\tgit fast-import &&\n    ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n    ++\t\ttest $COMMIT_SIGNING != $STRIPPED\n    ++\t)\n     +\n     +'\n     +\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n     +\tgit fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&\n     +\tgrep \"^gpgsig sha\" output &&\n     +\tgrep \"encoding ISO-8859-1\" output &&\n    -+\t(cd new &&\n    -+\t git fast-import &&\n    -+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n    ++\t(\n    ++\t\tcd new &&\n    ++\t\tgit fast-import &&\n    ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n    ++\t\ttest $COMMIT_SIGNING = $STRIPPED\n    ++\t) <output\n     +\n     +'\n     +\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n     +\tgrep \"^gpgsig sha\" output &&\n     +\tgrep \"encoding ISO-8859-1\" output &&\n     +\ttest -s err &&\n    -+\t(cd new &&\n    -+\t git fast-import &&\n    -+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n    ++\t(\n    ++\t\tcd new &&\n    ++\t\tgit fast-import &&\n    ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n    ++\t\ttest $COMMIT_SIGNING = $STRIPPED\n    ++\t) <output\n     +\n     +'\n     +\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n     +\tgit fast-export --signed-commits=strip --reencode=no commit-signing >output &&\n     +\t! grep ^gpgsig output &&\n     +\tgrep \"^encoding ISO-8859-1\" output &&\n    -+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n    -+\t\t(cd new &&\n    -+\t\t git fast-import &&\n    -+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n    ++\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n    ++\t\tcd new &&\n    ++\t\tgit fast-import &&\n    ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n    ++\t\ttest $COMMIT_SIGNING != $STRIPPED\n    ++\t)\n     +\n     +'\n     +\n    @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n     +\t! grep ^gpgsig output &&\n     +\tgrep \"^encoding ISO-8859-1\" output &&\n     +\ttest -s err &&\n    -+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n    -+\t\t(cd new &&\n    -+\t\t git fast-import &&\n    -+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n    ++\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n    ++\t\tcd new &&\n    ++\t\tgit fast-import &&\n    ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n    ++\t\ttest $COMMIT_SIGNING != $STRIPPED\n    ++\t)\n     +\n     +'\n     +\n      test_expect_success 'setup submodule' '\n      \n    + \ttest_config_global protocol.file.allow always &&\n      \tgit checkout -f main &&\n    -+\t{ git update-ref -d refs/heads/commit-signing || true; } &&\n    ++\ttest_might_fail git update-ref -d refs/heads/commit-signing &&\n      \tmkdir sub &&\n      \t(\n      \t\tcd sub &&\n\n\nChristian Couder (1):\n  fast-export: fix missing whitespace after switch\n\nLuke Shumaker (5):\n  git-fast-import.adoc: add missing LF in the BNF\n  fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n  git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n  fast-export: do not modify memory from get_commit_buffer\n  fast-export, fast-import: add support for signed-commits\n\n Documentation/git-fast-export.adoc |  25 +++-\n Documentation/git-fast-import.adoc |  20 ++-\n builtin/fast-export.c              | 189 +++++++++++++++++++++--------\n builtin/fast-import.c              |  23 ++++\n t/t9350-fast-export.sh             | 116 ++++++++++++++++++\n 5 files changed, 317 insertions(+), 56 deletions(-)\n\n-- \n2.48.1.401.g48e0d4203c\n\n"},{"id":"512901","messageId":"20250224142744.279643-4-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"[PATCH v5 3/6] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-02-24T14:27:41Z","receivedAt":"2025-02-24T14:28:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nThe --signed-tags= option takes one of five arguments specifying how to\nhandle signed tags during export.  Among these arguments, 'strip' is to\n'warn-strip' as 'verbatim' is to 'warn' (the unmentioned argument is\n'abort', which stops the fast-export process entirely).  That is,\nsignatures are either stripped or copied verbatim while exporting, with\nor without a warning.\n\nMatch the pattern and rename 'warn' to 'warn-verbatim' to make it clear\nthat it instructs fast-export to copy signatures verbatim.\n\nTo maintain backwards compatibility, 'warn' is still recognized as\ndeprecated synonym of 'warn-verbatim'.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-export.adoc |  6 +++---\n builtin/fast-export.c              |  8 ++++----\n t/t9350-fast-export.sh             | 18 ++++++++++++++++++\n 3 files changed, 25 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\nindex 752e4b9b01..ab9a315fa9 100644\n--- a/Documentation/git-fast-export.adoc\n+++ b/Documentation/git-fast-export.adoc\n@@ -27,7 +27,7 @@ OPTIONS\n \tInsert 'progress' statements every <n> objects, to be shown by\n \t'git fast-import' during import.\n \n---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n+--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n \tafter the export can change the tag names (which can also happen\n \twhen excluding revisions) the signatures will not match.\n@@ -36,8 +36,8 @@ When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n-exported and with 'warn', they will be exported, but you will see a\n-warning.\n+exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n+they will be exported, but you will see a warning.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 2bf787191a..2de2adc30e 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -36,7 +36,7 @@ static const char *fast_export_usage[] = {\n };\n \n static int progress;\n-static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -62,8 +62,8 @@ static int parse_opt_signed_tag_mode(const struct option *opt,\n \t\t*val = SIGNED_TAG_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n \t\t*val = VERBATIM;\n-\telse if (!strcmp(arg, \"warn\"))\n-\t\t*val = WARN;\n+\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n+\t\t*val = WARN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-strip\"))\n \t\t*val = WARN_STRIP;\n \telse if (!strcmp(arg, \"strip\"))\n@@ -833,7 +833,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN:\n+\t\t\tcase WARN_VERBATIM:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 40427883ec..cc110727fb 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -253,6 +253,24 @@ test_expect_success 'signed-tags=verbatim' '\n \n '\n \n+test_expect_success 'signed-tags=warn-verbatim' '\n+\n+\tgit fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n+# 'warn' is a backward-compatibility alias for 'warn-verbatim'; test\n+# that it keeps working.\n+test_expect_success 'signed-tags=warn' '\n+\n+\tgit fast-export --signed-tags=warn sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n test_expect_success 'signed-tags=strip' '\n \n \tgit fast-export --signed-tags=strip sign-your-name > output &&\n-- \n2.48.1.401.g48e0d4203c\n\n"},{"id":"512902","messageId":"20250224142744.279643-5-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"[PATCH v5 4/6] git-fast-export.txt: clarify why 'verbatim' may not be a good idea","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-02-24T14:27:42Z","receivedAt":"2025-02-24T14:28:10Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-export.adoc | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\nindex ab9a315fa9..1b19f17b78 100644\n--- a/Documentation/git-fast-export.adoc\n+++ b/Documentation/git-fast-export.adoc\n@@ -29,15 +29,19 @@ OPTIONS\n \n --signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n-\tafter the export can change the tag names (which can also happen\n-\twhen excluding revisions) the signatures will not match.\n+\tafter the export (or during the export, such as excluding\n+\trevisions) can change the hashes being signed, the signatures\n+\tmay become invalid.\n +\n When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n-they will be exported, but you will see a warning.\n+they will be exported, but you will see a warning.  'verbatim' and\n+'warn-verbatim' should only be used if you know that no\n+transformation affecting tags will be performed, or if you do not\n+care that the resulting tag will have an invalid signature.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n-- \n2.48.1.401.g48e0d4203c\n\n"},{"id":"512903","messageId":"20250224142744.279643-6-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"[PATCH v5 5/6] fast-export: do not modify memory from get_commit_buffer","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-02-24T14:27:43Z","receivedAt":"2025-02-24T14:28:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export's helper function find_encoding() takes a `const char *`, but\nmodifies that memory despite the `const`.  Ultimately, this memory came\nfrom get_commit_buffer(), and you're not supposed to modify the memory\nthat you get from get_commit_buffer().\n\nSo, get rid of find_encoding() in favor of commit.h:find_commit_header(),\nwhich gives back a string length, rather than mutating the memory to\ninsert a '\\0' terminator.\n\nBecause find_commit_header() detects the \"\\n\\n\" string that separates the\nheaders and the commit message, move the call to be above the\n`message = strstr(..., \"\\n\\n\")` call.  This helps readability, and allows\nfor the value of `encoding` to be used for a better value of \"...\" so that\nthe same memory doesn't need to be checked twice.  Introduce a\n`commit_buffer_cursor` variable to avoid writing an awkward\n`encoding ? encoding + encoding_len : committer_end` expression.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/fast-export.c | 61 +++++++++++++++++++++++--------------------\n 1 file changed, 33 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 2de2adc30e..39d43c2a29 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -510,21 +510,6 @@ static void show_filemodify(struct diff_queue_struct *q,\n \t}\n }\n \n-static const char *find_encoding(const char *begin, const char *end)\n-{\n-\tconst char *needle = \"\\nencoding \";\n-\tchar *bol, *eol;\n-\n-\tbol = memmem(begin, end ? end - begin : strlen(begin),\n-\t\t     needle, strlen(needle));\n-\tif (!bol)\n-\t\treturn NULL;\n-\tbol += strlen(needle);\n-\teol = strchrnul(bol, '\\n');\n-\t*eol = '\\0';\n-\treturn bol;\n-}\n-\n static char *anonymize_ref_component(void)\n {\n \tstatic int counter;\n@@ -630,9 +615,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t\t\t  struct string_list *paths_of_changed_objects)\n {\n \tint saved_output_format = rev->diffopt.output_format;\n-\tconst char *commit_buffer;\n+\tconst char *commit_buffer, *commit_buffer_cursor;\n \tconst char *author, *author_end, *committer, *committer_end;\n-\tconst char *encoding, *message;\n+\tconst char *encoding = NULL;\n+\tsize_t encoding_len;\n+\tconst char *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n \tconst char *refname;\n@@ -641,21 +628,35 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \trev->diffopt.output_format = DIFF_FORMAT_CALLBACK;\n \n \tparse_commit_or_die(commit);\n-\tcommit_buffer = repo_get_commit_buffer(the_repository, commit, NULL);\n-\tauthor = strstr(commit_buffer, \"\\nauthor \");\n+\tcommit_buffer_cursor = commit_buffer = repo_get_commit_buffer(the_repository, commit, NULL);\n+\n+\tauthor = strstr(commit_buffer_cursor, \"\\nauthor \");\n \tif (!author)\n \t\tdie(\"could not find author in commit %s\",\n \t\t    oid_to_hex(&commit->object.oid));\n \tauthor++;\n-\tauthor_end = strchrnul(author, '\\n');\n-\tcommitter = strstr(author_end, \"\\ncommitter \");\n+\tcommit_buffer_cursor = author_end = strchrnul(author, '\\n');\n+\n+\tcommitter = strstr(commit_buffer_cursor, \"\\ncommitter \");\n \tif (!committer)\n \t\tdie(\"could not find committer in commit %s\",\n \t\t    oid_to_hex(&commit->object.oid));\n \tcommitter++;\n-\tcommitter_end = strchrnul(committer, '\\n');\n-\tmessage = strstr(committer_end, \"\\n\\n\");\n-\tencoding = find_encoding(committer_end, message);\n+\tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n+\n+\t/*\n+\t * find_commit_header() gets a `+ 1` because\n+\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n+\t * of the previous line, but find_commit_header() wants a\n+\t * pointer to the beginning of the next line.\n+\t */\n+\tif (*commit_buffer_cursor == '\\n') {\n+\t\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n+\t\tif (encoding)\n+\t\t\tcommit_buffer_cursor = encoding + encoding_len;\n+\t}\n+\n+\tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n \tif (message)\n \t\tmessage += 2;\n \n@@ -694,16 +695,20 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tif (anonymize) {\n \t\treencoded = anonymize_commit_message();\n \t} else if (encoding) {\n+\t\tchar *buf;\n \t\tswitch (reencode_mode) {\n \t\tcase REENCODE_YES:\n-\t\t\treencoded = reencode_string(message, \"UTF-8\", encoding);\n+\t\t\tbuf = xstrfmt(\"%.*s\", (int)encoding_len, encoding);\n+\t\t\treencoded = reencode_string(message, \"UTF-8\", buf);\n+\t\t\tfree(buf);\n \t\t\tbreak;\n \t\tcase REENCODE_NO:\n \t\t\tbreak;\n \t\tcase REENCODE_ABORT:\n-\t\t\tdie(\"Encountered commit-specific encoding %s in commit \"\n+\t\t\tdie(\"Encountered commit-specific encoding %.*s in commit \"\n \t\t\t    \"%s; use --reencode=[yes|no] to handle it\",\n-\t\t\t    encoding, oid_to_hex(&commit->object.oid));\n+\t\t\t    (int)encoding_len, encoding,\n+\t\t\t    oid_to_hex(&commit->object.oid));\n \t\t}\n \t}\n \tif (!commit->parents)\n@@ -715,7 +720,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n \tif (!reencoded && encoding)\n-\t\tprintf(\"encoding %s\\n\", encoding);\n+\t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n \tprintf(\"data %u\\n%s\",\n \t       (unsigned)(reencoded\n \t\t\t  ? strlen(reencoded) : message\n-- \n2.48.1.401.g48e0d4203c\n\n"},{"id":"512904","messageId":"20250224142744.279643-7-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"[PATCH v5 6/6] fast-export, fast-import: add support for signed-commits","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-02-24T14:27:44Z","receivedAt":"2025-02-24T14:28:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export has a --signed-tags= option that controls how to handle tag\nsignatures.  However, there is no equivalent for commit signatures; it\njust silently strips the signature out of the commit (analogously to\n--signed-tags=strip).\n\nWhile signatures are generally problematic for fast-export/fast-import\n(because hashes are likely to change), if they're going to support tag\nsignatures, there's no reason to not also support commit signatures.\n\nSo, implement a --signed-commits= option that mirrors the --signed-tags=\noption.\n\nOn the fast-export side, try to be as much like signed-tags as possible,\nin both implementation and in user-interface.  This will change the\ndefault behavior to '--signed-commits=abort' from what is now\n'--signed-commits=strip'.  In order to provide an escape hatch for users\nof third-party tools that call fast-export and do not yet know of the\n--signed-commits= option, add an environment variable\n'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' that changes the default to\n'--signed-commits=warn-strip'.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-export.adoc |  11 +++\n Documentation/git-fast-import.adoc |  18 +++++\n builtin/fast-export.c              | 124 ++++++++++++++++++++++++-----\n builtin/fast-import.c              |  23 ++++++\n t/t9350-fast-export.sh             |  98 +++++++++++++++++++++++\n 5 files changed, 254 insertions(+), 20 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\nindex 1b19f17b78..8750dd150b 100644\n--- a/Documentation/git-fast-export.adoc\n+++ b/Documentation/git-fast-export.adoc\n@@ -43,6 +43,17 @@ they will be exported, but you will see a warning.  'verbatim' and\n transformation affecting tags will be performed, or if you do not\n care that the resulting tag will have an invalid signature.\n \n+--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n+\tSpecify how to handle signed commits.  Behaves exactly as\n+\t'--signed-tags', but for commits.\n++\n+Earlier versions this command that did not have '--signed-commits'\n+behaved as if '--signed-commits=strip'.  As an escape hatch for users\n+of tools that call 'git fast-export' but do not yet support\n+'--signed-commits', you may set the environment variable\n+'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default\n+from 'abort' to 'warn-strip'.\n+\n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n \tSince revisions and files to export can be limited by path,\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 8e0de618c0..7b107f5e8e 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -431,12 +431,21 @@ and control the current import process.  More detailed discussion\n Create or update a branch with a new commit, recording one logical\n change to the project.\n \n+////\n+Yes, it's intentional that the 'gpgsig' line doesn't have a trailing\n+`LF`; the definition of `data` has a byte-count prefix, so it\n+doesn't need an `LF` to act as a terminator (and `data` also already\n+includes an optional trailing `LF?` just in case you want to include\n+one).\n+////\n+\n ....\n \t'commit' SP <ref> LF\n \tmark?\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n+\t('gpgsig' SP <alg> LF data)?\n \t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n@@ -505,6 +514,15 @@ that was selected by the --date-format=<fmt> command-line option.\n See ``Date Formats'' above for the set of supported formats, and\n their syntax.\n \n+`gpgsig`\n+^^^^^^^^\n+\n+The optional `gpgsig` command is used to include a PGP/GPG signature\n+that signs the commit data.\n+\n+Here <alg> specifies which hashing algorithm is used for this\n+signature, either `sha1` or `sha256`.\n+\n `encoding`\n ^^^^^^^^^^\n The optional `encoding` command indicates the encoding of the commit\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 39d43c2a29..e34adb9ae8 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -35,8 +35,11 @@ static const char *fast_export_usage[] = {\n \tNULL\n };\n \n+enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN_WARN_STRIP };\n+\n static int progress;\n-static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum sign_mode signed_tag_mode = SIGN_ABORT;\n+static enum sign_mode signed_commit_mode = SIGN_ABORT;\n static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -53,23 +56,24 @@ static int anonymize;\n static struct hashmap anonymized_seeds;\n static struct revision_sources revision_sources;\n \n-static int parse_opt_signed_tag_mode(const struct option *opt,\n+static int parse_opt_sign_mode(const struct option *opt,\n \t\t\t\t     const char *arg, int unset)\n {\n-\tenum signed_tag_mode *val = opt->value;\n-\n-\tif (unset || !strcmp(arg, \"abort\"))\n-\t\t*val = SIGNED_TAG_ABORT;\n+\tenum sign_mode *val = opt->value;\n+\tif (unset)\n+\t\treturn 0;\n+\telse if (!strcmp(arg, \"abort\"))\n+\t\t*val = SIGN_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n-\t\t*val = VERBATIM;\n+\t\t*val = SIGN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n-\t\t*val = WARN_VERBATIM;\n+\t\t*val = SIGN_WARN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-strip\"))\n-\t\t*val = WARN_STRIP;\n+\t\t*val = SIGN_WARN_STRIP;\n \telse if (!strcmp(arg, \"strip\"))\n-\t\t*val = STRIP;\n+\t\t*val = SIGN_STRIP;\n \telse\n-\t\treturn error(\"Unknown signed-tags mode: %s\", arg);\n+\t\treturn error(\"Unknown %s mode: %s\", opt->long_name, arg);\n \treturn 0;\n }\n \n@@ -611,6 +615,44 @@ static void anonymize_ident_line(const char **beg, const char **end)\n \t*end = out->buf + out->len;\n }\n \n+/*\n+ * find_commit_multiline_header is similar to find_commit_header,\n+ * except that it handles multi-line headers, rathar than simply\n+ * returning the first line of the header.\n+ *\n+ * The returned string has had the ' ' line continuation markers\n+ * removed, and points to statically allocated memory (not to memory\n+ * within 'msg'), so it is only valid until the next call to\n+ * find_commit_multiline_header.\n+ *\n+ * If the header is found, then *end is set to point at the '\\n' in\n+ * msg that immediately follows the header value.\n+ */\n+static const char *find_commit_multiline_header(const char *msg,\n+\t\t\t\t\t\tconst char *key,\n+\t\t\t\t\t\tconst char **end)\n+{\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst char *bol, *eol;\n+\tsize_t len;\n+\n+\tbol = find_commit_header(msg, key, &len);\n+\tif (!bol)\n+\t\treturn NULL;\n+\teol = bol + len;\n+\tstrbuf_add(&val, bol, len);\n+\n+\twhile (eol[0] == '\\n' && eol[1] == ' ') {\n+\t\tbol = eol + 2;\n+\t\teol = strchrnul(bol, '\\n');\n+\t\tstrbuf_addch(&val, '\\n');\n+\t\tstrbuf_add(&val, bol, eol - bol);\n+\t}\n+\n+\t*end = eol;\n+\treturn strbuf_detach(&val, NULL);\n+}\n+\n static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t\t\t  struct string_list *paths_of_changed_objects)\n {\n@@ -619,6 +661,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tconst char *author, *author_end, *committer, *committer_end;\n \tconst char *encoding = NULL;\n \tsize_t encoding_len;\n+\tconst char *signature_alg = NULL, *signature = NULL;\n \tconst char *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n@@ -645,17 +688,25 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n \n \t/*\n-\t * find_commit_header() gets a `+ 1` because\n-\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n-\t * of the previous line, but find_commit_header() wants a\n+\t * find_commit_header() and find_commit_multiline_header() get\n+\t * a `+ 1` because commit_buffer_cursor points at the trailing\n+\t * \"\\n\" at the end of the previous line, but they want a\n \t * pointer to the beginning of the next line.\n \t */\n+\n \tif (*commit_buffer_cursor == '\\n') {\n \t\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n \t\tif (encoding)\n \t\t\tcommit_buffer_cursor = encoding + encoding_len;\n \t}\n \n+\tif (*commit_buffer_cursor == '\\n') {\n+\t\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n+\t\t\tsignature_alg = \"sha1\";\n+\t\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n+\t\t\tsignature_alg = \"sha256\";\n+\t}\n+\n \tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n \tif (message)\n \t\tmessage += 2;\n@@ -719,6 +770,31 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tprintf(\"%.*s\\n%.*s\\n\",\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n+\tif (signature) {\n+\t\tswitch (signed_commit_mode) {\n+\t\tcase SIGN_ABORT:\n+\t\t\tdie(\"encountered signed commit %s; use \"\n+\t\t\t    \"--signed-commits=<mode> to handle it\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tcase SIGN_WARN_VERBATIM:\n+\t\t\twarning(\"exporting signed commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_VERBATIM:\n+\t\t\tprintf(\"gpgsig %s\\ndata %u\\n%s\",\n+\t\t\t       signature_alg,\n+\t\t\t       (unsigned)strlen(signature),\n+\t\t\t       signature);\n+\t\t\tbreak;\n+\t\tcase SIGN_WARN_STRIP:\n+\t\t\twarning(\"stripping signature from commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_STRIP:\n+\t\t\tbreak;\n+\t\t}\n+\t\tfree((char *)signature);\n+\t}\n \tif (!reencoded && encoding)\n \t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n \tprintf(\"data %u\\n%s\",\n@@ -834,21 +910,21 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n \t\tif (signature)\n \t\t\tswitch (signed_tag_mode) {\n-\t\t\tcase SIGNED_TAG_ABORT:\n+\t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN_VERBATIM:\n+\t\t\tcase SIGN_WARN_VERBATIM:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase VERBATIM:\n+\t\t\tcase SIGN_VERBATIM:\n \t\t\t\tbreak;\n-\t\t\tcase WARN_STRIP:\n+\t\t\tcase SIGN_WARN_STRIP:\n \t\t\t\twarning(\"stripping signature from tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase STRIP:\n+\t\t\tcase SIGN_STRIP:\n \t\t\t\tmessage_size = signature + 1 - message;\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1194,6 +1270,7 @@ int cmd_fast_export(int argc,\n \t\t    const char *prefix,\n \t\t    struct repository *repo UNUSED)\n {\n+\tconst char *env_signed_commits_noabort;\n \tstruct rev_info revs;\n \tstruct commit *commit;\n \tchar *export_filename = NULL,\n@@ -1207,7 +1284,10 @@ int cmd_fast_export(int argc,\n \t\t\t    N_(\"show progress after <n> objects\")),\n \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of signed tags\"),\n-\t\t\t     parse_opt_signed_tag_mode),\n+\t\t\t     parse_opt_sign_mode),\n+\t\tOPT_CALLBACK(0, \"signed-commits\", &signed_commit_mode, N_(\"mode\"),\n+\t\t\t     N_(\"select handling of signed commits\"),\n+\t\t\t     parse_opt_sign_mode),\n \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n \t\t\t     parse_opt_tag_of_filtered_mode),\n@@ -1248,6 +1328,10 @@ int cmd_fast_export(int argc,\n \tif (argc == 1)\n \t\tusage_with_options (fast_export_usage, options);\n \n+\tenv_signed_commits_noabort = getenv(\"FAST_EXPORT_SIGNED_COMMITS_NOABORT\");\n+\tif (env_signed_commits_noabort && *env_signed_commits_noabort)\n+\t\tsigned_commit_mode = SIGN_WARN_STRIP;\n+\n \t/* we handle encodings */\n \tgit_config(git_default_config, NULL);\n \ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex d6a368a566..a5b33eb91e 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -2719,10 +2719,13 @@ static struct hash_list *parse_merge(unsigned int *count)\n \n static void parse_new_commit(const char *arg)\n {\n+\tstatic struct strbuf sig = STRBUF_INIT;\n \tstatic struct strbuf msg = STRBUF_INIT;\n+\tstruct string_list siglines = STRING_LIST_INIT_NODUP;\n \tstruct branch *b;\n \tchar *author = NULL;\n \tchar *committer = NULL;\n+\tchar *sig_alg = NULL;\n \tchar *encoding = NULL;\n \tstruct hash_list *merge_list = NULL;\n \tunsigned int merge_count;\n@@ -2746,6 +2749,13 @@ static void parse_new_commit(const char *arg)\n \t}\n \tif (!committer)\n \t\tdie(\"Expected committer but didn't get one\");\n+\tif (skip_prefix(command_buf.buf, \"gpgsig \", &v)) {\n+\t\tsig_alg = xstrdup(v);\n+\t\tread_next_command();\n+\t\tparse_data(&sig, 0, NULL);\n+\t\tread_next_command();\n+\t} else\n+\t\tstrbuf_setlen(&sig, 0);\n \tif (skip_prefix(command_buf.buf, \"encoding \", &v)) {\n \t\tencoding = xstrdup(v);\n \t\tread_next_command();\n@@ -2819,10 +2829,23 @@ static void parse_new_commit(const char *arg)\n \t\tstrbuf_addf(&new_data,\n \t\t\t\"encoding %s\\n\",\n \t\t\tencoding);\n+\tif (sig_alg) {\n+\t\tif (!strcmp(sig_alg, \"sha1\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig \");\n+\t\telse if (!strcmp(sig_alg, \"sha256\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig-sha256 \");\n+\t\telse\n+\t\t\tdie(\"Expected gpgsig algorithm sha1 or sha256, got %s\", sig_alg);\n+\t\tstring_list_split_in_place(&siglines, sig.buf, \"\\n\", -1);\n+\t\tstrbuf_add_separated_string_list(&new_data, \"\\n \", &siglines);\n+\t\tstrbuf_addch(&new_data, '\\n');\n+\t}\n \tstrbuf_addch(&new_data, '\\n');\n \tstrbuf_addbuf(&new_data, &msg);\n+\tstring_list_clear(&siglines, 1);\n \tfree(author);\n \tfree(committer);\n+\tfree(sig_alg);\n \tfree(encoding);\n \n \tif (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex cc110727fb..304bac5b1d 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -8,6 +8,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n test_expect_success 'setup' '\n \n@@ -284,10 +285,107 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\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+\t# that we can test that fast-import gets the ordering correct\n+\t# between the two.\n+\ttest_config i18n.commitEncoding ISO-8859-1 &&\n+\tgit checkout -f -b commit-signing main &&\n+\techo Sign your name >file-sign &&\n+\tgit add file-sign &&\n+\tgit commit -S -m \"signed commit\" &&\n+\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits default' '\n+\n+\tsane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n+\ttest_must_fail git fast-export --reencode=no commit-signing &&\n+\n+\tFAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n+\t\ttest $COMMIT_SIGNING != $STRIPPED\n+\t)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=abort' '\n+\n+\ttest_must_fail git fast-export --signed-commits=abort commit-signing\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=verbatim' '\n+\n+\tgit fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\t(\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n+\t\ttest $COMMIT_SIGNING = $STRIPPED\n+\t) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-verbatim' '\n+\n+\tgit fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\t(\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n+\t\ttest $COMMIT_SIGNING = $STRIPPED\n+\t) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=strip' '\n+\n+\tgit fast-export --signed-commits=strip --reencode=no commit-signing >output &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n+\t\ttest $COMMIT_SIGNING != $STRIPPED\n+\t)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-strip' '\n+\n+\tgit fast-export --signed-commits=warn-strip --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n+\t\ttest $COMMIT_SIGNING != $STRIPPED\n+\t)\n+\n+'\n+\n test_expect_success 'setup submodule' '\n \n \ttest_config_global protocol.file.allow always &&\n \tgit checkout -f main &&\n+\ttest_might_fail git update-ref -d refs/heads/commit-signing &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n-- \n2.48.1.401.g48e0d4203c\n\n"},{"id":"512921","messageId":"xmqq1pvn6zvg.fsf@gitster.g","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-24T17:01:23Z","receivedAt":"2025-02-24T17:01:27Z","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> Luke Shumaker sent the first 4 versions of this series in April 2021,\n> but it looks like he stopped before it got merged. Let's finish\n> polishing it.\n\nNice to see an old topic resurrected.\n\n> fast-export has an existing --signed-tags= option that controls how to\n> handle tag signatures.  However, there is no equivalent for commit\n> signatures; it just silently strips the signature out of the commit\n> (analogously to --signed-tags=strip).\n>\n> So implement a --signed-commits= flag in fast-export, and implement\n> the receiving side of it in fast-import.\n\nNice.\n\nI haven't thought about this topic obviously for a looooong time,\nbut I wonder we may want to have an option, which is independent\nfrom these --signed-tags/--signed-commits options addressed here,\nthat allows the person who performed the import to attest to the\nresult by adding their own signature on tags and commits, whether\nthese tags and commits were originally signed or not.\n\nObviously totally independent, orthogonal, and outside of the scope\nof this topic.\n\nThanks.\n"},{"id":"512927","messageId":"CABPp-BFG=g_tCz5HjjsLXNc41aYiGvLi_8oq3d5o9cBLKmd1og@mail.gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-5-christian.couder@gmail.com","subject":"Re: [PATCH v5 4/6] git-fast-export.txt: clarify why 'verbatim' may not be a good idea","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-02-24T19:26:28Z","receivedAt":"2025-02-24T19:26:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 24, 2025 at 6:28 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> From: Luke Shumaker <lukeshu@datawire.io>\n>\n> Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  Documentation/git-fast-export.adoc | 10 +++++++---\n>  1 file changed, 7 insertions(+), 3 deletions(-)\n>\n> diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\n> index ab9a315fa9..1b19f17b78 100644\n> --- a/Documentation/git-fast-export.adoc\n> +++ b/Documentation/git-fast-export.adoc\n> @@ -29,15 +29,19 @@ OPTIONS\n>\n>  --signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n>         Specify how to handle signed tags.  Since any transformation\n> -       after the export can change the tag names (which can also happen\n> -       when excluding revisions) the signatures will not match.\n> +       after the export (or during the export, such as excluding\n> +       revisions) can change the hashes being signed, the signatures\n> +       may become invalid.\n>  +\n>  When asking to 'abort' (which is the default), this program will die\n>  when encountering a signed tag.  With 'strip', the tags will silently\n>  be made unsigned, with 'warn-strip' they will be made unsigned but a\n>  warning will be displayed, with 'verbatim', they will be silently\n>  exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n> -they will be exported, but you will see a warning.\n> +they will be exported, but you will see a warning.  'verbatim' and\n> +'warn-verbatim' should only be used if you know that no\n> +transformation affecting tags will be performed, or if you do not\n\nperhaps it'd be worth clarifying this slightly to\n\n\"...transformation affecting tags or any commit in their history will\nbe performed...\"\n\nAlthough, I'm not sure if that's strong enough either.  Even if users\ndon't transform the fast-export output, the fast-export output will\nhave already possibly undergone transformations and fast-import might\nsend it through more.  For example, if someone had a permission\nrecorded as 644 or 100640 it'd be canonicalized to 100644.  If they\nhad a duplicate tree entry or an improperly sorted tree in their\nhistory, that would be corrected by fast-export + fast-import.  If\nthey had extended headers other than a commit signature, those would\nbe dropped.  So, maybe it needs to be something more like\n\n\"..transformation affecting tags or any commit in their history will\nbe performed by you or by fast-export or fast-import, or if you do\nnot....\n\n\n> +care that the resulting tag will have an invalid signature.\n>\n>  --tag-of-filtered-object=(abort|drop|rewrite)::\n>         Specify how to handle tags whose tagged object is filtered out.\n> --\n> 2.48.1.401.g48e0d4203c\n"},{"id":"512957","messageId":"CABPp-BHOvCWd6mMg0WdR4O5TfZS7TWtRCQCYPLnGpo5+jNHy5w@mail.gmail.com","threadId":"55538","inReplyTo":"xmqq1pvn6zvg.fsf@gitster.g","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-02-25T07:35:00Z","receivedAt":"2025-02-25T07:35:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 24, 2025 at 9:01 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > Luke Shumaker sent the first 4 versions of this series in April 2021,\n> > but it looks like he stopped before it got merged. Let's finish\n> > polishing it.\n>\n> Nice to see an old topic resurrected.\n>\n> > fast-export has an existing --signed-tags= option that controls how to\n> > handle tag signatures.  However, there is no equivalent for commit\n> > signatures; it just silently strips the signature out of the commit\n> > (analogously to --signed-tags=strip).\n> >\n> > So implement a --signed-commits= flag in fast-export, and implement\n> > the receiving side of it in fast-import.\n>\n> Nice.\n>\n> I haven't thought about this topic obviously for a looooong time,\n> but I wonder we may want to have an option, which is independent\n> from these --signed-tags/--signed-commits options addressed here,\n> that allows the person who performed the import to attest to the\n> result by adding their own signature on tags and commits, whether\n> these tags and commits were originally signed or not.\n\nFor what it's worth, this has been requested multiple times of\ngit-filter-repo, so there is some desire for this feature.\n\n> Obviously totally independent, orthogonal, and outside of the scope\n> of this topic.\n\nAgreed.\n"},{"id":"512958","messageId":"CABPp-BErRqke5DH7c3+u19iw1U5JgWYB=xcUwrE3NObf=EYz1Q@mail.gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-7-christian.couder@gmail.com","subject":"Re: [PATCH v5 6/6] fast-export, fast-import: add support for signed-commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-02-25T07:35:00Z","receivedAt":"2025-02-25T07:36:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 24, 2025 at 6:28 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n[...snip...]\n> diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\n> index 1b19f17b78..8750dd150b 100644\n> --- a/Documentation/git-fast-export.adoc\n> +++ b/Documentation/git-fast-export.adoc\n> @@ -43,6 +43,17 @@ they will be exported, but you will see a warning.  'verbatim' and\n>  transformation affecting tags will be performed, or if you do not\n>  care that the resulting tag will have an invalid signature.\n>\n> +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> +       Specify how to handle signed commits.  Behaves exactly as\n> +       '--signed-tags', but for commits.\n\nShould this also explicitly call out that the default is abort?  Yes,\nI know that...\n\n> ++\n> +Earlier versions this command that did not have '--signed-commits'\n> +behaved as if '--signed-commits=strip'.  As an escape hatch for users\n> +of tools that call 'git fast-export' but do not yet support\n> +'--signed-commits', you may set the environment variable\n> +'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default\n> +from 'abort' to 'warn-strip'.\n\n...this paragraph implies abort is the default, but I imagine we\neventually drop this paragraph, but\nit'd still be useful to have the default called out.\n\n[...snip...]\n\n> @@ -611,6 +615,44 @@ static void anonymize_ident_line(const char **beg, const char **end)\n>         *end = out->buf + out->len;\n>  }\n>\n> +/*\n> + * find_commit_multiline_header is similar to find_commit_header,\n> + * except that it handles multi-line headers, rathar than simply\n\ns/rathar/rather/\n\n[...snip...]\n"},{"id":"512965","messageId":"Z712Z0zGQD1zkdkZ@pks.im","threadId":"55538","inReplyTo":"CABPp-BHOvCWd6mMg0WdR4O5TfZS7TWtRCQCYPLnGpo5+jNHy5w@mail.gmail.com","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-02-25T07:51:03Z","receivedAt":"2025-02-25T07:51:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Feb 24, 2025 at 11:35:00PM -0800, Elijah Newren wrote:\n> On Mon, Feb 24, 2025 at 9:01 AM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > Christian Couder <christian.couder@gmail.com> writes:\n> >\n> > > Luke Shumaker sent the first 4 versions of this series in April 2021,\n> > > but it looks like he stopped before it got merged. Let's finish\n> > > polishing it.\n> >\n> > Nice to see an old topic resurrected.\n> >\n> > > fast-export has an existing --signed-tags= option that controls how to\n> > > handle tag signatures.  However, there is no equivalent for commit\n> > > signatures; it just silently strips the signature out of the commit\n> > > (analogously to --signed-tags=strip).\n> > >\n> > > So implement a --signed-commits= flag in fast-export, and implement\n> > > the receiving side of it in fast-import.\n> >\n> > Nice.\n> >\n> > I haven't thought about this topic obviously for a looooong time,\n> > but I wonder we may want to have an option, which is independent\n> > from these --signed-tags/--signed-commits options addressed here,\n> > that allows the person who performed the import to attest to the\n> > result by adding their own signature on tags and commits, whether\n> > these tags and commits were originally signed or not.\n> \n> For what it's worth, this has been requested multiple times of\n> git-filter-repo, so there is some desire for this feature.\n\nThis is also exactly the usecase we have been reviving this effort for\n:) We recently hit such a case where a customer was basically unable to\nuse git-filter-repo(1) due to commit signatures, so we wanted to help\nout and get this patch series landed so that the issue can ultimately be\naddressed in git-filter-repo(1).\n\nPatrick\n"},{"id":"513022","messageId":"98b4c9e7-4034-4692-bc86-f6b905dcc5aa@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-02-25T14:53:27Z","receivedAt":"2025-02-25T14:53:38Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Christian\n\nI've only glanced over this series, but I did notice a memory leak\n\nOn 24/02/2025 14:27, Christian Couder wrote:\n> \n>       + * The returned string has had the ' ' line continuation markers\n>      -+ * removed, and points to staticly allocated memory (not to memory\n>      ++ * removed, and points to statically allocated memory (not to memory\n\nThis corrects the spelling but the changes below remove the static \nbuffer so the user is now responsible for freeing the returned string. \nThat means this comment is wrong and I don't see any corresponding \nchanges to the callers to free the memory.\n\n>       + * within 'msg'), so it is only valid until the next call to\n>       + * find_commit_multiline_header.\n>       + *\n>      @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n>       +\t\t\t\t\t\tconst char *key,\n>       +\t\t\t\t\t\tconst char **end)\n>       +{\n>      -+\tstatic struct strbuf val = STRBUF_INIT;\n>      ++\tstruct strbuf val = STRBUF_INIT;\n>       +\tconst char *bol, *eol;\n>       +\tsize_t len;\n>       +\n>      -+\tstrbuf_reset(&val);\n>      -+\n>       +\tbol = find_commit_header(msg, key, &len);\n>       +\tif (!bol)\n>       +\t\treturn NULL;\n>      @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n>       +\t}\n>       +\n>       +\t*end = eol;\n>      -+\treturn val.buf;\n>      ++\treturn strbuf_detach(&val, NULL);\n>       +}\n\nBest Wishes\n\nPhillip\n\n>      - static char *reencode_message(const char *in_msg,\n>      - \t\t\t      const char *in_encoding, size_t in_encoding_len)\n>      + static void handle_commit(struct commit *commit, struct rev_info *rev,\n>      + \t\t\t  struct string_list *paths_of_changed_objects)\n>        {\n>       @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n>        \tconst char *author, *author_end, *committer, *committer_end;\n>      - \tconst char *encoding;\n>      + \tconst char *encoding = NULL;\n>        \tsize_t encoding_len;\n>      -+\tconst char *signature_alg = NULL, *signature;\n>      ++\tconst char *signature_alg = NULL, *signature = NULL;\n>        \tconst char *message;\n>        \tchar *reencoded = NULL;\n>        \tstruct commit_list *p;\n>       @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev,\n>      - \tcommitter++;\n>        \tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n>        \n>      --\t/* find_commit_header() gets a `+ 1` because\n>      + \t/*\n>      +-\t * find_commit_header() gets a `+ 1` because\n>       -\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n>       -\t * of the previous line, but find_commit_header() wants a\n>      -+\t/* find_commit_header() and find_commit_multiline_header() get\n>      ++\t * find_commit_header() and find_commit_multiline_header() get\n>       +\t * a `+ 1` because commit_buffer_cursor points at the trailing\n>       +\t * \"\\n\" at the end of the previous line, but they want a\n>      - \t * pointer to the beginning of the next line. */\n>      + \t * pointer to the beginning of the next line.\n>      + \t */\n>       +\n>      - \tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n>      - \tif (encoding)\n>      - \t\tcommit_buffer_cursor = encoding + encoding_len;\n>      + \tif (*commit_buffer_cursor == '\\n') {\n>      + \t\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n>      + \t\tif (encoding)\n>      + \t\t\tcommit_buffer_cursor = encoding + encoding_len;\n>      + \t}\n>        \n>      -+\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n>      -+\t\tsignature_alg = \"sha1\";\n>      -+\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n>      -+\t\tsignature_alg = \"sha256\";\n>      ++\tif (*commit_buffer_cursor == '\\n') {\n>      ++\t\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n>      ++\t\t\tsignature_alg = \"sha1\";\n>      ++\t\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n>      ++\t\t\tsignature_alg = \"sha256\";\n>      ++\t}\n>       +\n>        \tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n>        \tif (message)\n>      @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n>        \tprintf(\"%.*s\\n%.*s\\n\",\n>        \t       (int)(author_end - author), author,\n>        \t       (int)(committer_end - committer), committer);\n>      -+\tif (signature)\n>      -+\t\tswitch(signed_commit_mode) {\n>      ++\tif (signature) {\n>      ++\t\tswitch (signed_commit_mode) {\n>       +\t\tcase SIGN_ABORT:\n>       +\t\t\tdie(\"encountered signed commit %s; use \"\n>       +\t\t\t    \"--signed-commits=<mode> to handle it\",\n>      @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n>       +\t\tcase SIGN_STRIP:\n>       +\t\t\tbreak;\n>       +\t\t}\n>      ++\t\tfree((char *)signature);\n>      ++\t}\n>        \tif (!reencoded && encoding)\n>        \t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n>        \tprintf(\"data %u\\n%s\",\n>       @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n>        \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n>        \t\tif (signature)\n>      - \t\t\tswitch(signed_tag_mode) {\n>      + \t\t\tswitch (signed_tag_mode) {\n>       -\t\t\tcase SIGNED_TAG_ABORT:\n>       +\t\t\tcase SIGN_ABORT:\n>        \t\t\t\tdie(\"encountered signed tag %s; use \"\n>      @@ builtin/fast-export.c: static void handle_tag(const char *name, struct tag *tag)\n>        \t\t\t\tmessage_size = signature + 1 - message;\n>        \t\t\t\tbreak;\n>        \t\t\t}\n>      -@@ builtin/fast-export.c: static int parse_opt_anonymize_map(const struct option *opt,\n>      -\n>      - int cmd_fast_export(int argc, const char **argv, const char *prefix)\n>      +@@ builtin/fast-export.c: int cmd_fast_export(int argc,\n>      + \t\t    const char *prefix,\n>      + \t\t    struct repository *repo UNUSED)\n>        {\n>       +\tconst char *env_signed_commits_noabort;\n>        \tstruct rev_info revs;\n>      - \tstruct object_array commits = OBJECT_ARRAY_INIT;\n>        \tstruct commit *commit;\n>      -@@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const char *prefix)\n>      + \tchar *export_filename = NULL,\n>      +@@ builtin/fast-export.c: int cmd_fast_export(int argc,\n>        \t\t\t    N_(\"show progress after <n> objects\")),\n>        \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n>        \t\t\t     N_(\"select handling of signed tags\"),\n>      @@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const ch\n>        \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n>        \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n>        \t\t\t     parse_opt_tag_of_filtered_mode),\n>      -@@ builtin/fast-export.c: int cmd_fast_export(int argc, const char **argv, const char *prefix)\n>      +@@ builtin/fast-export.c: int cmd_fast_export(int argc,\n>        \tif (argc == 1)\n>        \t\tusage_with_options (fast_export_usage, options);\n>        \n>      @@ builtin/fast-import.c: static void parse_new_commit(const char *arg)\n>       +\t\t\tstrbuf_addstr(&new_data, \"gpgsig-sha256 \");\n>       +\t\telse\n>       +\t\t\tdie(\"Expected gpgsig algorithm sha1 or sha256, got %s\", sig_alg);\n>      -+\t\tstring_list_split_in_place(&siglines, sig.buf, '\\n', -1);\n>      ++\t\tstring_list_split_in_place(&siglines, sig.buf, \"\\n\", -1);\n>       +\t\tstrbuf_add_separated_string_list(&new_data, \"\\n \", &siglines);\n>       +\t\tstrbuf_addch(&new_data, '\\n');\n>       +\t}\n>      @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n>       +\t# between the two.\n>       +\ttest_config i18n.commitEncoding ISO-8859-1 &&\n>       +\tgit checkout -f -b commit-signing main &&\n>      -+\techo Sign your name > file-sign &&\n>      ++\techo Sign your name >file-sign &&\n>       +\tgit add file-sign &&\n>       +\tgit commit -S -m \"signed commit\" &&\n>       +\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n>      @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n>       +\n>       +test_expect_success GPG 'signed-commits default' '\n>       +\n>      -+\tunset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n>      ++\tsane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n>       +\ttest_must_fail git fast-export --reencode=no commit-signing &&\n>       +\n>       +\tFAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err &&\n>       +\t! grep ^gpgsig output &&\n>       +\tgrep \"^encoding ISO-8859-1\" output &&\n>       +\ttest -s err &&\n>      -+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n>      -+\t\t(cd new &&\n>      -+\t\t git fast-import &&\n>      -+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n>      ++\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n>      ++\t\tcd new &&\n>      ++\t\tgit fast-import &&\n>      ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n>      ++\t\ttest $COMMIT_SIGNING != $STRIPPED\n>      ++\t)\n>       +\n>       +'\n>       +\n>      @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n>       +\tgit fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&\n>       +\tgrep \"^gpgsig sha\" output &&\n>       +\tgrep \"encoding ISO-8859-1\" output &&\n>      -+\t(cd new &&\n>      -+\t git fast-import &&\n>      -+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n>      ++\t(\n>      ++\t\tcd new &&\n>      ++\t\tgit fast-import &&\n>      ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n>      ++\t\ttest $COMMIT_SIGNING = $STRIPPED\n>      ++\t) <output\n>       +\n>       +'\n>       +\n>      @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n>       +\tgrep \"^gpgsig sha\" output &&\n>       +\tgrep \"encoding ISO-8859-1\" output &&\n>       +\ttest -s err &&\n>      -+\t(cd new &&\n>      -+\t git fast-import &&\n>      -+\t test $COMMIT_SIGNING = $(git rev-parse --verify refs/heads/commit-signing)) <output\n>      ++\t(\n>      ++\t\tcd new &&\n>      ++\t\tgit fast-import &&\n>      ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n>      ++\t\ttest $COMMIT_SIGNING = $STRIPPED\n>      ++\t) <output\n>       +\n>       +'\n>       +\n>      @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n>       +\tgit fast-export --signed-commits=strip --reencode=no commit-signing >output &&\n>       +\t! grep ^gpgsig output &&\n>       +\tgrep \"^encoding ISO-8859-1\" output &&\n>      -+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n>      -+\t\t(cd new &&\n>      -+\t\t git fast-import &&\n>      -+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n>      ++\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n>      ++\t\tcd new &&\n>      ++\t\tgit fast-import &&\n>      ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n>      ++\t\ttest $COMMIT_SIGNING != $STRIPPED\n>      ++\t)\n>       +\n>       +'\n>       +\n>      @@ t/t9350-fast-export.sh: test_expect_success 'signed-tags=warn-strip' '\n>       +\t! grep ^gpgsig output &&\n>       +\tgrep \"^encoding ISO-8859-1\" output &&\n>       +\ttest -s err &&\n>      -+\tsed \"s/commit-signing/commit-strip-signing/\" output |\n>      -+\t\t(cd new &&\n>      -+\t\t git fast-import &&\n>      -+\t\t test $COMMIT_SIGNING != $(git rev-parse --verify refs/heads/commit-strip-signing))\n>      ++\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n>      ++\t\tcd new &&\n>      ++\t\tgit fast-import &&\n>      ++\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n>      ++\t\ttest $COMMIT_SIGNING != $STRIPPED\n>      ++\t)\n>       +\n>       +'\n>       +\n>        test_expect_success 'setup submodule' '\n>        \n>      + \ttest_config_global protocol.file.allow always &&\n>        \tgit checkout -f main &&\n>      -+\t{ git update-ref -d refs/heads/commit-signing || true; } &&\n>      ++\ttest_might_fail git update-ref -d refs/heads/commit-signing &&\n>        \tmkdir sub &&\n>        \t(\n>        \t\tcd sub &&\n> \n> \n> Christian Couder (1):\n>    fast-export: fix missing whitespace after switch\n> \n> Luke Shumaker (5):\n>    git-fast-import.adoc: add missing LF in the BNF\n>    fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n>    git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n>    fast-export: do not modify memory from get_commit_buffer\n>    fast-export, fast-import: add support for signed-commits\n> \n>   Documentation/git-fast-export.adoc |  25 +++-\n>   Documentation/git-fast-import.adoc |  20 ++-\n>   builtin/fast-export.c              | 189 +++++++++++++++++++++--------\n>   builtin/fast-import.c              |  23 ++++\n>   t/t9350-fast-export.sh             | 116 ++++++++++++++++++\n>   5 files changed, 317 insertions(+), 56 deletions(-)\n> \n\n"},{"id":"513026","messageId":"xmqqmseakn4b.fsf@gitster.g","threadId":"55538","inReplyTo":"CABPp-BErRqke5DH7c3+u19iw1U5JgWYB=xcUwrE3NObf=EYz1Q@mail.gmail.com","subject":"Re: [PATCH v5 6/6] fast-export, fast-import: add support for signed-commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-25T16:25:24Z","receivedAt":"2025-02-25T16:25:27Z","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> On Mon, Feb 24, 2025 at 6:28 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> [...snip...]\n>> diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\n>> index 1b19f17b78..8750dd150b 100644\n>> --- a/Documentation/git-fast-export.adoc\n>> +++ b/Documentation/git-fast-export.adoc\n>> @@ -43,6 +43,17 @@ they will be exported, but you will see a warning.  'verbatim' and\n>>  transformation affecting tags will be performed, or if you do not\n>>  care that the resulting tag will have an invalid signature.\n>>\n>> +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n>> +       Specify how to handle signed commits.  Behaves exactly as\n>> +       '--signed-tags', but for commits.\n>\n> Should this also explicitly call out that the default is abort?  Yes,\n> I know that...\n\nThanks.  We all tend to assume that readers know more than they\nactually are reasonably expected to know.\n\nI would have of course expected that any sensible designer would\npick 'abort' as the default, but I didn't know what we actually\nchose without looking at the code ;-)  \n\nIt would make sense to spell it out.\n\nThanks.\n"},{"id":"513027","messageId":"CABPp-BHDx4YAjCqWX_VUjHQQ-r1iDbw7UcUgLXtt1ZAJjNXD-Q@mail.gmail.com","threadId":"55538","inReplyTo":"Z712Z0zGQD1zkdkZ@pks.im","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-02-25T16:48:31Z","receivedAt":"2025-02-25T16:48:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 24, 2025 at 11:51 PM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Feb 24, 2025 at 11:35:00PM -0800, Elijah Newren wrote:\n> > On Mon, Feb 24, 2025 at 9:01 AM Junio C Hamano <gitster@pobox.com> wrote:\n> > >\n> > > Christian Couder <christian.couder@gmail.com> writes:\n> > >\n> > > > Luke Shumaker sent the first 4 versions of this series in April 2021,\n> > > > but it looks like he stopped before it got merged. Let's finish\n> > > > polishing it.\n> > >\n> > > Nice to see an old topic resurrected.\n> > >\n> > > > fast-export has an existing --signed-tags= option that controls how to\n> > > > handle tag signatures.  However, there is no equivalent for commit\n> > > > signatures; it just silently strips the signature out of the commit\n> > > > (analogously to --signed-tags=strip).\n> > > >\n> > > > So implement a --signed-commits= flag in fast-export, and implement\n> > > > the receiving side of it in fast-import.\n> > >\n> > > Nice.\n> > >\n> > > I haven't thought about this topic obviously for a looooong time,\n> > > but I wonder we may want to have an option, which is independent\n> > > from these --signed-tags/--signed-commits options addressed here,\n> > > that allows the person who performed the import to attest to the\n> > > result by adding their own signature on tags and commits, whether\n> > > these tags and commits were originally signed or not.\n> >\n> > For what it's worth, this has been requested multiple times of\n> > git-filter-repo, so there is some desire for this feature.\n>\n> This is also exactly the usecase we have been reviving this effort for\n> :) We recently hit such a case where a customer was basically unable to\n> use git-filter-repo(1) due to commit signatures, so we wanted to help\n> out and get this patch series landed so that the issue can ultimately be\n> addressed in git-filter-repo(1).\n\nI'm confused; this patch series doesn't implement the option Junio and\nI were talking about.  It only allows existing signatures to be\ncarried as-is, as opposed to resigning all the commits with the\ncurrent user's signature.\n"},{"id":"513028","messageId":"xmqqikoyklo9.fsf@gitster.g","threadId":"55538","inReplyTo":"CABPp-BHDx4YAjCqWX_VUjHQQ-r1iDbw7UcUgLXtt1ZAJjNXD-Q@mail.gmail.com","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-25T16:56:38Z","receivedAt":"2025-02-25T16:56:41Z","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>> This is also exactly the usecase we have been reviving this effort for\n>> :) We recently hit such a case where a customer was basically unable to\n>> use git-filter-repo(1) due to commit signatures, so we wanted to help\n>> out and get this patch series landed so that the issue can ultimately be\n>> addressed in git-filter-repo(1).\n>\n> I'm confused; this patch series doesn't implement the option Junio and\n> I were talking about.  It only allows existing signatures to be\n> carried as-is, as opposed to resigning all the commits with the\n> current user's signature.\n\nI read the \"can ultimately be\" as \"this series lays the groundwork\nby upstreaming what the earlier effort started and stops there. a\nfuture follow-up work will build on this to add more\".\n"},{"id":"513905","messageId":"20250310155746.879481-1-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250224142744.279643-1-christian.couder@gmail.com","subject":"[PATCH v6 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:57:40Z","receivedAt":"2025-03-10T15:58:04Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Luke Shumaker sent the first 4 versions of this series in April 2021,\nbut it looks like he stopped before it got merged. Let's finish\npolishing it.\n\nGoal of this series\n~~~~~~~~~~~~~~~~~~~\n\nfast-export has an existing --signed-tags= option that controls how to\nhandle tag signatures.  However, there is no equivalent for commit\nsignatures; it just silently strips the signature out of the commit\n(analogously to --signed-tags=strip).\n\nSo implement a --signed-commits= flag in fast-export, and implement\nthe receiving side of it in fast-import.\n\nBig picture goal\n~~~~~~~~~~~~~~~~\n\nIndependent from these --signed-tags/--signed-commits options\naddressed in this series, we want to have an option, that allows the\nperson who performed the import to attest to the result by adding\ntheir own signature on tags and commits, whether these tags and\ncommits were originally signed or not.\n\nThis series lays the groundwork for that future option by upstreaming\nthe earlier effort started by Luke Shumaker and stops there. Future\nfollow-up work will build on it towards the big picture goal.\n\nOverview of the changes since v5\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThere is no real code change since v5, only a commit message, the\ndocumentation and some code comments are improved.\n\nDetails of the changes since v5\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n  - Rebased on top of current 'master' branch at a36e024e98 (Merge\n    branch 'js/win-2.49-build-fixes', 2025-03-06). This is to get a\n    base as close as possible to v2.49.0 final.\n\n  - In patch 4/6 the commit message subject started with\n    \"git-fast-export.txt:\" instead of \"git-fast-export.adoc\" which has\n    been fixed.\n\n  - In patch 4/6 the documentation for `--signed-tags` in\n    \"Documentation/git-fast-export.adoc\" is improved to better explain\n    when it makes sense to use 'verbatim' and 'warn-verbatim', thanks\n    to Elijah.\n\n  - In patch 6/6 the documentation for `--signed-commits` in\n    \"Documentation/git-fast-export.adoc\" now spells out that its\n    default is 'abort', thanks to Elijah.\n\n  - In patch 6/6 a code comment in front of\n    find_commit_multiline_header() in \"builtin/fast-export.c\" has been\n    improved:\n\n      - a \"rathar\" vs \"rather\" typo has been fixed, thanks to Elijah,\n\n      - what should be done to the memory returned by the function has\n        been corrected, thanks to Phillip Wood.\n\nCI tests\n~~~~~~~~\n\nAll the CI tests passed, except perhaps the osx-gcc one which isn't\nfinished yet, see:\n\nhttps://github.com/chriscool/git/actions/runs/13767984505\n\nRange diff compared to version 5\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n1:  f97247e17d = 1:  395dc9b1d9 git-fast-import.adoc: add missing LF in the BNF\n2:  b71588563d = 2:  6265fd51aa fast-export: fix missing whitespace after switch\n3:  947bc267e6 = 3:  9e290bab22 fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n4:  45087db345 ! 4:  923885134f git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n    @@ Metadata\n     Author: Luke Shumaker <lukeshu@datawire.io>\n     \n      ## Commit message ##\n    -    git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n    +    git-fast-export.adoc: clarify why 'verbatim' may not be a good idea\n     \n         Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n    @@ Documentation/git-fast-export.adoc: OPTIONS\n      exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n     -they will be exported, but you will see a warning.\n     +they will be exported, but you will see a warning.  'verbatim' and\n    -+'warn-verbatim' should only be used if you know that no\n    -+transformation affecting tags will be performed, or if you do not\n    -+care that the resulting tag will have an invalid signature.\n    ++'warn-verbatim' should only be used if you know that no transformation\n    ++affecting tags or any commit in their history will be performed by you\n    ++or by fast-export or fast-import, or if you do not care that the\n    ++resulting tag will have an invalid signature.\n      \n      --tag-of-filtered-object=(abort|drop|rewrite)::\n      \tSpecify how to handle tags whose tagged object is filtered out.\n5:  20f085a790 = 5:  49f73ee6ef fast-export: do not modify memory from get_commit_buffer\n6:  48e0d4203c ! 6:  542c692e67 fast-export, fast-import: add support for signed-commits\n    @@ Commit message\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Documentation/git-fast-export.adoc ##\n    -@@ Documentation/git-fast-export.adoc: they will be exported, but you will see a warning.  'verbatim' and\n    - transformation affecting tags will be performed, or if you do not\n    - care that the resulting tag will have an invalid signature.\n    +@@ Documentation/git-fast-export.adoc: affecting tags or any commit in their history will be performed by you\n    + or by fast-export or fast-import, or if you do not care that the\n    + resulting tag will have an invalid signature.\n      \n     +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n     +\tSpecify how to handle signed commits.  Behaves exactly as\n    -+\t'--signed-tags', but for commits.\n    ++\t'--signed-tags', but for commits.  Default is 'abort'.\n     ++\n     +Earlier versions this command that did not have '--signed-commits'\n     +behaved as if '--signed-commits=strip'.  As an escape hatch for users\n    @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n      \n     +/*\n     + * find_commit_multiline_header is similar to find_commit_header,\n    -+ * except that it handles multi-line headers, rathar than simply\n    ++ * except that it handles multi-line headers, rather than simply\n     + * returning the first line of the header.\n     + *\n     + * The returned string has had the ' ' line continuation markers\n    -+ * removed, and points to statically allocated memory (not to memory\n    -+ * within 'msg'), so it is only valid until the next call to\n    -+ * find_commit_multiline_header.\n    ++ * removed, and points to allocated memory that must be free()d (not\n    ++ * to memory within 'msg').\n     + *\n     + * If the header is found, then *end is set to point at the '\\n' in\n     + * msg that immediately follows the header value.\n\n\nChristian Couder (1):\n  fast-export: fix missing whitespace after switch\n\nLuke Shumaker (5):\n  git-fast-import.adoc: add missing LF in the BNF\n  fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n  git-fast-export.adoc: clarify why 'verbatim' may not be a good idea\n  fast-export: do not modify memory from get_commit_buffer\n  fast-export, fast-import: add support for signed-commits\n\n Documentation/git-fast-export.adoc |  26 +++-\n Documentation/git-fast-import.adoc |  20 ++-\n builtin/fast-export.c              | 188 +++++++++++++++++++++--------\n builtin/fast-import.c              |  23 ++++\n t/t9350-fast-export.sh             | 116 ++++++++++++++++++\n 5 files changed, 317 insertions(+), 56 deletions(-)\n\n-- \n2.49.0.rc1.89.g148d1db992\n\n"},{"id":"513906","messageId":"20250310155746.879481-2-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250310155746.879481-1-christian.couder@gmail.com","subject":"[PATCH v6 1/6] git-fast-import.adoc: add missing LF in the BNF","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:57:41Z","receivedAt":"2025-03-10T15:58:05Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-import.adoc | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 58a2eaa51a..8e0de618c0 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -437,7 +437,7 @@ change to the project.\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n-\t('encoding' SP <encoding>)?\n+\t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n \t('merge' SP <commit-ish> LF)*\n-- \n2.49.0.rc1.89.g148d1db992\n\n"},{"id":"513907","messageId":"20250310155746.879481-3-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250310155746.879481-1-christian.couder@gmail.com","subject":"[PATCH v6 2/6] fast-export: fix missing whitespace after switch","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:57:42Z","receivedAt":"2025-03-10T15:58:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"\"Documentation/CodingGuidelines\" says that there should be whitespaces\naround operators like 'if', 'switch', 'for', etc.\n\nLet's fix this in \"builtin/fast-export.c\".\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/fast-export.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex a5c82eef1d..2bf787191a 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -694,7 +694,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tif (anonymize) {\n \t\treencoded = anonymize_commit_message();\n \t} else if (encoding) {\n-\t\tswitch(reencode_mode) {\n+\t\tswitch (reencode_mode) {\n \t\tcase REENCODE_YES:\n \t\t\treencoded = reencode_string(message, \"UTF-8\", encoding);\n \t\t\tbreak;\n@@ -828,7 +828,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\tconst char *signature = strstr(message,\n \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n \t\tif (signature)\n-\t\t\tswitch(signed_tag_mode) {\n+\t\t\tswitch (signed_tag_mode) {\n \t\t\tcase SIGNED_TAG_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n@@ -853,7 +853,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \ttagged = tag->tagged;\n \ttagged_mark = get_object_mark(tagged);\n \tif (!tagged_mark) {\n-\t\tswitch(tag_of_filtered_mode) {\n+\t\tswitch (tag_of_filtered_mode) {\n \t\tcase TAG_FILTERING_ABORT:\n \t\t\tdie(\"tag %s tags unexported object; use \"\n \t\t\t    \"--tag-of-filtered-object=<mode> to handle it\",\n@@ -965,7 +965,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\tcontinue;\n \t\t}\n \n-\t\tswitch(commit->object.type) {\n+\t\tswitch (commit->object.type) {\n \t\tcase OBJ_COMMIT:\n \t\t\tbreak;\n \t\tcase OBJ_BLOB:\n-- \n2.49.0.rc1.89.g148d1db992\n\n"},{"id":"513908","messageId":"20250310155746.879481-4-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250310155746.879481-1-christian.couder@gmail.com","subject":"[PATCH v6 3/6] fast-export: rename --signed-tags='warn' to 'warn-verbatim'","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:57:43Z","receivedAt":"2025-03-10T15:58:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nThe --signed-tags= option takes one of five arguments specifying how to\nhandle signed tags during export.  Among these arguments, 'strip' is to\n'warn-strip' as 'verbatim' is to 'warn' (the unmentioned argument is\n'abort', which stops the fast-export process entirely).  That is,\nsignatures are either stripped or copied verbatim while exporting, with\nor without a warning.\n\nMatch the pattern and rename 'warn' to 'warn-verbatim' to make it clear\nthat it instructs fast-export to copy signatures verbatim.\n\nTo maintain backwards compatibility, 'warn' is still recognized as\ndeprecated synonym of 'warn-verbatim'.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-export.adoc |  6 +++---\n builtin/fast-export.c              |  8 ++++----\n t/t9350-fast-export.sh             | 18 ++++++++++++++++++\n 3 files changed, 25 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\nindex 752e4b9b01..ab9a315fa9 100644\n--- a/Documentation/git-fast-export.adoc\n+++ b/Documentation/git-fast-export.adoc\n@@ -27,7 +27,7 @@ OPTIONS\n \tInsert 'progress' statements every <n> objects, to be shown by\n \t'git fast-import' during import.\n \n---signed-tags=(verbatim|warn|warn-strip|strip|abort)::\n+--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n \tafter the export can change the tag names (which can also happen\n \twhen excluding revisions) the signatures will not match.\n@@ -36,8 +36,8 @@ When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n-exported and with 'warn', they will be exported, but you will see a\n-warning.\n+exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n+they will be exported, but you will see a warning.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 2bf787191a..2de2adc30e 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -36,7 +36,7 @@ static const char *fast_export_usage[] = {\n };\n \n static int progress;\n-static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -62,8 +62,8 @@ static int parse_opt_signed_tag_mode(const struct option *opt,\n \t\t*val = SIGNED_TAG_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n \t\t*val = VERBATIM;\n-\telse if (!strcmp(arg, \"warn\"))\n-\t\t*val = WARN;\n+\telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n+\t\t*val = WARN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-strip\"))\n \t\t*val = WARN_STRIP;\n \telse if (!strcmp(arg, \"strip\"))\n@@ -833,7 +833,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN:\n+\t\t\tcase WARN_VERBATIM:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 40427883ec..cc110727fb 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -253,6 +253,24 @@ test_expect_success 'signed-tags=verbatim' '\n \n '\n \n+test_expect_success 'signed-tags=warn-verbatim' '\n+\n+\tgit fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n+# 'warn' is a backward-compatibility alias for 'warn-verbatim'; test\n+# that it keeps working.\n+test_expect_success 'signed-tags=warn' '\n+\n+\tgit fast-export --signed-tags=warn sign-your-name >output 2>err &&\n+\tgrep PGP output &&\n+\ttest -s err\n+\n+'\n+\n test_expect_success 'signed-tags=strip' '\n \n \tgit fast-export --signed-tags=strip sign-your-name > output &&\n-- \n2.49.0.rc1.89.g148d1db992\n\n"},{"id":"513909","messageId":"20250310155746.879481-5-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250310155746.879481-1-christian.couder@gmail.com","subject":"[PATCH v6 4/6] git-fast-export.adoc: clarify why 'verbatim' may not be a good idea","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:57:44Z","receivedAt":"2025-03-10T15:58:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-export.adoc | 11 ++++++++---\n 1 file changed, 8 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\nindex ab9a315fa9..2bb52261a0 100644\n--- a/Documentation/git-fast-export.adoc\n+++ b/Documentation/git-fast-export.adoc\n@@ -29,15 +29,20 @@ OPTIONS\n \n --signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n \tSpecify how to handle signed tags.  Since any transformation\n-\tafter the export can change the tag names (which can also happen\n-\twhen excluding revisions) the signatures will not match.\n+\tafter the export (or during the export, such as excluding\n+\trevisions) can change the hashes being signed, the signatures\n+\tmay become invalid.\n +\n When asking to 'abort' (which is the default), this program will die\n when encountering a signed tag.  With 'strip', the tags will silently\n be made unsigned, with 'warn-strip' they will be made unsigned but a\n warning will be displayed, with 'verbatim', they will be silently\n exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n-they will be exported, but you will see a warning.\n+they will be exported, but you will see a warning.  'verbatim' and\n+'warn-verbatim' should only be used if you know that no transformation\n+affecting tags or any commit in their history will be performed by you\n+or by fast-export or fast-import, or if you do not care that the\n+resulting tag will have an invalid signature.\n \n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n-- \n2.49.0.rc1.89.g148d1db992\n\n"},{"id":"513910","messageId":"20250310155746.879481-6-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250310155746.879481-1-christian.couder@gmail.com","subject":"[PATCH v6 5/6] fast-export: do not modify memory from get_commit_buffer","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:57:45Z","receivedAt":"2025-03-10T15:58:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export's helper function find_encoding() takes a `const char *`, but\nmodifies that memory despite the `const`.  Ultimately, this memory came\nfrom get_commit_buffer(), and you're not supposed to modify the memory\nthat you get from get_commit_buffer().\n\nSo, get rid of find_encoding() in favor of commit.h:find_commit_header(),\nwhich gives back a string length, rather than mutating the memory to\ninsert a '\\0' terminator.\n\nBecause find_commit_header() detects the \"\\n\\n\" string that separates the\nheaders and the commit message, move the call to be above the\n`message = strstr(..., \"\\n\\n\")` call.  This helps readability, and allows\nfor the value of `encoding` to be used for a better value of \"...\" so that\nthe same memory doesn't need to be checked twice.  Introduce a\n`commit_buffer_cursor` variable to avoid writing an awkward\n`encoding ? encoding + encoding_len : committer_end` expression.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/fast-export.c | 61 +++++++++++++++++++++++--------------------\n 1 file changed, 33 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 2de2adc30e..39d43c2a29 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -510,21 +510,6 @@ static void show_filemodify(struct diff_queue_struct *q,\n \t}\n }\n \n-static const char *find_encoding(const char *begin, const char *end)\n-{\n-\tconst char *needle = \"\\nencoding \";\n-\tchar *bol, *eol;\n-\n-\tbol = memmem(begin, end ? end - begin : strlen(begin),\n-\t\t     needle, strlen(needle));\n-\tif (!bol)\n-\t\treturn NULL;\n-\tbol += strlen(needle);\n-\teol = strchrnul(bol, '\\n');\n-\t*eol = '\\0';\n-\treturn bol;\n-}\n-\n static char *anonymize_ref_component(void)\n {\n \tstatic int counter;\n@@ -630,9 +615,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t\t\t  struct string_list *paths_of_changed_objects)\n {\n \tint saved_output_format = rev->diffopt.output_format;\n-\tconst char *commit_buffer;\n+\tconst char *commit_buffer, *commit_buffer_cursor;\n \tconst char *author, *author_end, *committer, *committer_end;\n-\tconst char *encoding, *message;\n+\tconst char *encoding = NULL;\n+\tsize_t encoding_len;\n+\tconst char *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n \tconst char *refname;\n@@ -641,21 +628,35 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \trev->diffopt.output_format = DIFF_FORMAT_CALLBACK;\n \n \tparse_commit_or_die(commit);\n-\tcommit_buffer = repo_get_commit_buffer(the_repository, commit, NULL);\n-\tauthor = strstr(commit_buffer, \"\\nauthor \");\n+\tcommit_buffer_cursor = commit_buffer = repo_get_commit_buffer(the_repository, commit, NULL);\n+\n+\tauthor = strstr(commit_buffer_cursor, \"\\nauthor \");\n \tif (!author)\n \t\tdie(\"could not find author in commit %s\",\n \t\t    oid_to_hex(&commit->object.oid));\n \tauthor++;\n-\tauthor_end = strchrnul(author, '\\n');\n-\tcommitter = strstr(author_end, \"\\ncommitter \");\n+\tcommit_buffer_cursor = author_end = strchrnul(author, '\\n');\n+\n+\tcommitter = strstr(commit_buffer_cursor, \"\\ncommitter \");\n \tif (!committer)\n \t\tdie(\"could not find committer in commit %s\",\n \t\t    oid_to_hex(&commit->object.oid));\n \tcommitter++;\n-\tcommitter_end = strchrnul(committer, '\\n');\n-\tmessage = strstr(committer_end, \"\\n\\n\");\n-\tencoding = find_encoding(committer_end, message);\n+\tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n+\n+\t/*\n+\t * find_commit_header() gets a `+ 1` because\n+\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n+\t * of the previous line, but find_commit_header() wants a\n+\t * pointer to the beginning of the next line.\n+\t */\n+\tif (*commit_buffer_cursor == '\\n') {\n+\t\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n+\t\tif (encoding)\n+\t\t\tcommit_buffer_cursor = encoding + encoding_len;\n+\t}\n+\n+\tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n \tif (message)\n \t\tmessage += 2;\n \n@@ -694,16 +695,20 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tif (anonymize) {\n \t\treencoded = anonymize_commit_message();\n \t} else if (encoding) {\n+\t\tchar *buf;\n \t\tswitch (reencode_mode) {\n \t\tcase REENCODE_YES:\n-\t\t\treencoded = reencode_string(message, \"UTF-8\", encoding);\n+\t\t\tbuf = xstrfmt(\"%.*s\", (int)encoding_len, encoding);\n+\t\t\treencoded = reencode_string(message, \"UTF-8\", buf);\n+\t\t\tfree(buf);\n \t\t\tbreak;\n \t\tcase REENCODE_NO:\n \t\t\tbreak;\n \t\tcase REENCODE_ABORT:\n-\t\t\tdie(\"Encountered commit-specific encoding %s in commit \"\n+\t\t\tdie(\"Encountered commit-specific encoding %.*s in commit \"\n \t\t\t    \"%s; use --reencode=[yes|no] to handle it\",\n-\t\t\t    encoding, oid_to_hex(&commit->object.oid));\n+\t\t\t    (int)encoding_len, encoding,\n+\t\t\t    oid_to_hex(&commit->object.oid));\n \t\t}\n \t}\n \tif (!commit->parents)\n@@ -715,7 +720,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n \tif (!reencoded && encoding)\n-\t\tprintf(\"encoding %s\\n\", encoding);\n+\t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n \tprintf(\"data %u\\n%s\",\n \t       (unsigned)(reencoded\n \t\t\t  ? strlen(reencoded) : message\n-- \n2.49.0.rc1.89.g148d1db992\n\n"},{"id":"513911","messageId":"20250310155746.879481-7-christian.couder@gmail.com","threadId":"55538","inReplyTo":"20250310155746.879481-1-christian.couder@gmail.com","subject":"[PATCH v6 6/6] fast-export, fast-import: add support for signed-commits","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:57:46Z","receivedAt":"2025-03-10T15:58:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nfast-export has a --signed-tags= option that controls how to handle tag\nsignatures.  However, there is no equivalent for commit signatures; it\njust silently strips the signature out of the commit (analogously to\n--signed-tags=strip).\n\nWhile signatures are generally problematic for fast-export/fast-import\n(because hashes are likely to change), if they're going to support tag\nsignatures, there's no reason to not also support commit signatures.\n\nSo, implement a --signed-commits= option that mirrors the --signed-tags=\noption.\n\nOn the fast-export side, try to be as much like signed-tags as possible,\nin both implementation and in user-interface.  This will change the\ndefault behavior to '--signed-commits=abort' from what is now\n'--signed-commits=strip'.  In order to provide an escape hatch for users\nof third-party tools that call fast-export and do not yet know of the\n--signed-commits= option, add an environment variable\n'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' that changes the default to\n'--signed-commits=warn-strip'.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-fast-export.adoc |  11 +++\n Documentation/git-fast-import.adoc |  18 +++++\n builtin/fast-export.c              | 123 ++++++++++++++++++++++++-----\n builtin/fast-import.c              |  23 ++++++\n t/t9350-fast-export.sh             |  98 +++++++++++++++++++++++\n 5 files changed, 253 insertions(+), 20 deletions(-)\n\ndiff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\nindex 2bb52261a0..413a527496 100644\n--- a/Documentation/git-fast-export.adoc\n+++ b/Documentation/git-fast-export.adoc\n@@ -44,6 +44,17 @@ affecting tags or any commit in their history will be performed by you\n or by fast-export or fast-import, or if you do not care that the\n resulting tag will have an invalid signature.\n \n+--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n+\tSpecify how to handle signed commits.  Behaves exactly as\n+\t'--signed-tags', but for commits.  Default is 'abort'.\n++\n+Earlier versions this command that did not have '--signed-commits'\n+behaved as if '--signed-commits=strip'.  As an escape hatch for users\n+of tools that call 'git fast-export' but do not yet support\n+'--signed-commits', you may set the environment variable\n+'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default\n+from 'abort' to 'warn-strip'.\n+\n --tag-of-filtered-object=(abort|drop|rewrite)::\n \tSpecify how to handle tags whose tagged object is filtered out.\n \tSince revisions and files to export can be limited by path,\ndiff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc\nindex 8e0de618c0..7b107f5e8e 100644\n--- a/Documentation/git-fast-import.adoc\n+++ b/Documentation/git-fast-import.adoc\n@@ -431,12 +431,21 @@ and control the current import process.  More detailed discussion\n Create or update a branch with a new commit, recording one logical\n change to the project.\n \n+////\n+Yes, it's intentional that the 'gpgsig' line doesn't have a trailing\n+`LF`; the definition of `data` has a byte-count prefix, so it\n+doesn't need an `LF` to act as a terminator (and `data` also already\n+includes an optional trailing `LF?` just in case you want to include\n+one).\n+////\n+\n ....\n \t'commit' SP <ref> LF\n \tmark?\n \toriginal-oid?\n \t('author' (SP <name>)? SP LT <email> GT SP <when> LF)?\n \t'committer' (SP <name>)? SP LT <email> GT SP <when> LF\n+\t('gpgsig' SP <alg> LF data)?\n \t('encoding' SP <encoding> LF)?\n \tdata\n \t('from' SP <commit-ish> LF)?\n@@ -505,6 +514,15 @@ that was selected by the --date-format=<fmt> command-line option.\n See ``Date Formats'' above for the set of supported formats, and\n their syntax.\n \n+`gpgsig`\n+^^^^^^^^\n+\n+The optional `gpgsig` command is used to include a PGP/GPG signature\n+that signs the commit data.\n+\n+Here <alg> specifies which hashing algorithm is used for this\n+signature, either `sha1` or `sha256`.\n+\n `encoding`\n ^^^^^^^^^^\n The optional `encoding` command indicates the encoding of the commit\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 39d43c2a29..126980f724 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -35,8 +35,11 @@ static const char *fast_export_usage[] = {\n \tNULL\n };\n \n+enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN_WARN_STRIP };\n+\n static int progress;\n-static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN_VERBATIM, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;\n+static enum sign_mode signed_tag_mode = SIGN_ABORT;\n+static enum sign_mode signed_commit_mode = SIGN_ABORT;\n static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;\n static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;\n static int fake_missing_tagger;\n@@ -53,23 +56,24 @@ static int anonymize;\n static struct hashmap anonymized_seeds;\n static struct revision_sources revision_sources;\n \n-static int parse_opt_signed_tag_mode(const struct option *opt,\n+static int parse_opt_sign_mode(const struct option *opt,\n \t\t\t\t     const char *arg, int unset)\n {\n-\tenum signed_tag_mode *val = opt->value;\n-\n-\tif (unset || !strcmp(arg, \"abort\"))\n-\t\t*val = SIGNED_TAG_ABORT;\n+\tenum sign_mode *val = opt->value;\n+\tif (unset)\n+\t\treturn 0;\n+\telse if (!strcmp(arg, \"abort\"))\n+\t\t*val = SIGN_ABORT;\n \telse if (!strcmp(arg, \"verbatim\") || !strcmp(arg, \"ignore\"))\n-\t\t*val = VERBATIM;\n+\t\t*val = SIGN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-verbatim\") || !strcmp(arg, \"warn\"))\n-\t\t*val = WARN_VERBATIM;\n+\t\t*val = SIGN_WARN_VERBATIM;\n \telse if (!strcmp(arg, \"warn-strip\"))\n-\t\t*val = WARN_STRIP;\n+\t\t*val = SIGN_WARN_STRIP;\n \telse if (!strcmp(arg, \"strip\"))\n-\t\t*val = STRIP;\n+\t\t*val = SIGN_STRIP;\n \telse\n-\t\treturn error(\"Unknown signed-tags mode: %s\", arg);\n+\t\treturn error(\"Unknown %s mode: %s\", opt->long_name, arg);\n \treturn 0;\n }\n \n@@ -611,6 +615,43 @@ static void anonymize_ident_line(const char **beg, const char **end)\n \t*end = out->buf + out->len;\n }\n \n+/*\n+ * find_commit_multiline_header is similar to find_commit_header,\n+ * except that it handles multi-line headers, rather than simply\n+ * returning the first line of the header.\n+ *\n+ * The returned string has had the ' ' line continuation markers\n+ * removed, and points to allocated memory that must be free()d (not\n+ * to memory within 'msg').\n+ *\n+ * If the header is found, then *end is set to point at the '\\n' in\n+ * msg that immediately follows the header value.\n+ */\n+static const char *find_commit_multiline_header(const char *msg,\n+\t\t\t\t\t\tconst char *key,\n+\t\t\t\t\t\tconst char **end)\n+{\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst char *bol, *eol;\n+\tsize_t len;\n+\n+\tbol = find_commit_header(msg, key, &len);\n+\tif (!bol)\n+\t\treturn NULL;\n+\teol = bol + len;\n+\tstrbuf_add(&val, bol, len);\n+\n+\twhile (eol[0] == '\\n' && eol[1] == ' ') {\n+\t\tbol = eol + 2;\n+\t\teol = strchrnul(bol, '\\n');\n+\t\tstrbuf_addch(&val, '\\n');\n+\t\tstrbuf_add(&val, bol, eol - bol);\n+\t}\n+\n+\t*end = eol;\n+\treturn strbuf_detach(&val, NULL);\n+}\n+\n static void handle_commit(struct commit *commit, struct rev_info *rev,\n \t\t\t  struct string_list *paths_of_changed_objects)\n {\n@@ -619,6 +660,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tconst char *author, *author_end, *committer, *committer_end;\n \tconst char *encoding = NULL;\n \tsize_t encoding_len;\n+\tconst char *signature_alg = NULL, *signature = NULL;\n \tconst char *message;\n \tchar *reencoded = NULL;\n \tstruct commit_list *p;\n@@ -645,17 +687,25 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tcommit_buffer_cursor = committer_end = strchrnul(committer, '\\n');\n \n \t/*\n-\t * find_commit_header() gets a `+ 1` because\n-\t * commit_buffer_cursor points at the trailing \"\\n\" at the end\n-\t * of the previous line, but find_commit_header() wants a\n+\t * find_commit_header() and find_commit_multiline_header() get\n+\t * a `+ 1` because commit_buffer_cursor points at the trailing\n+\t * \"\\n\" at the end of the previous line, but they want a\n \t * pointer to the beginning of the next line.\n \t */\n+\n \tif (*commit_buffer_cursor == '\\n') {\n \t\tencoding = find_commit_header(commit_buffer_cursor + 1, \"encoding\", &encoding_len);\n \t\tif (encoding)\n \t\t\tcommit_buffer_cursor = encoding + encoding_len;\n \t}\n \n+\tif (*commit_buffer_cursor == '\\n') {\n+\t\tif ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n+\t\t\tsignature_alg = \"sha1\";\n+\t\telse if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n+\t\t\tsignature_alg = \"sha256\";\n+\t}\n+\n \tmessage = strstr(commit_buffer_cursor, \"\\n\\n\");\n \tif (message)\n \t\tmessage += 2;\n@@ -719,6 +769,31 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,\n \tprintf(\"%.*s\\n%.*s\\n\",\n \t       (int)(author_end - author), author,\n \t       (int)(committer_end - committer), committer);\n+\tif (signature) {\n+\t\tswitch (signed_commit_mode) {\n+\t\tcase SIGN_ABORT:\n+\t\t\tdie(\"encountered signed commit %s; use \"\n+\t\t\t    \"--signed-commits=<mode> to handle it\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tcase SIGN_WARN_VERBATIM:\n+\t\t\twarning(\"exporting signed commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_VERBATIM:\n+\t\t\tprintf(\"gpgsig %s\\ndata %u\\n%s\",\n+\t\t\t       signature_alg,\n+\t\t\t       (unsigned)strlen(signature),\n+\t\t\t       signature);\n+\t\t\tbreak;\n+\t\tcase SIGN_WARN_STRIP:\n+\t\t\twarning(\"stripping signature from commit %s\",\n+\t\t\t\toid_to_hex(&commit->object.oid));\n+\t\t\t/* fallthru */\n+\t\tcase SIGN_STRIP:\n+\t\t\tbreak;\n+\t\t}\n+\t\tfree((char *)signature);\n+\t}\n \tif (!reencoded && encoding)\n \t\tprintf(\"encoding %.*s\\n\", (int)encoding_len, encoding);\n \tprintf(\"data %u\\n%s\",\n@@ -834,21 +909,21 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\t\t       \"\\n-----BEGIN PGP SIGNATURE-----\\n\");\n \t\tif (signature)\n \t\t\tswitch (signed_tag_mode) {\n-\t\t\tcase SIGNED_TAG_ABORT:\n+\t\t\tcase SIGN_ABORT:\n \t\t\t\tdie(\"encountered signed tag %s; use \"\n \t\t\t\t    \"--signed-tags=<mode> to handle it\",\n \t\t\t\t    oid_to_hex(&tag->object.oid));\n-\t\t\tcase WARN_VERBATIM:\n+\t\t\tcase SIGN_WARN_VERBATIM:\n \t\t\t\twarning(\"exporting signed tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase VERBATIM:\n+\t\t\tcase SIGN_VERBATIM:\n \t\t\t\tbreak;\n-\t\t\tcase WARN_STRIP:\n+\t\t\tcase SIGN_WARN_STRIP:\n \t\t\t\twarning(\"stripping signature from tag %s\",\n \t\t\t\t\toid_to_hex(&tag->object.oid));\n \t\t\t\t/* fallthru */\n-\t\t\tcase STRIP:\n+\t\t\tcase SIGN_STRIP:\n \t\t\t\tmessage_size = signature + 1 - message;\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1194,6 +1269,7 @@ int cmd_fast_export(int argc,\n \t\t    const char *prefix,\n \t\t    struct repository *repo UNUSED)\n {\n+\tconst char *env_signed_commits_noabort;\n \tstruct rev_info revs;\n \tstruct commit *commit;\n \tchar *export_filename = NULL,\n@@ -1207,7 +1283,10 @@ int cmd_fast_export(int argc,\n \t\t\t    N_(\"show progress after <n> objects\")),\n \t\tOPT_CALLBACK(0, \"signed-tags\", &signed_tag_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of signed tags\"),\n-\t\t\t     parse_opt_signed_tag_mode),\n+\t\t\t     parse_opt_sign_mode),\n+\t\tOPT_CALLBACK(0, \"signed-commits\", &signed_commit_mode, N_(\"mode\"),\n+\t\t\t     N_(\"select handling of signed commits\"),\n+\t\t\t     parse_opt_sign_mode),\n \t\tOPT_CALLBACK(0, \"tag-of-filtered-object\", &tag_of_filtered_mode, N_(\"mode\"),\n \t\t\t     N_(\"select handling of tags that tag filtered objects\"),\n \t\t\t     parse_opt_tag_of_filtered_mode),\n@@ -1248,6 +1327,10 @@ int cmd_fast_export(int argc,\n \tif (argc == 1)\n \t\tusage_with_options (fast_export_usage, options);\n \n+\tenv_signed_commits_noabort = getenv(\"FAST_EXPORT_SIGNED_COMMITS_NOABORT\");\n+\tif (env_signed_commits_noabort && *env_signed_commits_noabort)\n+\t\tsigned_commit_mode = SIGN_WARN_STRIP;\n+\n \t/* we handle encodings */\n \tgit_config(git_default_config, NULL);\n \ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 397a6f46ad..e432e8d5a1 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -2719,10 +2719,13 @@ static struct hash_list *parse_merge(unsigned int *count)\n \n static void parse_new_commit(const char *arg)\n {\n+\tstatic struct strbuf sig = STRBUF_INIT;\n \tstatic struct strbuf msg = STRBUF_INIT;\n+\tstruct string_list siglines = STRING_LIST_INIT_NODUP;\n \tstruct branch *b;\n \tchar *author = NULL;\n \tchar *committer = NULL;\n+\tchar *sig_alg = NULL;\n \tchar *encoding = NULL;\n \tstruct hash_list *merge_list = NULL;\n \tunsigned int merge_count;\n@@ -2746,6 +2749,13 @@ static void parse_new_commit(const char *arg)\n \t}\n \tif (!committer)\n \t\tdie(\"Expected committer but didn't get one\");\n+\tif (skip_prefix(command_buf.buf, \"gpgsig \", &v)) {\n+\t\tsig_alg = xstrdup(v);\n+\t\tread_next_command();\n+\t\tparse_data(&sig, 0, NULL);\n+\t\tread_next_command();\n+\t} else\n+\t\tstrbuf_setlen(&sig, 0);\n \tif (skip_prefix(command_buf.buf, \"encoding \", &v)) {\n \t\tencoding = xstrdup(v);\n \t\tread_next_command();\n@@ -2819,10 +2829,23 @@ static void parse_new_commit(const char *arg)\n \t\tstrbuf_addf(&new_data,\n \t\t\t\"encoding %s\\n\",\n \t\t\tencoding);\n+\tif (sig_alg) {\n+\t\tif (!strcmp(sig_alg, \"sha1\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig \");\n+\t\telse if (!strcmp(sig_alg, \"sha256\"))\n+\t\t\tstrbuf_addstr(&new_data, \"gpgsig-sha256 \");\n+\t\telse\n+\t\t\tdie(\"Expected gpgsig algorithm sha1 or sha256, got %s\", sig_alg);\n+\t\tstring_list_split_in_place(&siglines, sig.buf, \"\\n\", -1);\n+\t\tstrbuf_add_separated_string_list(&new_data, \"\\n \", &siglines);\n+\t\tstrbuf_addch(&new_data, '\\n');\n+\t}\n \tstrbuf_addch(&new_data, '\\n');\n \tstrbuf_addbuf(&new_data, &msg);\n+\tstring_list_clear(&siglines, 1);\n \tfree(author);\n \tfree(committer);\n+\tfree(sig_alg);\n \tfree(encoding);\n \n \tif (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex cc110727fb..304bac5b1d 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -8,6 +8,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n test_expect_success 'setup' '\n \n@@ -284,10 +285,107 @@ test_expect_success 'signed-tags=warn-strip' '\n \ttest -s err\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+\t# that we can test that fast-import gets the ordering correct\n+\t# between the two.\n+\ttest_config i18n.commitEncoding ISO-8859-1 &&\n+\tgit checkout -f -b commit-signing main &&\n+\techo Sign your name >file-sign &&\n+\tgit add file-sign &&\n+\tgit commit -S -m \"signed commit\" &&\n+\tCOMMIT_SIGNING=$(git rev-parse --verify commit-signing)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits default' '\n+\n+\tsane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT &&\n+\ttest_must_fail git fast-export --reencode=no commit-signing &&\n+\n+\tFAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n+\t\ttest $COMMIT_SIGNING != $STRIPPED\n+\t)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=abort' '\n+\n+\ttest_must_fail git fast-export --signed-commits=abort commit-signing\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=verbatim' '\n+\n+\tgit fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\t(\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n+\t\ttest $COMMIT_SIGNING = $STRIPPED\n+\t) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-verbatim' '\n+\n+\tgit fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err &&\n+\tgrep \"^gpgsig sha\" output &&\n+\tgrep \"encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\t(\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-signing) &&\n+\t\ttest $COMMIT_SIGNING = $STRIPPED\n+\t) <output\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=strip' '\n+\n+\tgit fast-export --signed-commits=strip --reencode=no commit-signing >output &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n+\t\ttest $COMMIT_SIGNING != $STRIPPED\n+\t)\n+\n+'\n+\n+test_expect_success GPG 'signed-commits=warn-strip' '\n+\n+\tgit fast-export --signed-commits=warn-strip --reencode=no commit-signing >output 2>err &&\n+\t! grep ^gpgsig output &&\n+\tgrep \"^encoding ISO-8859-1\" output &&\n+\ttest -s err &&\n+\tsed \"s/commit-signing/commit-strip-signing/\" output | (\n+\t\tcd new &&\n+\t\tgit fast-import &&\n+\t\tSTRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) &&\n+\t\ttest $COMMIT_SIGNING != $STRIPPED\n+\t)\n+\n+'\n+\n test_expect_success 'setup submodule' '\n \n \ttest_config_global protocol.file.allow always &&\n \tgit checkout -f main &&\n+\ttest_might_fail git update-ref -d refs/heads/commit-signing &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n-- \n2.49.0.rc1.89.g148d1db992\n\n"},{"id":"513912","messageId":"CAP8UFD3sSnvGwebKRvYRkf_V4RjUT0G=h0tpGDHkrKfvYZiZ6g@mail.gmail.com","threadId":"55538","inReplyTo":"CABPp-BFG=g_tCz5HjjsLXNc41aYiGvLi_8oq3d5o9cBLKmd1og@mail.gmail.com","subject":"Re: [PATCH v5 4/6] git-fast-export.txt: clarify why 'verbatim' may not be a good idea","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:58:31Z","receivedAt":"2025-03-10T15:58:45Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Feb 24, 2025 at 8:26 PM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Mon, Feb 24, 2025 at 6:28 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> >\n> > From: Luke Shumaker <lukeshu@datawire.io>\n> >\n> > Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n> > Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> > ---\n> >  Documentation/git-fast-export.adoc | 10 +++++++---\n> >  1 file changed, 7 insertions(+), 3 deletions(-)\n> >\n> > diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\n> > index ab9a315fa9..1b19f17b78 100644\n> > --- a/Documentation/git-fast-export.adoc\n> > +++ b/Documentation/git-fast-export.adoc\n> > @@ -29,15 +29,19 @@ OPTIONS\n> >\n> >  --signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> >         Specify how to handle signed tags.  Since any transformation\n> > -       after the export can change the tag names (which can also happen\n> > -       when excluding revisions) the signatures will not match.\n> > +       after the export (or during the export, such as excluding\n> > +       revisions) can change the hashes being signed, the signatures\n> > +       may become invalid.\n> >  +\n> >  When asking to 'abort' (which is the default), this program will die\n> >  when encountering a signed tag.  With 'strip', the tags will silently\n> >  be made unsigned, with 'warn-strip' they will be made unsigned but a\n> >  warning will be displayed, with 'verbatim', they will be silently\n> >  exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n> > -they will be exported, but you will see a warning.\n> > +they will be exported, but you will see a warning.  'verbatim' and\n> > +'warn-verbatim' should only be used if you know that no\n> > +transformation affecting tags will be performed, or if you do not\n>\n> perhaps it'd be worth clarifying this slightly to\n>\n> \"...transformation affecting tags or any commit in their history will\n> be performed...\"\n>\n> Although, I'm not sure if that's strong enough either.  Even if users\n> don't transform the fast-export output, the fast-export output will\n> have already possibly undergone transformations and fast-import might\n> send it through more.  For example, if someone had a permission\n> recorded as 644 or 100640 it'd be canonicalized to 100644.  If they\n> had a duplicate tree entry or an improperly sorted tree in their\n> history, that would be corrected by fast-export + fast-import.  If\n> they had extended headers other than a commit signature, those would\n> be dropped.  So, maybe it needs to be something more like\n>\n> \"..transformation affecting tags or any commit in their history will\n> be performed by you or by fast-export or fast-import, or if you do\n> not....\n\nI agree it's better like this, so this is used in the next version.\n\n> > +care that the resulting tag will have an invalid signature.\n\nThanks!\n"},{"id":"513913","messageId":"CAP8UFD0MxxqxgZHe5_7do_d1ub=OLRZyAuqRX5QHFzrDZNvt=Q@mail.gmail.com","threadId":"55538","inReplyTo":"CABPp-BErRqke5DH7c3+u19iw1U5JgWYB=xcUwrE3NObf=EYz1Q@mail.gmail.com","subject":"Re: [PATCH v5 6/6] fast-export, fast-import: add support for signed-commits","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:58:52Z","receivedAt":"2025-03-10T15:59:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 25, 2025 at 8:36 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Mon, Feb 24, 2025 at 6:28 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> [...snip...]\n> > diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc\n> > index 1b19f17b78..8750dd150b 100644\n> > --- a/Documentation/git-fast-export.adoc\n> > +++ b/Documentation/git-fast-export.adoc\n> > @@ -43,6 +43,17 @@ they will be exported, but you will see a warning.  'verbatim' and\n> >  transformation affecting tags will be performed, or if you do not\n> >  care that the resulting tag will have an invalid signature.\n> >\n> > +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n> > +       Specify how to handle signed commits.  Behaves exactly as\n> > +       '--signed-tags', but for commits.\n>\n> Should this also explicitly call out that the default is abort?\n\nYeah, that might help, so \"Default is 'abort'.\" has been added in the\nnext version.\n\n>  Yes,\n> I know that...\n>\n> > ++\n> > +Earlier versions this command that did not have '--signed-commits'\n> > +behaved as if '--signed-commits=strip'.  As an escape hatch for users\n> > +of tools that call 'git fast-export' but do not yet support\n> > +'--signed-commits', you may set the environment variable\n> > +'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default\n> > +from 'abort' to 'warn-strip'.\n>\n> ...this paragraph implies abort is the default, but I imagine we\n> eventually drop this paragraph, but\n> it'd still be useful to have the default called out.\n\nWe could still rely on the fact that the doc above says \"Behaves\nexactly as '--signed-tags', but for commits.\" and the default for\n'--signed-tags' is 'abort', but I agree that it can still help to\nspell it out.\n\n> [...snip...]\n>\n> > @@ -611,6 +615,44 @@ static void anonymize_ident_line(const char **beg, const char **end)\n> >         *end = out->buf + out->len;\n> >  }\n> >\n> > +/*\n> > + * find_commit_multiline_header is similar to find_commit_header,\n> > + * except that it handles multi-line headers, rathar than simply\n>\n> s/rathar/rather/\n\nFixed in the next version. Thanks.\n"},{"id":"513914","messageId":"CAP8UFD1TyDQahYOm9D8ohU-F95XneOgk7fg5mSH_k+s3ZG7omg@mail.gmail.com","threadId":"55538","inReplyTo":"98b4c9e7-4034-4692-bc86-f6b905dcc5aa@gmail.com","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:59:01Z","receivedAt":"2025-03-10T15:59:15Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Phillip,\n\nOn Tue, Feb 25, 2025 at 3:53 PM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Christian\n>\n> I've only glanced over this series,\n\nThanks for taking a look at it!\n\n> but I did notice a memory leak\n>\n> On 24/02/2025 14:27, Christian Couder wrote:\n> >\n> >       + * The returned string has had the ' ' line continuation markers\n> >      -+ * removed, and points to staticly allocated memory (not to memory\n> >      ++ * removed, and points to statically allocated memory (not to memory\n>\n> This corrects the spelling but the changes below remove the static\n> buffer so the user is now responsible for freeing the returned string.\n> That means this comment is wrong\n\nYeah, this part of the comment is wrong. I have changed it in the next\nversion to the following:\n\n * The returned string has had the ' ' line continuation markers\n * removed, and points to allocated memory that must be free()d (not\n * to memory within 'msg').\n\n> and I don't see any corresponding\n> changes to the callers to free the memory.\n\nIt is called by the following lines:\n\n> >      -+       if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n> >      -+               signature_alg = \"sha1\";\n> >      -+       else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n> >      -+               signature_alg = \"sha256\";\n> >      ++       if (*commit_buffer_cursor == '\\n') {\n> >      ++               if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig\", &commit_buffer_cursor)))\n> >      ++                       signature_alg = \"sha1\";\n> >      ++               else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, \"gpgsig-sha256\", &commit_buffer_cursor)))\n> >      ++                       signature_alg = \"sha256\";\n> >      ++       }\n\nso the 'signature' variable points to the allocated memory, and then\nit's used like this:\n\n> >      @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n> >               printf(\"%.*s\\n%.*s\\n\",\n> >                      (int)(author_end - author), author,\n> >                      (int)(committer_end - committer), committer);\n> >      -+       if (signature)\n> >      -+               switch(signed_commit_mode) {\n> >      ++       if (signature) {\n> >      ++               switch (signed_commit_mode) {\n> >       +               case SIGN_ABORT:\n> >       +                       die(\"encountered signed commit %s; use \"\n> >       +                           \"--signed-commits=<mode> to handle it\",\n> >      @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r\n> >       +               case SIGN_STRIP:\n> >       +                       break;\n> >       +               }\n> >      ++               free((char *)signature);\n\nAnd eventually the memory is freed by the added call to free() above.\n\n> >      ++       }\n\nBut yeah, the description of the changes since the previous version in\nthe cover letter might have done a better job of explaining this.\n"},{"id":"513915","messageId":"CAP8UFD1m2Lb=e-gQgR_oT7u67-S0_XY=OebOiRq6Tnn3g9-pkQ@mail.gmail.com","threadId":"55538","inReplyTo":"xmqqikoyklo9.fsf@gitster.g","subject":"Re: [PATCH v5 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-03-10T15:59:04Z","receivedAt":"2025-03-10T15:59:32Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Feb 25, 2025 at 5:56 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> >> This is also exactly the usecase we have been reviving this effort for\n> >> :) We recently hit such a case where a customer was basically unable to\n> >> use git-filter-repo(1) due to commit signatures, so we wanted to help\n> >> out and get this patch series landed so that the issue can ultimately be\n> >> addressed in git-filter-repo(1).\n> >\n> > I'm confused; this patch series doesn't implement the option Junio and\n> > I were talking about.  It only allows existing signatures to be\n> > carried as-is, as opposed to resigning all the commits with the\n> > current user's signature.\n>\n> I read the \"can ultimately be\" as \"this series lays the groundwork\n> by upstreaming what the earlier effort started and stops there. a\n> future follow-up work will build on this to add more\".\n\nYeah, this is our goal. I have added the following section to the\ncover letter to clarify this:\n\nBig picture goal\n~~~~~~~~~~~~~~~~\n\nIndependent from these --signed-tags/--signed-commits options\naddressed in this series, we want to have an option, that allows the\nperson who performed the import to attest to the result by adding\ntheir own signature on tags and commits, whether these tags and\ncommits were originally signed or not.\n\nThis series lays the groundwork for that future option by upstreaming\nthe earlier effort started by Luke Shumaker and stops there. Future\nfollow-up work will build on it towards the big picture goal.\n"},{"id":"513949","messageId":"CABPp-BGyA8iBA0BFO8FcpZAMca94aVu2vHHRi4Oz=nCWxJSDPg@mail.gmail.com","threadId":"55538","inReplyTo":"20250310155746.879481-1-christian.couder@gmail.com","subject":"Re: [PATCH v6 0/6] fast-export, fast-import: add support for signed-commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-03-10T22:36:21Z","receivedAt":"2025-03-10T22:36:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Christian,\n\nOn Mon, Mar 10, 2025 at 8:58 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> Luke Shumaker sent the first 4 versions of this series in April 2021,\n> but it looks like he stopped before it got merged. Let's finish\n> polishing it.\n>\n> Goal of this series\n> ~~~~~~~~~~~~~~~~~~~\n>\n> fast-export has an existing --signed-tags= option that controls how to\n> handle tag signatures.  However, there is no equivalent for commit\n> signatures; it just silently strips the signature out of the commit\n> (analogously to --signed-tags=strip).\n>\n> So implement a --signed-commits= flag in fast-export, and implement\n> the receiving side of it in fast-import.\n>\n> Big picture goal\n> ~~~~~~~~~~~~~~~~\n>\n> Independent from these --signed-tags/--signed-commits options\n> addressed in this series, we want to have an option, that allows the\n> person who performed the import to attest to the result by adding\n> their own signature on tags and commits, whether these tags and\n> commits were originally signed or not.\n>\n> This series lays the groundwork for that future option by upstreaming\n> the earlier effort started by Luke Shumaker and stops there. Future\n> follow-up work will build on it towards the big picture goal.\n>\n> Overview of the changes since v5\n> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n>\n> There is no real code change since v5, only a commit message, the\n> documentation and some code comments are improved.\n>\n> Details of the changes since v5\n> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n>\n>   - Rebased on top of current 'master' branch at a36e024e98 (Merge\n>     branch 'js/win-2.49-build-fixes', 2025-03-06). This is to get a\n>     base as close as possible to v2.49.0 final.\n>\n>   - In patch 4/6 the commit message subject started with\n>     \"git-fast-export.txt:\" instead of \"git-fast-export.adoc\" which has\n>     been fixed.\n>\n>   - In patch 4/6 the documentation for `--signed-tags` in\n>     \"Documentation/git-fast-export.adoc\" is improved to better explain\n>     when it makes sense to use 'verbatim' and 'warn-verbatim', thanks\n>     to Elijah.\n>\n>   - In patch 6/6 the documentation for `--signed-commits` in\n>     \"Documentation/git-fast-export.adoc\" now spells out that its\n>     default is 'abort', thanks to Elijah.\n>\n>   - In patch 6/6 a code comment in front of\n>     find_commit_multiline_header() in \"builtin/fast-export.c\" has been\n>     improved:\n>\n>       - a \"rathar\" vs \"rather\" typo has been fixed, thanks to Elijah,\n>\n>       - what should be done to the memory returned by the function has\n>         been corrected, thanks to Phillip Wood.\n>\n> CI tests\n> ~~~~~~~~\n>\n> All the CI tests passed, except perhaps the osx-gcc one which isn't\n> finished yet, see:\n>\n> https://github.com/chriscool/git/actions/runs/13767984505\n>\n> Range diff compared to version 5\n> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n>\n> 1:  f97247e17d = 1:  395dc9b1d9 git-fast-import.adoc: add missing LF in the BNF\n> 2:  b71588563d = 2:  6265fd51aa fast-export: fix missing whitespace after switch\n> 3:  947bc267e6 = 3:  9e290bab22 fast-export: rename --signed-tags='warn' to 'warn-verbatim'\n> 4:  45087db345 ! 4:  923885134f git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n>     @@ Metadata\n>      Author: Luke Shumaker <lukeshu@datawire.io>\n>\n>       ## Commit message ##\n>     -    git-fast-export.txt: clarify why 'verbatim' may not be a good idea\n>     +    git-fast-export.adoc: clarify why 'verbatim' may not be a good idea\n>\n>          Signed-off-by: Luke Shumaker <lukeshu@datawire.io>\n>          Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n>     @@ Documentation/git-fast-export.adoc: OPTIONS\n>       exported and with 'warn-verbatim' (or 'warn', a deprecated synonym),\n>      -they will be exported, but you will see a warning.\n>      +they will be exported, but you will see a warning.  'verbatim' and\n>     -+'warn-verbatim' should only be used if you know that no\n>     -+transformation affecting tags will be performed, or if you do not\n>     -+care that the resulting tag will have an invalid signature.\n>     ++'warn-verbatim' should only be used if you know that no transformation\n>     ++affecting tags or any commit in their history will be performed by you\n>     ++or by fast-export or fast-import, or if you do not care that the\n>     ++resulting tag will have an invalid signature.\n>\n>       --tag-of-filtered-object=(abort|drop|rewrite)::\n>         Specify how to handle tags whose tagged object is filtered out.\n> 5:  20f085a790 = 5:  49f73ee6ef fast-export: do not modify memory from get_commit_buffer\n> 6:  48e0d4203c ! 6:  542c692e67 fast-export, fast-import: add support for signed-commits\n>     @@ Commit message\n>          Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n>\n>       ## Documentation/git-fast-export.adoc ##\n>     -@@ Documentation/git-fast-export.adoc: they will be exported, but you will see a warning.  'verbatim' and\n>     - transformation affecting tags will be performed, or if you do not\n>     - care that the resulting tag will have an invalid signature.\n>     +@@ Documentation/git-fast-export.adoc: affecting tags or any commit in their history will be performed by you\n>     + or by fast-export or fast-import, or if you do not care that the\n>     + resulting tag will have an invalid signature.\n>\n>      +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::\n>      +  Specify how to handle signed commits.  Behaves exactly as\n>     -+  '--signed-tags', but for commits.\n>     ++  '--signed-tags', but for commits.  Default is 'abort'.\n>      ++\n>      +Earlier versions this command that did not have '--signed-commits'\n>      +behaved as if '--signed-commits=strip'.  As an escape hatch for users\n>     @@ builtin/fast-export.c: static void anonymize_ident_line(const char **beg, const\n>\n>      +/*\n>      + * find_commit_multiline_header is similar to find_commit_header,\n>     -+ * except that it handles multi-line headers, rathar than simply\n>     ++ * except that it handles multi-line headers, rather than simply\n>      + * returning the first line of the header.\n>      + *\n>      + * The returned string has had the ' ' line continuation markers\n>     -+ * removed, and points to statically allocated memory (not to memory\n>     -+ * within 'msg'), so it is only valid until the next call to\n>     -+ * find_commit_multiline_header.\n>     ++ * removed, and points to allocated memory that must be free()d (not\n>     ++ * to memory within 'msg').\n>      + *\n>      + * If the header is found, then *end is set to point at the '\\n' in\n>      + * msg that immediately follows the header value.\n\nI didn't look closely at Phillip's comments or your changes related to\nthose, but the other changes in the range-diff address my comments\nfrom v5, so this version looks good to me.\n\nThanks!\n"}]}