* [PATCH] fast-(import|export): improve on the signature algorithm name @ 2025-04-24 20:39 Christian Couder 2025-04-24 21:19 ` Junio C Hamano ` (4 more replies) 0 siblings, 5 replies; 65+ messages in thread From: Christian Couder @ 2025-04-24 20:39 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin, Christian Couder, Christian Couder A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for signed-commits, 2025-03-10), added support for signed commits. However, when processing signatures `git fast-export` outputs "gpgsig sha1" not just when it encounters an OpenPGP SHA-1 signature, but also when it encounters an SSH or X.509 signature. This is not very informative to say the least, and this might prevent tools that process the output from easily and properly handling signatures. Let's improve on that by reusing the existing code from "gpg-interface.{c,h}" to detect the signature algorithm, and then put the signature algorithm name (like "openpgp", "x509" or "ssh") instead of "sha1" in the output. If we can't detect the signature algorithm we will use "unknown". It might be a signature added by an external tool and we should likely keep it. Similarly on the `git fast-import` side, let's use the existing code from "gpg-interface.{c,h}" to check if a signature algorithm name is valid. In case of an "unknown" signature algorithm name, we will warn but still keep it. Future work might implement several options to let users deal with it in different ways, and might implement checking known signatures too. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- This is a follow up from cc/signed-fast-export-import that was merged by 01d17c0530 (Merge branch 'cc/signed-fast-export-import', 2025-03-29) and introduced the support for signed commits. The format that this series implemented was lacking a bit, so the goal with this patch is to improve it and handle signed commits a bit more consistently in the code base. It also shows in the tests and in our documentation that SSH and X.509 signatures are supported. Documentation/git-fast-export.adoc | 5 +++ Documentation/git-fast-import.adoc | 15 +++++++- builtin/fast-export.c | 8 ++-- builtin/fast-import.c | 14 ++++--- gpg-interface.c | 11 ++++++ gpg-interface.h | 10 +++++ t/t9350-fast-export.sh | 60 +++++++++++++++++++++++++++++- 7 files changed, 112 insertions(+), 11 deletions(-) diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 413a527496..d03aeca781 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -54,6 +54,11 @@ of tools that call 'git fast-export' but do not yet support '--signed-commits', you may set the environment variable 'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default from 'abort' to 'warn-strip'. ++ +When exported, signature starts with "gpgsig <alg>" where <alg> is the +signature algorithm name as identified by Git (e.g. "openpgp", "x509", +"ssh", or "sha256" for SHA-256 OpenPGP signatures), or "unknown" for +signatures that can't be identified. --tag-of-filtered-object=(abort|drop|rewrite):: Specify how to handle tags whose tagged object is filtered out. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 7b107f5e8e..50b6d2cc1d 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -521,7 +521,20 @@ The optional `gpgsig` command is used to include a PGP/GPG signature that signs the commit data. Here <alg> specifies which hashing algorithm is used for this -signature, either `sha1` or `sha256`. +signature. Current valid values are: + +* "openpgp" for SHA-1 OpenPGP signatures, + +* "sha256" for SHA-256 OpenPGP signatures, + +* "x509" for X.509 (GPGSM) signatures, + +* "ssh", for SSH signatures, + +* "unknown" for signatures that can't be identified (a warning is + emitted). + +Signatures are not yet checked in the current implementation though. `encoding` ^^^^^^^^^^ diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 170126d41a..d00f02dc74 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -29,6 +29,7 @@ #include "quote.h" #include "remote.h" #include "blob.h" +#include "gpg-interface.h" static const char *fast_export_usage[] = { N_("git fast-export [<rev-list-opts>]"), @@ -700,9 +701,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, } if (*commit_buffer_cursor == '\n') { - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) - signature_alg = "sha1"; - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) + if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) { + const char *name = get_signature_name(signature); + signature_alg = name ? name : "unknown"; + } else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) signature_alg = "sha256"; } diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 63880b595c..59e991a03c 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -29,6 +29,7 @@ #include "commit-reach.h" #include "khash.h" #include "date.h" +#include "gpg-interface.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -2830,12 +2831,15 @@ static void parse_new_commit(const char *arg) "encoding %s\n", encoding); if (sig_alg) { - if (!strcmp(sig_alg, "sha1")) - strbuf_addstr(&new_data, "gpgsig "); - else if (!strcmp(sig_alg, "sha256")) + if (!strcmp(sig_alg, "sha256")) strbuf_addstr(&new_data, "gpgsig-sha256 "); - else - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); + else if (valid_signature_name(sig_alg)) + strbuf_addstr(&new_data, "gpgsig "); + else if (!strcmp(sig_alg, "unknown")) { + warning("Unknown gpgsig algorithm name!"); + strbuf_addstr(&new_data, "gpgsig "); + } else + die("Invalid gpgsig algorithm name, got '%s'", sig_alg); string_list_split_in_place(&siglines, sig.buf, "\n", -1); strbuf_add_separated_string_list(&new_data, "\n ", &siglines); strbuf_addch(&new_data, '\n'); diff --git a/gpg-interface.c b/gpg-interface.c index 0896458de5..dc6ea904d0 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -144,6 +144,17 @@ static struct gpg_format *get_format_by_sig(const char *sig) return NULL; } +const char *get_signature_name(const char *buf) +{ + struct gpg_format *format = get_format_by_sig(buf); + return format ? format->name : NULL; +} + +int valid_signature_name(const char *name) +{ + return (get_format_by_name(name) != NULL); +} + void signature_check_clear(struct signature_check *sigc) { FREE_AND_NULL(sigc->payload); diff --git a/gpg-interface.h b/gpg-interface.h index e09f12e8d0..332707facc 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -47,6 +47,16 @@ struct signature_check { void signature_check_clear(struct signature_check *sigc); +/* + * Return the name of the signature (like "openpgp", "x509" or "ssh"). + */ +const char *get_signature_name(const char *buf); + +/* + * Is the signature name valid (like "openpgp", "x509" or "ssh"). + */ +int valid_signature_name(const char *name); + /* * Look at a GPG signed tag object. If such a signature exists, store it in * signature and the signed content in payload. Return 1 if a signature was diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index dda9e7c3e7..2e2c83d153 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -326,7 +326,7 @@ test_expect_success GPG 'signed-commits=abort' ' test_expect_success GPG 'signed-commits=verbatim' ' git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && - grep "^gpgsig sha" output && + grep "^gpgsig openpgp" output && grep "encoding ISO-8859-1" output && ( cd new && @@ -340,7 +340,7 @@ test_expect_success GPG 'signed-commits=verbatim' ' test_expect_success GPG 'signed-commits=warn-verbatim' ' git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && - grep "^gpgsig sha" output && + grep "^gpgsig openpgp" output && grep "encoding ISO-8859-1" output && test -s err && ( @@ -381,6 +381,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' ' +test_expect_success GPGSM 'setup x509 signed commit' ' + + git checkout -b x509-signing main && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + echo "x509 content" >file_for_x509 && + git add file_for_x509 && + git commit -S -m "X.509 signed commit" && + X509_COMMIT=$(git rev-parse --verify HEAD) && + git checkout main + +' + +test_expect_success GPGSM 'x509 signature identified' ' + + git fast-export --signed-commits=verbatim --reencode=no x509-signing >output 2>err && + grep "^gpgsig x509" output && + test ! -s err && + ( + cd new && + git fast-import && + STRIPPED=$(git rev-parse --verify refs/heads/x509-signing) && + test $X509_COMMIT = $STRIPPED + ) <output && + test_might_fail git update-ref -d refs/heads/x509-signing + +' + +test_expect_success GPGSSH 'setup ssh signed commit' ' + + git checkout -b ssh-signing main && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + echo "ssh content" >file_for_ssh && + git add file_for_ssh && + git commit -S -m "SSH signed commit" && + SSH_COMMIT=$(git rev-parse --verify HEAD) && + git checkout main + +' + +test_expect_success GPGSSH 'ssh signature identified' ' + + git fast-export --signed-commits=verbatim --reencode=no ssh-signing >output 2>err && + grep "^gpgsig ssh" output && + test ! -s err && + ( + cd new && + git fast-import && + STRIPPED=$(git rev-parse --verify refs/heads/ssh-signing) && + test "$SSH_COMMIT" = "$STRIPPED" + ) <output && + test_might_fail git update-ref -d refs/heads/ssh-signing + +' + test_expect_success 'setup submodule' ' test_config_global protocol.file.allow always && -- 2.49.0.392.g2fa1c74b07 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 20:39 [PATCH] fast-(import|export): improve on the signature algorithm name Christian Couder @ 2025-04-24 21:19 ` Junio C Hamano 2025-04-24 21:59 ` Elijah Newren 2025-05-26 10:34 ` Christian Couder 2025-04-24 21:41 ` Elijah Newren ` (3 subsequent siblings) 4 siblings, 2 replies; 65+ messages in thread From: Junio C Hamano @ 2025-04-24 21:19 UTC (permalink / raw) To: Christian Couder Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for > signed-commits, 2025-03-10), added support for signed commits. > > However, when processing signatures `git fast-export` outputs "gpgsig > sha1" not just when it encounters an OpenPGP SHA-1 signature, but also > when it encounters an SSH or X.509 signature. This is not very > informative to say the least, and this might prevent tools that process > the output from easily and properly handling signatures. > > Let's improve on that by reusing the existing code from > "gpg-interface.{c,h}" to detect the signature algorithm, and then put > the signature algorithm name (like "openpgp", "x509" or "ssh") instead > of "sha1" in the output. If we can't detect the signature algorithm we > will use "unknown". It might be a signature added by an external tool > and we should likely keep it. > > Similarly on the `git fast-import` side, let's use the existing code > from "gpg-interface.{c,h}" to check if a signature algorithm name is > valid. In case of an "unknown" signature algorithm name, we will warn > but still keep it. Future work might implement several options to let > users deal with it in different ways, and might implement checking > known signatures too. > > Signed-off-by: Christian Couder <chriscool@tuxfamily.org> > --- > > This is a follow up from cc/signed-fast-export-import that was merged > by 01d17c0530 (Merge branch 'cc/signed-fast-export-import', 2025-03-29) > and introduced the support for signed commits. > > The format that this series implemented was lacking a bit, so the goal > with this patch is to improve it and handle signed commits a bit more > consistently in the code base. It also shows in the tests and in our > documentation that SSH and X.509 signatures are supported. Thanks. It is a bit surprising and slightly sad that nobody bothered to report/complain about the brokenness until the original author follows up one month later X-<. Nobody but the original author is using this feature? I would have expected that use of signed commits were of high demand and many more people were actively interested in the topic. > '--signed-commits', you may set the environment variable > 'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default > from 'abort' to 'warn-strip'. > ++ > +When exported, signature starts with "gpgsig <alg>" where <alg> is the > +signature algorithm name as identified by Git (e.g. "openpgp", "x509", > +"ssh", or "sha256" for SHA-256 OpenPGP signatures), or "unknown" for > +signatures that can't be identified. Nice to see these enumerated. As we are not opening the choices of "algorithms" up to end-users by allowing custom signature routines to be plugged in, configured, or hooked into the system, it may make sense to make it clear that we will keep a canonical and exhausitve list here, by saying "one of these:" followed by a bulleted list, instead of a parenthesized examples (e.g.), like you did in the other documentation below. Or perhaps refer to the other document from here so that we do not have to keep two lists in sync? > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc > index 7b107f5e8e..50b6d2cc1d 100644 > --- a/Documentation/git-fast-import.adoc > +++ b/Documentation/git-fast-import.adoc > @@ -521,7 +521,20 @@ The optional `gpgsig` command is used to include a PGP/GPG signature > that signs the commit data. > > Here <alg> specifies which hashing algorithm is used for this > -signature, either `sha1` or `sha256`. > +signature. Current valid values are: > + > +* "openpgp" for SHA-1 OpenPGP signatures, > + > +* "sha256" for SHA-256 OpenPGP signatures, > + > +* "x509" for X.509 (GPGSM) signatures, > + > +* "ssh", for SSH signatures, > + > +* "unknown" for signatures that can't be identified (a warning is > + emitted). > + > +Signatures are not yet checked in the current implementation though. Excellent. > diff --git a/builtin/fast-export.c b/builtin/fast-export.c > index 170126d41a..d00f02dc74 100644 > --- a/builtin/fast-export.c > +++ b/builtin/fast-export.c > @@ -29,6 +29,7 @@ > #include "quote.h" > #include "remote.h" > #include "blob.h" > +#include "gpg-interface.h" > > static const char *fast_export_usage[] = { > N_("git fast-export [<rev-list-opts>]"), > @@ -700,9 +701,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, > } > > if (*commit_buffer_cursor == '\n') { > - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) > - signature_alg = "sha1"; > - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) > + if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) { > + const char *name = get_signature_name(signature); > + signature_alg = name ? name : "unknown"; > + } else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) > signature_alg = "sha256"; The original is bad enough but can we do something to these overly long lines? > } > > diff --git a/builtin/fast-import.c b/builtin/fast-import.c > index 63880b595c..59e991a03c 100644 > --- a/builtin/fast-import.c > +++ b/builtin/fast-import.c > @@ -29,6 +29,7 @@ > #include "commit-reach.h" > #include "khash.h" > #include "date.h" > +#include "gpg-interface.h" > > #define PACK_ID_BITS 16 > #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) > @@ -2830,12 +2831,15 @@ static void parse_new_commit(const char *arg) > "encoding %s\n", > encoding); > if (sig_alg) { > - if (!strcmp(sig_alg, "sha1")) > - strbuf_addstr(&new_data, "gpgsig "); > - else if (!strcmp(sig_alg, "sha256")) > + if (!strcmp(sig_alg, "sha256")) > strbuf_addstr(&new_data, "gpgsig-sha256 "); > - else > - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); > + else if (valid_signature_name(sig_alg)) > + strbuf_addstr(&new_data, "gpgsig "); > + else if (!strcmp(sig_alg, "unknown")) { > + warning("Unknown gpgsig algorithm name!"); > + strbuf_addstr(&new_data, "gpgsig "); > + } else > + die("Invalid gpgsig algorithm name, got '%s'", sig_alg); Hmph, we used to have special cases for sha1 and sha256 but now we can handle sha1 with a more generic "valid_signature_name()" logic? And yet we need to still special case sha256? Not that I trust the old code all that much and take deviations from the patterns in the old code as a sign of something not right... The fast-export stream produced by the code with d9cb0e6f (fast-export, fast-import: add support for signed-commits, 2025-03-10) used to identify a signature algorithm "sha1", but this new version of fast-import lost the support for it, and will barf when seeing such an existing fast-export stream? I am not sure what is going on around this code. I am not so worried about the other case, where the stream produced by fast-export contained in this version may or may not be readable by an older version of fast-import. I am puzzled enough, so I'll stop here for now. Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 21:19 ` Junio C Hamano @ 2025-04-24 21:59 ` Elijah Newren 2025-04-24 22:58 ` Junio C Hamano 2025-05-26 10:34 ` Christian Couder 1 sibling, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-04-24 21:59 UTC (permalink / raw) To: Junio C Hamano Cc: Christian Couder, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder On Thu, Apr 24, 2025 at 2:19 PM Junio C Hamano <gitster@pobox.com> wrote: > [...] > > > > This is a follow up from cc/signed-fast-export-import that was merged > > by 01d17c0530 (Merge branch 'cc/signed-fast-export-import', 2025-03-29) > > and introduced the support for signed commits. > > > > The format that this series implemented was lacking a bit, so the goal > > with this patch is to improve it and handle signed commits a bit more > > consistently in the code base. It also shows in the tests and in our > > documentation that SSH and X.509 signatures are supported. > > Thanks. > > It is a bit surprising and slightly sad that nobody bothered to > report/complain about the brokenness until the original author > follows up one month later X-<. Nobody but the original author is > using this feature? I would have expected that use of signed > commits were of high demand and many more people were actively > interested in the topic. Signed commits may be high demand, but we do have the intersection of signed commits, usage of fast-export/fast-import, and using a development version of Git. I suspect that intersection is somewhat small; my guess is that signed commits tends to be associated with large enterprisy things, development versions of git tend to be the opposite, and fast-export/fast-import are by design infrequently used tools for any given repo. (Also, not sure if this helps, but the original author was Luke, not Christian; Christian was the one who came by three years later to jump in and polish up the patches.) [...] > > @@ -2830,12 +2831,15 @@ static void parse_new_commit(const char *arg) > > "encoding %s\n", > > encoding); > > if (sig_alg) { > > - if (!strcmp(sig_alg, "sha1")) > > - strbuf_addstr(&new_data, "gpgsig "); > > - else if (!strcmp(sig_alg, "sha256")) > > + if (!strcmp(sig_alg, "sha256")) > > strbuf_addstr(&new_data, "gpgsig-sha256 "); > > - else > > - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); > > + else if (valid_signature_name(sig_alg)) > > + strbuf_addstr(&new_data, "gpgsig "); > > + else if (!strcmp(sig_alg, "unknown")) { > > + warning("Unknown gpgsig algorithm name!"); > > + strbuf_addstr(&new_data, "gpgsig "); > > + } else > > + die("Invalid gpgsig algorithm name, got '%s'", sig_alg); > > Hmph, we used to have special cases for sha1 and sha256 but now we > can handle sha1 with a more generic "valid_signature_name()" logic? > And yet we need to still special case sha256? Not that I trust the > old code all that much and take deviations from the patterns in the > old code as a sign of something not right... > > The fast-export stream produced by the code with d9cb0e6f > (fast-export, fast-import: add support for signed-commits, > 2025-03-10) used to identify a signature algorithm "sha1", but this > new version of fast-import lost the support for it, and will barf > when seeing such an existing fast-export stream? I am not sure what > is going on around this code. > > I am not so worried about the other case, where the stream produced > by fast-export contained in this version may or may not be readable > by an older version of fast-import. I certainly can't answer anything here as I know little about signatures, but your comment brought up a different question for me: Given that d9cb0e6ff8b3 (fast-export, fast-import: add support for signed-commits, 2025-03-10) isn't part of any release (not even a release candidate), do we need to have backward compatibility with that version? ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 21:59 ` Elijah Newren @ 2025-04-24 22:58 ` Junio C Hamano 2025-05-26 10:35 ` Christian Couder 0 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-04-24 22:58 UTC (permalink / raw) To: Elijah Newren Cc: Christian Couder, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder Elijah Newren <newren@gmail.com> writes: >> The fast-export stream produced by the code with d9cb0e6f >> (fast-export, fast-import: add support for signed-commits, >> 2025-03-10) used to identify a signature algorithm "sha1", but this >> new version of fast-import lost the support for it, and will barf >> when seeing such an existing fast-export stream? I am not sure what >> is going on around this code. >> >> I am not so worried about the other case, where the stream produced >> by fast-export contained in this version may or may not be readable >> by an older version of fast-import. > > I certainly can't answer anything here as I know little about > signatures, but your comment brought up a different question for me: > Given that d9cb0e6ff8b3 (fast-export, fast-import: add support for > signed-commits, 2025-03-10) isn't part of any release (not even a > release candidate), do we need to have backward compatibility with > that version? I think we will lose all the credibility if we said "that's not in an official release, so we are free to break early adopters", once something is in 'master'. As some corp environment are know to run 'next' and indeed we do encourage more folks to do so so that we can catch breakages before they escape to 'master', I actually am equally worried about things in 'next'. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 22:58 ` Junio C Hamano @ 2025-05-26 10:35 ` Christian Couder 2025-05-27 15:18 ` Junio C Hamano 0 siblings, 1 reply; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:35 UTC (permalink / raw) To: Junio C Hamano Cc: Elijah Newren, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder On Fri, Apr 25, 2025 at 12:58 AM Junio C Hamano <gitster@pobox.com> wrote: > > Elijah Newren <newren@gmail.com> writes: > > >> The fast-export stream produced by the code with d9cb0e6f > >> (fast-export, fast-import: add support for signed-commits, > >> 2025-03-10) used to identify a signature algorithm "sha1", but this > >> new version of fast-import lost the support for it, and will barf > >> when seeing such an existing fast-export stream? I am not sure what > >> is going on around this code. > >> > >> I am not so worried about the other case, where the stream produced > >> by fast-export contained in this version may or may not be readable > >> by an older version of fast-import. > > > > I certainly can't answer anything here as I know little about > > signatures, but your comment brought up a different question for me: > > Given that d9cb0e6ff8b3 (fast-export, fast-import: add support for > > signed-commits, 2025-03-10) isn't part of any release (not even a > > release candidate), do we need to have backward compatibility with > > that version? > > I think we will lose all the credibility if we said "that's not in > an official release, so we are free to break early adopters", once > something is in 'master'. I agree that we should have at least said in big letters that the improved support for signed commits in fast-export/import is very experimental and very likely to change in the future. We could still do so. This could give us a bit of time and flexibility until we agree on and implement something better and backward compatible. (Hopefully the v2 will help us move forward.) > As some corp environment are know to run > 'next' and indeed we do encourage more folks to do so so that we can > catch breakages before they escape to 'master', I actually am equally > worried about things in 'next'. Yeah, right. On the other hand I think it's fair for us to experiment in some areas, at least when we document that we are experimenting, which means that running 'master' or 'next' and blindly using any feature we have just added is not the right thing to do if people don't want to be exposed to not just bugs but also some possible backward incompatibilities in some areas. So yeah, we should definitely have said that we are experimenting. Let me know if you want me to prepare a patch to add such wordings on top of d9cb0e6f (fast-export, fast-import: add support for signed-commits, 2025-03-10). Otherwise we will have to be backward compatible, which is not ideal, but might not be a very big issue either. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-26 10:35 ` Christian Couder @ 2025-05-27 15:18 ` Junio C Hamano 2025-05-28 17:29 ` Junio C Hamano 0 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-05-27 15:18 UTC (permalink / raw) To: Christian Couder Cc: Elijah Newren, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > I agree that we should have at least said in big letters that the > improved support for signed commits in fast-export/import is very > experimental and very likely to change in the future. > > We could still do so. This could give us a bit of time and flexibility > until we agree on and implement something better and backward > compatible. (Hopefully the v2 will help us move forward.) OK, as the next release is approaching, perhaps we do a bit of documentation update to address that "we are experimenting" and nothing else, and leave the v2 updates for the next cycle? Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-27 15:18 ` Junio C Hamano @ 2025-05-28 17:29 ` Junio C Hamano 2025-05-28 20:06 ` Elijah Newren 2025-06-02 15:56 ` Christian Couder 0 siblings, 2 replies; 65+ messages in thread From: Junio C Hamano @ 2025-05-28 17:29 UTC (permalink / raw) To: Christian Couder, Luke Shumaker Cc: Elijah Newren, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder Junio C Hamano <gitster@pobox.com> writes: > Christian Couder <christian.couder@gmail.com> writes: > >> I agree that we should have at least said in big letters that the >> improved support for signed commits in fast-export/import is very >> experimental and very likely to change in the future. >> >> We could still do so. This could give us a bit of time and flexibility >> until we agree on and implement something better and backward >> compatible. (Hopefully the v2 will help us move forward.) > > OK, as the next release is approaching, perhaps we do a bit of > documentation update to address that "we are experimenting" and > nothing else, and leave the v2 updates for the next cycle? ---- >8 ---- Subject: [PATCH] fast-export: --signed-commits is experimental As the design of signature handling is still being discussed, it is likely that the data stream produced by the code in Git 2.50 would have to be changed in such a way that is not backward compatible. Mark the feature as experimental and discourge its use for now. Also flip the default on the generation side to "strip"; users of existing versions would not have passed --signed-commits=strip and will be broken by this change if the default is made to abort, and will be encouraged by the error message to produce data stream with future breakage guarantees by passing --signed-commits option. As we tone down the default behaviour, we no longer need the FAST_EXPORT_SIGNED_COMMITS_NOABORT environment variable, which was not discoverable enough. Signed-off-by: Junio C Hamano <gitster@pobox.com> --- Documentation/RelNotes/2.50.0.adoc | 4 +++- Documentation/git-fast-export.adoc | 12 +++++------- Documentation/git-fast-import.adoc | 3 +++ builtin/fast-export.c | 7 +------ t/t9350-fast-export.sh | 20 ++++---------------- 5 files changed, 16 insertions(+), 30 deletions(-) diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc index c6c34d1a1d..9a1cdf0dc0 100644 --- a/Documentation/RelNotes/2.50.0.adoc +++ b/Documentation/RelNotes/2.50.0.adoc @@ -100,7 +100,9 @@ Performance, Internal Implementation, Development Support etc. * "git fsck" becomes more careful when checking the refs. * "git fast-export | git fast-import" learns to deal with commit and - tag objects with embedded signatures a bit better. + tag objects with embedded signatures a bit better. This is highly + experimental and the format of the data stream may change in the + future without compatibility guarantees. * The code paths to check whether a refname X is available (by seeing if another ref X/Y exists, etc.) have been optimized. diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 413a527496..43bbb4f63c 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -46,14 +46,12 @@ resulting tag will have an invalid signature. --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort):: Specify how to handle signed commits. Behaves exactly as - '--signed-tags', but for commits. Default is 'abort'. + '--signed-tags', but for commits. Default is 'strip', which + is the same as how earlier versions of this command without + this option behaved. + -Earlier versions this command that did not have '--signed-commits' -behaved as if '--signed-commits=strip'. As an escape hatch for users -of tools that call 'git fast-export' but do not yet support -'--signed-commits', you may set the environment variable -'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default -from 'abort' to 'warn-strip'. +NOTE: This is highly experimental and the format of the data stream may +change in the future without compatibility guarantees. --tag-of-filtered-object=(abort|drop|rewrite):: Specify how to handle tags whose tagged object is filtered out. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 7b107f5e8e..250d866652 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -523,6 +523,9 @@ that signs the commit data. Here <alg> specifies which hashing algorithm is used for this signature, either `sha1` or `sha256`. +NOTE: This is highly experimental and the format of the data stream may +change in the future without compatibility guarantees. + `encoding` ^^^^^^^^^^ The optional `encoding` command indicates the encoding of the commit diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 37c01d6c6f..fcf6b00d5f 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -39,7 +39,7 @@ enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN static int progress; static enum sign_mode signed_tag_mode = SIGN_ABORT; -static enum sign_mode signed_commit_mode = SIGN_ABORT; +static enum sign_mode signed_commit_mode = SIGN_STRIP; static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; static int fake_missing_tagger; @@ -1269,7 +1269,6 @@ int cmd_fast_export(int argc, const char *prefix, struct repository *repo UNUSED) { - const char *env_signed_commits_noabort; struct rev_info revs; struct commit *commit; char *export_filename = NULL, @@ -1327,10 +1326,6 @@ int cmd_fast_export(int argc, if (argc == 1) usage_with_options (fast_export_usage, options); - env_signed_commits_noabort = getenv("FAST_EXPORT_SIGNED_COMMITS_NOABORT"); - if (env_signed_commits_noabort && *env_signed_commits_noabort) - signed_commit_mode = SIGN_WARN_STRIP; - /* we handle encodings */ git_config(git_default_config, NULL); diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index dda9e7c3e7..76619765fc 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -299,22 +299,10 @@ test_expect_success GPG 'set up signed commit' ' ' -test_expect_success GPG 'signed-commits default' ' - - sane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT && - test_must_fail git fast-export --reencode=no commit-signing && - - FAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err && - ! grep ^gpgsig output && - grep "^encoding ISO-8859-1" output && - test -s err && - sed "s/commit-signing/commit-strip-signing/" output | ( - cd new && - git fast-import && - STRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) && - test $COMMIT_SIGNING != $STRIPPED - ) - +test_expect_success GPG 'signed-commits default is same as strip' ' + git fast-export --reencode=no commit-signing >out1 2>err && + git fast-export --reencode=no --signed-commits=strip commit-signing >out2 && + test_cmp out1 out2 ' test_expect_success GPG 'signed-commits=abort' ' -- 2.50.0-rc0-134-gb29a910c2a ^ permalink raw reply related [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-28 17:29 ` Junio C Hamano @ 2025-05-28 20:06 ` Elijah Newren 2025-05-28 21:59 ` Junio C Hamano 2025-06-02 15:56 ` Christian Couder 1 sibling, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-05-28 20:06 UTC (permalink / raw) To: Junio C Hamano Cc: Christian Couder, Luke Shumaker, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder On Wed, May 28, 2025 at 10:29 AM Junio C Hamano <gitster@pobox.com> wrote: > > Junio C Hamano <gitster@pobox.com> writes: > > > Christian Couder <christian.couder@gmail.com> writes: > > > >> I agree that we should have at least said in big letters that the > >> improved support for signed commits in fast-export/import is very > >> experimental and very likely to change in the future. > >> > >> We could still do so. This could give us a bit of time and flexibility > >> until we agree on and implement something better and backward > >> compatible. (Hopefully the v2 will help us move forward.) > > > > OK, as the next release is approaching, perhaps we do a bit of > > documentation update to address that "we are experimenting" and > > nothing else, and leave the v2 updates for the next cycle? > > ---- >8 ---- > Subject: [PATCH] fast-export: --signed-commits is experimental > > As the design of signature handling is still being discussed, it is > likely that the data stream produced by the code in Git 2.50 would > have to be changed in such a way that is not backward compatible. > > Mark the feature as experimental and discourge its use for now. I think this is a very good thing to do. minor nit: discourge -> discourage > Also flip the default on the generation side to "strip"; users of > existing versions would not have passed --signed-commits=strip and > will be broken by this change if the default is made to abort, and > will be encouraged by the error message to produce data stream with > future breakage guarantees by passing --signed-commits option. So...git-filter-repo runs fast-export and has limited flexibility about which options it passes to fast-export under the hood, so this change would save me from the patch I was planning to add to filter-repo. So that's evidence in support of your statement, but the "will be broken" statement appears to me to be incongruent with past deprecations and changes of default that we have gone through. Often when we have deprecated or changed an option our process was to first produce an error and update documentation and wait a while, then go and change the default after a sufficiently long time. Here, we had kind of stopped at just producing the error with no plans to take another step. If that was the route we took in the past, what makes this considered a breakage and not the other changes we made? (Just curious, I'm not against this change.) > As we tone down the default behaviour, we no longer need the > FAST_EXPORT_SIGNED_COMMITS_NOABORT environment variable, which was > not discoverable enough. Makes sense. > > Signed-off-by: Junio C Hamano <gitster@pobox.com> > --- > Documentation/RelNotes/2.50.0.adoc | 4 +++- > Documentation/git-fast-export.adoc | 12 +++++------- > Documentation/git-fast-import.adoc | 3 +++ > builtin/fast-export.c | 7 +------ > t/t9350-fast-export.sh | 20 ++++---------------- > 5 files changed, 16 insertions(+), 30 deletions(-) > > diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc > index c6c34d1a1d..9a1cdf0dc0 100644 > --- a/Documentation/RelNotes/2.50.0.adoc > +++ b/Documentation/RelNotes/2.50.0.adoc > @@ -100,7 +100,9 @@ Performance, Internal Implementation, Development Support etc. > * "git fsck" becomes more careful when checking the refs. > > * "git fast-export | git fast-import" learns to deal with commit and > - tag objects with embedded signatures a bit better. > + tag objects with embedded signatures a bit better. This is highly > + experimental and the format of the data stream may change in the > + future without compatibility guarantees. > > * The code paths to check whether a refname X is available (by seeing > if another ref X/Y exists, etc.) have been optimized. > diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc > index 413a527496..43bbb4f63c 100644 > --- a/Documentation/git-fast-export.adoc > +++ b/Documentation/git-fast-export.adoc > @@ -46,14 +46,12 @@ resulting tag will have an invalid signature. > > --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort):: > Specify how to handle signed commits. Behaves exactly as > - '--signed-tags', but for commits. Default is 'abort'. > + '--signed-tags', but for commits. Default is 'strip', which > + is the same as how earlier versions of this command without > + this option behaved. > + > -Earlier versions this command that did not have '--signed-commits' > -behaved as if '--signed-commits=strip'. As an escape hatch for users > -of tools that call 'git fast-export' but do not yet support > -'--signed-commits', you may set the environment variable > -'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default > -from 'abort' to 'warn-strip'. > +NOTE: This is highly experimental and the format of the data stream may > +change in the future without compatibility guarantees. > > --tag-of-filtered-object=(abort|drop|rewrite):: > Specify how to handle tags whose tagged object is filtered out. > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc > index 7b107f5e8e..250d866652 100644 > --- a/Documentation/git-fast-import.adoc > +++ b/Documentation/git-fast-import.adoc > @@ -523,6 +523,9 @@ that signs the commit data. > Here <alg> specifies which hashing algorithm is used for this > signature, either `sha1` or `sha256`. > > +NOTE: This is highly experimental and the format of the data stream may > +change in the future without compatibility guarantees. > + > `encoding` > ^^^^^^^^^^ > The optional `encoding` command indicates the encoding of the commit > diff --git a/builtin/fast-export.c b/builtin/fast-export.c > index 37c01d6c6f..fcf6b00d5f 100644 > --- a/builtin/fast-export.c > +++ b/builtin/fast-export.c > @@ -39,7 +39,7 @@ enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN > > static int progress; > static enum sign_mode signed_tag_mode = SIGN_ABORT; > -static enum sign_mode signed_commit_mode = SIGN_ABORT; > +static enum sign_mode signed_commit_mode = SIGN_STRIP; > static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; > static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; > static int fake_missing_tagger; > @@ -1269,7 +1269,6 @@ int cmd_fast_export(int argc, > const char *prefix, > struct repository *repo UNUSED) > { > - const char *env_signed_commits_noabort; > struct rev_info revs; > struct commit *commit; > char *export_filename = NULL, > @@ -1327,10 +1326,6 @@ int cmd_fast_export(int argc, > if (argc == 1) > usage_with_options (fast_export_usage, options); > > - env_signed_commits_noabort = getenv("FAST_EXPORT_SIGNED_COMMITS_NOABORT"); > - if (env_signed_commits_noabort && *env_signed_commits_noabort) > - signed_commit_mode = SIGN_WARN_STRIP; > - > /* we handle encodings */ > git_config(git_default_config, NULL); > > diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh > index dda9e7c3e7..76619765fc 100755 > --- a/t/t9350-fast-export.sh > +++ b/t/t9350-fast-export.sh > @@ -299,22 +299,10 @@ test_expect_success GPG 'set up signed commit' ' > > ' > > -test_expect_success GPG 'signed-commits default' ' > - > - sane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT && > - test_must_fail git fast-export --reencode=no commit-signing && > - > - FAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err && > - ! grep ^gpgsig output && > - grep "^encoding ISO-8859-1" output && > - test -s err && > - sed "s/commit-signing/commit-strip-signing/" output | ( > - cd new && > - git fast-import && > - STRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) && > - test $COMMIT_SIGNING != $STRIPPED > - ) > - > +test_expect_success GPG 'signed-commits default is same as strip' ' > + git fast-export --reencode=no commit-signing >out1 2>err && > + git fast-export --reencode=no --signed-commits=strip commit-signing >out2 && > + test_cmp out1 out2 > ' > > test_expect_success GPG 'signed-commits=abort' ' > -- > 2.50.0-rc0-134-gb29a910c2a Looks good to me. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-28 20:06 ` Elijah Newren @ 2025-05-28 21:59 ` Junio C Hamano 2025-05-28 23:15 ` Elijah Newren 0 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-05-28 21:59 UTC (permalink / raw) To: Elijah Newren Cc: Christian Couder, Luke Shumaker, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder Elijah Newren <newren@gmail.com> writes: > Often > when we have deprecated or changed an option our process was to first > produce an error and update documentation and wait a while, then go > and change the default after a sufficiently long time. Here, we had > kind of stopped at just producing the error with no plans to take > another step. If that was the route we took in the past, what makes > this considered a breakage and not the other changes we made? > > (Just curious, I'm not against this change.) What is wrong is the behaviour change in the original, which luckily is not in any released versions (except for 2.50-rc0, which should not count, as I think we should do this toning-down before -rc1). We used to silently ignore and strip commit signatures and that has always been the behaviour the existing users have relied upon; we started requiring these existing users to either explicitly pass --signed-c=strip or set an environmtne variable. A new feature should be opt-in to make the transition smoother, but the topic did not follow that pattern. I view this last-minute band-aid patch that flips the default back to what it used to be as remedying that mistake in the original series. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-28 21:59 ` Junio C Hamano @ 2025-05-28 23:15 ` Elijah Newren 2025-05-29 3:14 ` Junio C Hamano 0 siblings, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-05-28 23:15 UTC (permalink / raw) To: Junio C Hamano Cc: Christian Couder, Luke Shumaker, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder On Wed, May 28, 2025 at 2:59 PM Junio C Hamano <gitster@pobox.com> wrote: > > Elijah Newren <newren@gmail.com> writes: > > > Often > > when we have deprecated or changed an option our process was to first > > produce an error and update documentation and wait a while, then go > > and change the default after a sufficiently long time. Here, we had > > kind of stopped at just producing the error with no plans to take > > another step. If that was the route we took in the past, what makes > > this considered a breakage and not the other changes we made? > > > > (Just curious, I'm not against this change.) > > What is wrong is the behaviour change in the original, which luckily > is not in any released versions (except for 2.50-rc0, which should > not count, as I think we should do this toning-down before -rc1). > > We used to silently ignore and strip commit signatures and that has > always been the behaviour the existing users have relied upon; we > started requiring these existing users to either explicitly pass > --signed-c=strip or set an environmtne variable. A new feature > should be opt-in to make the transition smoother, but the topic did > not follow that pattern. > > I view this last-minute band-aid patch that flips the default back > to what it used to be as remedying that mistake in the original > series. An earlier version of the series did keep the stripping, but you were the one who objected (https://lore.kernel.org/git/xmqqfszbcazc.fsf@gitster.g/): """ Why deliberate inconsistency? I am not sure "historically we did a wrong thing" is a good reason (if we view that silently stripping was a disservice to the users, aborting would be a bugfix). """ and later in the series (https://lore.kernel.org/git/xmqqim44fyjj.fsf@gitster.g/), you added: """ Thanks. The "filter-repo already gets bug reports from the users" is a valuable input when deciding if it is reasonable to sell the behaviour change as a bugfix to our users. """ Personally, I kind of think abort makes more sense as the default -- at some point. So I'm curious if you've just changed your mind completely from before and are against changing the default at all, whether you think there's more steps that should be done before we change the default, or whether this new flag feeling incomplete and development related to it being slow moving means we should wait off until we have a better picture of where it'll end up and leave the defaults alone until we get there. (I'm kind of leaning towards the 3rd of those, but am curious if I'm misaligned with your vision.) ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-28 23:15 ` Elijah Newren @ 2025-05-29 3:14 ` Junio C Hamano 2025-06-02 15:56 ` Christian Couder 0 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-05-29 3:14 UTC (permalink / raw) To: Elijah Newren Cc: Christian Couder, Luke Shumaker, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder Elijah Newren <newren@gmail.com> writes: > Personally, I kind of think abort makes more sense as the default -- > at some point. Oh, of course. > So I'm curious if you've just changed your mind > completely from before and are against changing the default at all, Yes, after seeing that the representation of the signature algorithm and encapsulation format was not as well thought out as I thought it would already be and its design still being discussed, I realized that the new feature was way premature to have in the release. At some point, when things mature and we are reasonably sure we will not have to make incompatible changes in the data stream, we might need to switch, and the best default might turn out to be to refuse to work unless the end-user makes an explicit choice, but as the design of the feature stands now, I have a feeling that it is a bit premature. Certainly not ready for general consumption. Of course, I could have just reverted the merge of the original topic and give it a chance for a fresh restart the next cycle, but a new feature clearly marked "highly experimental" would hopefully set the end-user expectation straight, as long as the default is "do not do anything different from before", which is the safest choice for a feature whose design is still wobbly. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-29 3:14 ` Junio C Hamano @ 2025-06-02 15:56 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-06-02 15:56 UTC (permalink / raw) To: Junio C Hamano Cc: Elijah Newren, Luke Shumaker, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder On Thu, May 29, 2025 at 5:14 AM Junio C Hamano <gitster@pobox.com> wrote: > > Elijah Newren <newren@gmail.com> writes: > > > Personally, I kind of think abort makes more sense as the default -- > > at some point. > > Oh, of course. > > > So I'm curious if you've just changed your mind > > completely from before and are against changing the default at all, > > Yes, after seeing that the representation of the signature algorithm > and encapsulation format was not as well thought out as I thought it > would already be and its design still being discussed, I realized > that the new feature was way premature to have in the release. At > some point, when things mature and we are reasonably sure we will > not have to make incompatible changes in the data stream, we might > need to switch, and the best default might turn out to be to refuse > to work unless the end-user makes an explicit choice, but as the > design of the feature stands now, I have a feeling that it is a bit > premature. Certainly not ready for general consumption. Discouraging the use of the feature and saying it's highly experimental should hint that the default might change in the future, but maybe we should explicitly say so? > Of course, I could have just reverted the merge of the original > topic and give it a chance for a fresh restart the next cycle, but a > new feature clearly marked "highly experimental" would hopefully set > the end-user expectation straight, as long as the default is "do not > do anything different from before", which is the safest choice for a > feature whose design is still wobbly. Yeah, I agree with this approach in general. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-05-28 17:29 ` Junio C Hamano 2025-05-28 20:06 ` Elijah Newren @ 2025-06-02 15:56 ` Christian Couder 2025-06-02 16:20 ` Junio C Hamano 1 sibling, 1 reply; 65+ messages in thread From: Christian Couder @ 2025-06-02 15:56 UTC (permalink / raw) To: Junio C Hamano Cc: Luke Shumaker, Elijah Newren, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder (Sorry for the late answer to this.) On Wed, May 28, 2025 at 7:29 PM Junio C Hamano <gitster@pobox.com> wrote: > > Junio C Hamano <gitster@pobox.com> writes: > > > Christian Couder <christian.couder@gmail.com> writes: > > > >> I agree that we should have at least said in big letters that the > >> improved support for signed commits in fast-export/import is very > >> experimental and very likely to change in the future. > >> > >> We could still do so. This could give us a bit of time and flexibility > >> until we agree on and implement something better and backward > >> compatible. (Hopefully the v2 will help us move forward.) > > > > OK, as the next release is approaching, perhaps we do a bit of > > documentation update to address that "we are experimenting" and > > nothing else, and leave the v2 updates for the next cycle? Thanks for this. I agree that it's the best approach. > ---- >8 ---- > Subject: [PATCH] fast-export: --signed-commits is experimental > > As the design of signature handling is still being discussed, it is > likely that the data stream produced by the code in Git 2.50 would > have to be changed in such a way that is not backward compatible. > > Mark the feature as experimental and discourge its use for now. Yeah, right. > Also flip the default on the generation side to "strip"; users of > existing versions would not have passed --signed-commits=strip and > will be broken by this change if the default is made to abort, and > will be encouraged by the error message to produce data stream with > future breakage guarantees by passing --signed-commits option. > > As we tone down the default behaviour, we no longer need the > FAST_EXPORT_SIGNED_COMMITS_NOABORT environment variable, which was > not discoverable enough. > > Signed-off-by: Junio C Hamano <gitster@pobox.com> > --- > Documentation/RelNotes/2.50.0.adoc | 4 +++- > Documentation/git-fast-export.adoc | 12 +++++------- > Documentation/git-fast-import.adoc | 3 +++ > builtin/fast-export.c | 7 +------ > t/t9350-fast-export.sh | 20 ++++---------------- > 5 files changed, 16 insertions(+), 30 deletions(-) > > diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc > index c6c34d1a1d..9a1cdf0dc0 100644 > --- a/Documentation/RelNotes/2.50.0.adoc > +++ b/Documentation/RelNotes/2.50.0.adoc > @@ -100,7 +100,9 @@ Performance, Internal Implementation, Development Support etc. > * "git fsck" becomes more careful when checking the refs. > > * "git fast-export | git fast-import" learns to deal with commit and > - tag objects with embedded signatures a bit better. > + tag objects with embedded signatures a bit better. This is highly > + experimental and the format of the data stream may change in the > + future without compatibility guarantees. > > * The code paths to check whether a refname X is available (by seeing > if another ref X/Y exists, etc.) have been optimized. > diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc > index 413a527496..43bbb4f63c 100644 > --- a/Documentation/git-fast-export.adoc > +++ b/Documentation/git-fast-export.adoc > @@ -46,14 +46,12 @@ resulting tag will have an invalid signature. > > --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort):: > Specify how to handle signed commits. Behaves exactly as > - '--signed-tags', but for commits. Default is 'abort'. > + '--signed-tags', but for commits. Default is 'strip', which > + is the same as how earlier versions of this command without > + this option behaved. > + > -Earlier versions this command that did not have '--signed-commits' > -behaved as if '--signed-commits=strip'. As an escape hatch for users > -of tools that call 'git fast-export' but do not yet support > -'--signed-commits', you may set the environment variable > -'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default > -from 'abort' to 'warn-strip'. > +NOTE: This is highly experimental and the format of the data stream may > +change in the future without compatibility guarantees. I wonder if it should say that the default is likely to change too? > --tag-of-filtered-object=(abort|drop|rewrite):: > Specify how to handle tags whose tagged object is filtered out. > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc > index 7b107f5e8e..250d866652 100644 > --- a/Documentation/git-fast-import.adoc > +++ b/Documentation/git-fast-import.adoc > @@ -523,6 +523,9 @@ that signs the commit data. > Here <alg> specifies which hashing algorithm is used for this > signature, either `sha1` or `sha256`. > > +NOTE: This is highly experimental and the format of the data stream may > +change in the future without compatibility guarantees. > + > `encoding` > ^^^^^^^^^^ > The optional `encoding` command indicates the encoding of the commit > diff --git a/builtin/fast-export.c b/builtin/fast-export.c > index 37c01d6c6f..fcf6b00d5f 100644 > --- a/builtin/fast-export.c > +++ b/builtin/fast-export.c > @@ -39,7 +39,7 @@ enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN > > static int progress; > static enum sign_mode signed_tag_mode = SIGN_ABORT; > -static enum sign_mode signed_commit_mode = SIGN_ABORT; > +static enum sign_mode signed_commit_mode = SIGN_STRIP; > static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; > static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; > static int fake_missing_tagger; > @@ -1269,7 +1269,6 @@ int cmd_fast_export(int argc, > const char *prefix, > struct repository *repo UNUSED) > { > - const char *env_signed_commits_noabort; > struct rev_info revs; > struct commit *commit; > char *export_filename = NULL, > @@ -1327,10 +1326,6 @@ int cmd_fast_export(int argc, > if (argc == 1) > usage_with_options (fast_export_usage, options); > > - env_signed_commits_noabort = getenv("FAST_EXPORT_SIGNED_COMMITS_NOABORT"); > - if (env_signed_commits_noabort && *env_signed_commits_noabort) > - signed_commit_mode = SIGN_WARN_STRIP; > - > /* we handle encodings */ > git_config(git_default_config, NULL); > > diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh > index dda9e7c3e7..76619765fc 100755 > --- a/t/t9350-fast-export.sh > +++ b/t/t9350-fast-export.sh > @@ -299,22 +299,10 @@ test_expect_success GPG 'set up signed commit' ' > > ' > > -test_expect_success GPG 'signed-commits default' ' > - > - sane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT && > - test_must_fail git fast-export --reencode=no commit-signing && > - > - FAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err && > - ! grep ^gpgsig output && > - grep "^encoding ISO-8859-1" output && > - test -s err && > - sed "s/commit-signing/commit-strip-signing/" output | ( > - cd new && > - git fast-import && > - STRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) && > - test $COMMIT_SIGNING != $STRIPPED > - ) > - > +test_expect_success GPG 'signed-commits default is same as strip' ' Here also maybe we should say that the default could change in case advanced users look at test cases to get hints at what is cast in stone? > + git fast-export --reencode=no commit-signing >out1 2>err && > + git fast-export --reencode=no --signed-commits=strip commit-signing >out2 && > + test_cmp out1 out2 > ' Otherwise the patch looks good to me. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-06-02 15:56 ` Christian Couder @ 2025-06-02 16:20 ` Junio C Hamano 0 siblings, 0 replies; 65+ messages in thread From: Junio C Hamano @ 2025-06-02 16:20 UTC (permalink / raw) To: Christian Couder Cc: Luke Shumaker, Elijah Newren, git, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: >> -Earlier versions this command that did not have '--signed-commits' >> -behaved as if '--signed-commits=strip'. As an escape hatch for users >> -of tools that call 'git fast-export' but do not yet support >> -'--signed-commits', you may set the environment variable >> -'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default >> -from 'abort' to 'warn-strip'. >> +NOTE: This is highly experimental and the format of the data stream may >> +change in the future without compatibility guarantees. > > I wonder if it should say that the default is likely to change too? It is to be decided in the future and we have no plan to do so before the feature loses "experimental" label, no? Those who are opting into a highly experimental feature would know that already. >> +test_expect_success GPG 'signed-commits default is same as strip' ' > > Here also maybe we should say that the default could change in case > advanced users look at test cases to get hints at what is cast in > stone? The same answer applies. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 21:19 ` Junio C Hamano 2025-04-24 21:59 ` Elijah Newren @ 2025-05-26 10:34 ` Christian Couder 1 sibling, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:34 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin, Christian Couder On Thu, Apr 24, 2025 at 11:19 PM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > diff --git a/builtin/fast-import.c b/builtin/fast-import.c > > index 63880b595c..59e991a03c 100644 > > --- a/builtin/fast-import.c > > +++ b/builtin/fast-import.c > > @@ -29,6 +29,7 @@ > > #include "commit-reach.h" > > #include "khash.h" > > #include "date.h" > > +#include "gpg-interface.h" > > > > #define PACK_ID_BITS 16 > > #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) > > @@ -2830,12 +2831,15 @@ static void parse_new_commit(const char *arg) > > "encoding %s\n", > > encoding); > > if (sig_alg) { > > - if (!strcmp(sig_alg, "sha1")) > > - strbuf_addstr(&new_data, "gpgsig "); > > - else if (!strcmp(sig_alg, "sha256")) > > + if (!strcmp(sig_alg, "sha256")) > > strbuf_addstr(&new_data, "gpgsig-sha256 "); > > - else > > - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); > > + else if (valid_signature_name(sig_alg)) > > + strbuf_addstr(&new_data, "gpgsig "); > > + else if (!strcmp(sig_alg, "unknown")) { > > + warning("Unknown gpgsig algorithm name!"); > > + strbuf_addstr(&new_data, "gpgsig "); > > + } else > > + die("Invalid gpgsig algorithm name, got '%s'", sig_alg); > > Hmph, we used to have special cases for sha1 and sha256 but now we > can handle sha1 with a more generic "valid_signature_name()" logic? > And yet we need to still special case sha256? Not that I trust the > old code all that much and take deviations from the patterns in the > old code as a sign of something not right... > > The fast-export stream produced by the code with d9cb0e6f > (fast-export, fast-import: add support for signed-commits, > 2025-03-10) used to identify a signature algorithm "sha1", but this > new version of fast-import lost the support for it, and will barf > when seeing such an existing fast-export stream? I am not sure what > is going on around this code. > > I am not so worried about the other case, where the stream produced > by fast-export contained in this version may or may not be readable > by an older version of fast-import. > > I am puzzled enough, so I'll stop here for now. I am also not sure about the best way to approach this, so I tried a new approach in v2. Anyway thanks for your comments! I will address them if we decide that the approach in this patch is still worth pursuing. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 20:39 [PATCH] fast-(import|export): improve on the signature algorithm name Christian Couder 2025-04-24 21:19 ` Junio C Hamano @ 2025-04-24 21:41 ` Elijah Newren 2025-05-26 10:34 ` Christian Couder 2025-04-24 22:05 ` brian m. carlson ` (2 subsequent siblings) 4 siblings, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-04-24 21:41 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder On Thu, Apr 24, 2025 at 1:39 PM Christian Couder <christian.couder@gmail.com> wrote: > > A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for > signed-commits, 2025-03-10), added support for signed commits. > > However, when processing signatures `git fast-export` outputs "gpgsig > sha1" not just when it encounters an OpenPGP SHA-1 signature, but also > when it encounters an SSH or X.509 signature. This is not very > informative to say the least, and this might prevent tools that process > the output from easily and properly handling signatures. > > Let's improve on that by reusing the existing code from > "gpg-interface.{c,h}" to detect the signature algorithm, and then put > the signature algorithm name (like "openpgp", "x509" or "ssh") instead > of "sha1" in the output. If we can't detect the signature algorithm we > will use "unknown". It might be a signature added by an external tool > and we should likely keep it. > > Similarly on the `git fast-import` side, let's use the existing code > from "gpg-interface.{c,h}" to check if a signature algorithm name is > valid. In case of an "unknown" signature algorithm name, we will warn > but still keep it. Future work might implement several options to let > users deal with it in different ways, and might implement checking > known signatures too. The last sentence is somewhat ambiguous about whether it is only about the "unknown" case or whether the second half of the sentence was switching tracks to discuss something else about the known cases. Do you perhaps mean something like "Future work might implement several options to let users deal with an "unknown" signature algorithm, and when we have a valid signature algorithm, we may be able to not only verify the signature algorithm name but start verifying the signature itself to ensure it is valid as well." ? > Signed-off-by: Christian Couder <chriscool@tuxfamily.org> > --- > > This is a follow up from cc/signed-fast-export-import that was merged > by 01d17c0530 (Merge branch 'cc/signed-fast-export-import', 2025-03-29) > and introduced the support for signed commits. > > The format that this series implemented was lacking a bit, so the goal > with this patch is to improve it and handle signed commits a bit more > consistently in the code base. It also shows in the tests and in our > documentation that SSH and X.509 signatures are supported. > > Documentation/git-fast-export.adoc | 5 +++ > Documentation/git-fast-import.adoc | 15 +++++++- > builtin/fast-export.c | 8 ++-- > builtin/fast-import.c | 14 ++++--- > gpg-interface.c | 11 ++++++ > gpg-interface.h | 10 +++++ > t/t9350-fast-export.sh | 60 +++++++++++++++++++++++++++++- > 7 files changed, 112 insertions(+), 11 deletions(-) > > diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc > index 413a527496..d03aeca781 100644 > --- a/Documentation/git-fast-export.adoc > +++ b/Documentation/git-fast-export.adoc > @@ -54,6 +54,11 @@ of tools that call 'git fast-export' but do not yet support > '--signed-commits', you may set the environment variable > 'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default > from 'abort' to 'warn-strip'. > ++ > +When exported, signature starts with "gpgsig <alg>" where <alg> is the > +signature algorithm name as identified by Git (e.g. "openpgp", "x509", > +"ssh", or "sha256" for SHA-256 OpenPGP signatures), or "unknown" for > +signatures that can't be identified. > > --tag-of-filtered-object=(abort|drop|rewrite):: > Specify how to handle tags whose tagged object is filtered out. > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc > index 7b107f5e8e..50b6d2cc1d 100644 > --- a/Documentation/git-fast-import.adoc > +++ b/Documentation/git-fast-import.adoc > @@ -521,7 +521,20 @@ The optional `gpgsig` command is used to include a PGP/GPG signature > that signs the commit data. > > Here <alg> specifies which hashing algorithm is used for this > -signature, either `sha1` or `sha256`. > +signature. Current valid values are: > + > +* "openpgp" for SHA-1 OpenPGP signatures, > + > +* "sha256" for SHA-256 OpenPGP signatures, > + > +* "x509" for X.509 (GPGSM) signatures, > + > +* "ssh", for SSH signatures, > + > +* "unknown" for signatures that can't be identified (a warning is > + emitted). > + > +Signatures are not yet checked in the current implementation though. Thanks for calling this out. > `encoding` > ^^^^^^^^^^ > diff --git a/builtin/fast-export.c b/builtin/fast-export.c > index 170126d41a..d00f02dc74 100644 > --- a/builtin/fast-export.c > +++ b/builtin/fast-export.c > @@ -29,6 +29,7 @@ > #include "quote.h" > #include "remote.h" > #include "blob.h" > +#include "gpg-interface.h" > > static const char *fast_export_usage[] = { > N_("git fast-export [<rev-list-opts>]"), > @@ -700,9 +701,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, > } > > if (*commit_buffer_cursor == '\n') { > - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) > - signature_alg = "sha1"; > - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) > + if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) { > + const char *name = get_signature_name(signature); > + signature_alg = name ? name : "unknown"; > + } else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) > signature_alg = "sha256"; > } > > diff --git a/builtin/fast-import.c b/builtin/fast-import.c > index 63880b595c..59e991a03c 100644 > --- a/builtin/fast-import.c > +++ b/builtin/fast-import.c > @@ -29,6 +29,7 @@ > #include "commit-reach.h" > #include "khash.h" > #include "date.h" > +#include "gpg-interface.h" > > #define PACK_ID_BITS 16 > #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) > @@ -2830,12 +2831,15 @@ static void parse_new_commit(const char *arg) > "encoding %s\n", > encoding); > if (sig_alg) { > - if (!strcmp(sig_alg, "sha1")) > - strbuf_addstr(&new_data, "gpgsig "); > - else if (!strcmp(sig_alg, "sha256")) > + if (!strcmp(sig_alg, "sha256")) > strbuf_addstr(&new_data, "gpgsig-sha256 "); > - else > - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); > + else if (valid_signature_name(sig_alg)) > + strbuf_addstr(&new_data, "gpgsig "); > + else if (!strcmp(sig_alg, "unknown")) { > + warning("Unknown gpgsig algorithm name!"); > + strbuf_addstr(&new_data, "gpgsig "); > + } else > + die("Invalid gpgsig algorithm name, got '%s'", sig_alg); > string_list_split_in_place(&siglines, sig.buf, "\n", -1); > strbuf_add_separated_string_list(&new_data, "\n ", &siglines); > strbuf_addch(&new_data, '\n'); I'm not very familiar with gpg and other signatures, and was stuck trying to parse this logic when a review from Junio came in, and I decided to read it since he often "thinks out loud" to see if that'd explain it better. Sadly, didn't help... ;-) But I'll watch for any follow-up response you add over there. > diff --git a/gpg-interface.c b/gpg-interface.c > index 0896458de5..dc6ea904d0 100644 > --- a/gpg-interface.c > +++ b/gpg-interface.c > @@ -144,6 +144,17 @@ static struct gpg_format *get_format_by_sig(const char *sig) > return NULL; > } > > +const char *get_signature_name(const char *buf) > +{ > + struct gpg_format *format = get_format_by_sig(buf); > + return format ? format->name : NULL; > +} > + > +int valid_signature_name(const char *name) > +{ > + return (get_format_by_name(name) != NULL); > +} > + > void signature_check_clear(struct signature_check *sigc) > { > FREE_AND_NULL(sigc->payload); > diff --git a/gpg-interface.h b/gpg-interface.h > index e09f12e8d0..332707facc 100644 > --- a/gpg-interface.h > +++ b/gpg-interface.h > @@ -47,6 +47,16 @@ struct signature_check { > > void signature_check_clear(struct signature_check *sigc); > > +/* > + * Return the name of the signature (like "openpgp", "x509" or "ssh"). > + */ > +const char *get_signature_name(const char *buf); > + > +/* > + * Is the signature name valid (like "openpgp", "x509" or "ssh"). > + */ > +int valid_signature_name(const char *name); > + > /* > * Look at a GPG signed tag object. If such a signature exists, store it in > * signature and the signed content in payload. Return 1 if a signature was > diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh > index dda9e7c3e7..2e2c83d153 100755 > --- a/t/t9350-fast-export.sh > +++ b/t/t9350-fast-export.sh > @@ -326,7 +326,7 @@ test_expect_success GPG 'signed-commits=abort' ' > test_expect_success GPG 'signed-commits=verbatim' ' > > git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && > - grep "^gpgsig sha" output && > + grep "^gpgsig openpgp" output && > grep "encoding ISO-8859-1" output && > ( > cd new && > @@ -340,7 +340,7 @@ test_expect_success GPG 'signed-commits=verbatim' ' > test_expect_success GPG 'signed-commits=warn-verbatim' ' > > git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && > - grep "^gpgsig sha" output && > + grep "^gpgsig openpgp" output && > grep "encoding ISO-8859-1" output && > test -s err && > ( > @@ -381,6 +381,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' > > ' > > +test_expect_success GPGSM 'setup x509 signed commit' ' > + > + git checkout -b x509-signing main && > + test_config gpg.format x509 && > + test_config user.signingkey $GIT_COMMITTER_EMAIL && > + echo "x509 content" >file_for_x509 && > + git add file_for_x509 && > + git commit -S -m "X.509 signed commit" && > + X509_COMMIT=$(git rev-parse --verify HEAD) && > + git checkout main > + > +' > + > +test_expect_success GPGSM 'x509 signature identified' ' > + > + git fast-export --signed-commits=verbatim --reencode=no x509-signing >output 2>err && Is --reencode=no important here or does this work with --reencode=yes as well? (I understand the default being --reencode=abort and fact that you are reusing an example that used a specialized encoding means you need to specify something, was just curious if this particular value was important) > + grep "^gpgsig x509" output && > + test ! -s err && > + ( > + cd new && > + git fast-import && > + STRIPPED=$(git rev-parse --verify refs/heads/x509-signing) && > + test $X509_COMMIT = $STRIPPED Ah, --reencode=no is critical for the test, but only because you are trying to ensure you get the same commit back. Should there also be a test for when something is tweaked, such as the encoding, and whether the signature is still found? > + ) <output && > + test_might_fail git update-ref -d refs/heads/x509-signing > + > +' > + > +test_expect_success GPGSSH 'setup ssh signed commit' ' > + > + git checkout -b ssh-signing main && > + test_config gpg.format ssh && > + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && > + echo "ssh content" >file_for_ssh && > + git add file_for_ssh && > + git commit -S -m "SSH signed commit" && > + SSH_COMMIT=$(git rev-parse --verify HEAD) && > + git checkout main > + > +' > + > +test_expect_success GPGSSH 'ssh signature identified' ' > + > + git fast-export --signed-commits=verbatim --reencode=no ssh-signing >output 2>err && Out of curiosity, any particular reason to export the entire history instead of just the new commit you just added (though you'd likely want --reference-excluded-parents if you did that, which really ought to be the default)? Anyway... > + grep "^gpgsig ssh" output && > + test ! -s err && > + ( > + cd new && > + git fast-import && > + STRIPPED=$(git rev-parse --verify refs/heads/ssh-signing) && > + test "$SSH_COMMIT" = "$STRIPPED" Looks like your two tests are for different signature types, but as noted above, I'm kind of curious to see a test covering what happens when the resulting commit doesn't exactly match the original but still retains a signature. > + ) <output && > + test_might_fail git update-ref -d refs/heads/ssh-signing > + > +' > + > test_expect_success 'setup submodule' ' > > test_config_global protocol.file.allow always && > -- > 2.49.0.392.g2fa1c74b07 ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 21:41 ` Elijah Newren @ 2025-05-26 10:34 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:34 UTC (permalink / raw) To: Elijah Newren Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, Johannes Schindelin, Christian Couder On Thu, Apr 24, 2025 at 11:41 PM Elijah Newren <newren@gmail.com> wrote: > > On Thu, Apr 24, 2025 at 1:39 PM Christian Couder > <christian.couder@gmail.com> wrote: > > > > A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for > > signed-commits, 2025-03-10), added support for signed commits. > > > > However, when processing signatures `git fast-export` outputs "gpgsig > > sha1" not just when it encounters an OpenPGP SHA-1 signature, but also > > when it encounters an SSH or X.509 signature. This is not very > > informative to say the least, and this might prevent tools that process > > the output from easily and properly handling signatures. > > > > Let's improve on that by reusing the existing code from > > "gpg-interface.{c,h}" to detect the signature algorithm, and then put > > the signature algorithm name (like "openpgp", "x509" or "ssh") instead > > of "sha1" in the output. If we can't detect the signature algorithm we > > will use "unknown". It might be a signature added by an external tool > > and we should likely keep it. > > > > Similarly on the `git fast-import` side, let's use the existing code > > from "gpg-interface.{c,h}" to check if a signature algorithm name is > > valid. In case of an "unknown" signature algorithm name, we will warn > > but still keep it. Future work might implement several options to let > > users deal with it in different ways, and might implement checking > > known signatures too. > > The last sentence is somewhat ambiguous about whether it is only about > the "unknown" case or whether the second half of the sentence was > switching tracks to discuss something else about the known cases. Yeah, the second half of the sentence was about switching tracks to discuss other things we might do in the future. > Do > you perhaps mean something like "Future work might implement several > options to let users deal with an "unknown" signature algorithm, and > when we have a valid signature algorithm, we may be able to not only > verify the signature algorithm name but start verifying the signature > itself to ensure it is valid as well." ? Yeah, that was the idea. Thanks for spelling it in a better way than I did. In v2, I have actually tried an approach based on verifying signatures, and I'd be happy to know your opinion about the different approaches. Thanks! [...] > > +Signatures are not yet checked in the current implementation though. > > Thanks for calling this out. [...] > > @@ -2830,12 +2831,15 @@ static void parse_new_commit(const char *arg) > > "encoding %s\n", > > encoding); > > if (sig_alg) { > > - if (!strcmp(sig_alg, "sha1")) > > - strbuf_addstr(&new_data, "gpgsig "); > > - else if (!strcmp(sig_alg, "sha256")) > > + if (!strcmp(sig_alg, "sha256")) > > strbuf_addstr(&new_data, "gpgsig-sha256 "); > > - else > > - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); > > + else if (valid_signature_name(sig_alg)) > > + strbuf_addstr(&new_data, "gpgsig "); > > + else if (!strcmp(sig_alg, "unknown")) { > > + warning("Unknown gpgsig algorithm name!"); > > + strbuf_addstr(&new_data, "gpgsig "); > > + } else > > + die("Invalid gpgsig algorithm name, got '%s'", sig_alg); > > string_list_split_in_place(&siglines, sig.buf, "\n", -1); > > strbuf_add_separated_string_list(&new_data, "\n ", &siglines); > > strbuf_addch(&new_data, '\n'); > > I'm not very familiar with gpg and other signatures, and was stuck > trying to parse this logic when a review from Junio came in, and I > decided to read it since he often "thinks out loud" to see if that'd > explain it better. Sadly, didn't help... ;-) But I'll watch for any > follow-up response you add over there. Yeah, if we want to continue in the direction of this patch, let's discuss it over there. > > @@ -381,6 +381,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' > > > > ' > > > > +test_expect_success GPGSM 'setup x509 signed commit' ' > > + > > + git checkout -b x509-signing main && > > + test_config gpg.format x509 && > > + test_config user.signingkey $GIT_COMMITTER_EMAIL && > > + echo "x509 content" >file_for_x509 && > > + git add file_for_x509 && > > + git commit -S -m "X.509 signed commit" && > > + X509_COMMIT=$(git rev-parse --verify HEAD) && > > + git checkout main > > + > > +' > > + > > +test_expect_success GPGSM 'x509 signature identified' ' > > + > > + git fast-export --signed-commits=verbatim --reencode=no x509-signing >output 2>err && > > Is --reencode=no important here or does this work with --reencode=yes > as well? (I understand the default being --reencode=abort and fact > that you are reusing an example that used a specialized encoding means > you need to specify something, was just curious if this particular > value was important) > > > + grep "^gpgsig x509" output && > > + test ! -s err && > > + ( > > + cd new && > > + git fast-import && > > + STRIPPED=$(git rev-parse --verify refs/heads/x509-signing) && > > + test $X509_COMMIT = $STRIPPED > > Ah, --reencode=no is critical for the test, but only because you are > trying to ensure you get the same commit back. Yeah, I think it's useful to check that we can get the same commit back. > Should there also be a > test for when something is tweaked, such as the encoding, and whether > the signature is still found? [...] > > +test_expect_success GPGSSH 'ssh signature identified' ' > > + > > + git fast-export --signed-commits=verbatim --reencode=no ssh-signing >output 2>err && > > Out of curiosity, any particular reason to export the entire history > instead of just the new commit you just added (though you'd likely > want --reference-excluded-parents if you did that, which really ought > to be the default)? Anyway... Thanks for your review and all your comments! As I am not sure which approach is best and I am trying a different approach in v2, it might not be worth discussing some of the details of the approach I took in this v1 anymore. So I will skip answering some of your specific comments unless we decide to go back to that approach later. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 20:39 [PATCH] fast-(import|export): improve on the signature algorithm name Christian Couder 2025-04-24 21:19 ` Junio C Hamano 2025-04-24 21:41 ` Elijah Newren @ 2025-04-24 22:05 ` brian m. carlson 2025-05-26 10:35 ` Christian Couder 2025-04-24 23:25 ` Junio C Hamano 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder 4 siblings, 1 reply; 65+ messages in thread From: brian m. carlson @ 2025-04-24 22:05 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin, Christian Couder [-- Attachment #1: Type: text/plain, Size: 1313 bytes --] On 2025-04-24 at 20:39:04, Christian Couder wrote: > Here <alg> specifies which hashing algorithm is used for this > -signature, either `sha1` or `sha256`. > +signature. Current valid values are: > + > +* "openpgp" for SHA-1 OpenPGP signatures, > + > +* "sha256" for SHA-256 OpenPGP signatures, > + > +* "x509" for X.509 (GPGSM) signatures, > + > +* "ssh", for SSH signatures, > + > +* "unknown" for signatures that can't be identified (a warning is > + emitted). I don't think this is a good set of options. We can have SHA-1 or SHA-256 options for any of the three. If I create a SHA-256 commit and sign it with SSH, then it couldn't be exported with this type. It is even possible and valid to create a signature over the SHA-1 content of an object and sign it with one protocol, say, OpenPGP, and then create a signature over the SHA-256 content of the object and sign it with a different one, such as SSH. Git does not natively support this, but it is possible to do by hand. These should be separate fields: one for the hash algorithm and one for the protocol. Alternatively, we can just keep the hash algorithm field and parse the protocol by reading the first line, which will differ for different protocols. -- brian m. carlson (they/them) Toronto, Ontario, CA [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 325 bytes --] ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 22:05 ` brian m. carlson @ 2025-05-26 10:35 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:35 UTC (permalink / raw) To: brian m. carlson, Christian Couder, git, Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin, Christian Couder On Fri, Apr 25, 2025 at 12:05 AM brian m. carlson <sandals@crustytoothpaste.net> wrote: > > On 2025-04-24 at 20:39:04, Christian Couder wrote: > > Here <alg> specifies which hashing algorithm is used for this > > -signature, either `sha1` or `sha256`. > > +signature. Current valid values are: > > + > > +* "openpgp" for SHA-1 OpenPGP signatures, > > + > > +* "sha256" for SHA-256 OpenPGP signatures, > > + > > +* "x509" for X.509 (GPGSM) signatures, > > + > > +* "ssh", for SSH signatures, > > + > > +* "unknown" for signatures that can't be identified (a warning is > > + emitted). > > I don't think this is a good set of options. We can have SHA-1 or > SHA-256 options for any of the three. If I create a SHA-256 commit and > sign it with SSH, then it couldn't be exported with this type. > > It is even possible and valid to create a signature over the SHA-1 > content of an object and sign it with one protocol, say, OpenPGP, and > then create a signature over the SHA-256 content of the object and sign > it with a different one, such as SSH. Git does not natively support > this, but it is possible to do by hand. > > These should be separate fields: one for the hash algorithm and one for > the protocol. Yeah, I agree that the set of options is not ideal and it would be better if it was possible to get these two separate fields. > Alternatively, we can just keep the hash algorithm field > and parse the protocol by reading the first line, which will differ for > different protocols. I am not sure it's easy to get all the information without checking the signature. I have tried a different approach based on checking the signature in the v2. Thanks for the review! ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH] fast-(import|export): improve on the signature algorithm name 2025-04-24 20:39 [PATCH] fast-(import|export): improve on the signature algorithm name Christian Couder ` (2 preceding siblings ...) 2025-04-24 22:05 ` brian m. carlson @ 2025-04-24 23:25 ` Junio C Hamano 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder 4 siblings, 0 replies; 65+ messages in thread From: Junio C Hamano @ 2025-04-24 23:25 UTC (permalink / raw) To: Christian Couder Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > This is a follow up from cc/signed-fast-export-import that was merged > by 01d17c0530 (Merge branch 'cc/signed-fast-export-import', 2025-03-29) > and introduced the support for signed commits. I've looked at it and tried to test the result of merging it to 'seen' locally, but for now I'll eject it from 'seen' before pushing today's integration result out, as the local tests seem to be failing (and even though I haven't had time to positively identify this patch is the culprit, as nothing else is affecting fast-export, I'd need to move with an educated guess and then dig deeper later to keep the other topics moving). Test Summary Report ------------------- t9350-fast-export.sh (Wstat: 256 (exited 1) Tests: 65 Failed: 11) Failed tests: 24-25, 29, 31, 33, 35-37, 56-58 Non-zero exit status: 1 Files=1021, Tests=31986, 646 wallclock secs (16.20 usr 5.76 sys + 1053.65 cusr 8317.02 csys = 9392.63 CPU) Result: FAIL gmake[1]: *** [Makefile:78: prove] Error 1 gmake[1]: Leaving directory '/usr/local/google/home/jch/w/buildfarm/seen/t' gmake: *** [Makefile:3287: test] Error 2 ^ permalink raw reply [flat|nested] 65+ messages in thread
* [PATCH v2 0/6] extract algo information from signatures 2025-04-24 20:39 [PATCH] fast-(import|export): improve on the signature algorithm name Christian Couder ` (3 preceding siblings ...) 2025-04-24 23:25 ` Junio C Hamano @ 2025-05-26 10:33 ` Christian Couder 2025-05-26 10:33 ` [PATCH v2 1/6] gpg-interface: simplify ssh fingerprint parsing Christian Couder ` (8 more replies) 4 siblings, 9 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:33 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Around one month ago, I sent a patch that tried to improve on how `git fast-export` handled SSH and X.509 commit signatures: https://lore.kernel.org/git/20250424203904.909777-1-christian.couder@gmail.com/ This patch was showing a single string for the hash algorithm with the following possible values: * "openpgp" for SHA-1 OpenPGP signatures, * "sha256" for SHA-256 OpenPGP signatures, * "x509" for X.509 (GPGSM) signatures, * "ssh", for SSH signatures, * "unknown" for signatures that can't be identified (a warning is emitted). brian m. carlson however replied that it would be better to show two pieces of information instead of one: one for the hash algorithm and one for the protocol. I have tried to do that but there were a number of issues. First it seems to be easier to extract information from signatures when checking them. And if you check them, then it might be interesting to show the result of the check. Also for SSH signatures, it's difficult and not so informative to get the hash algorithm. That's because the hash algorithm is often specified by the key type (like "RSA", "ECDSA", "Ed25519", ...). For example "Ed25519" has SHA-512 integrated into its design, and "ECDSA" and "RSA" are typically used with SHA-256. So for SSH signatures it seems better to just show the key type and not the hash algorithm. In general I am not sure what users might want regarding commit signatures when using fast-export. Some might not need much signature information at all, and for them checking signatures might just slow the export process for no benefit, while others might want more signature information even at the expense of a slower export. To address this, I decided to focus first on extracting the hash algorithm from OpenPGP/X.509 signatures and the key type from SSH signature when checking signatures. To test that, I thought that it could be interesting to add a `--summary` option to `verify-commit` that shows a concise, one-line summary of the signature verification to standard output in the `STATUS FORMAT ALGORITHM` format, where: * STATUS is the result character (e.g., G, B, E, U, N, ...), similar as what the "%G?" pretty format specifier shows, * FORMAT is the signature format (`openpgp`, `x509`, or `ssh`), * ALGORITHM is the hash algorithm used for GPG/GPGSM signatures (e.g. `sha1`, `sha256`, ...), or the key type for SSH signatures (`RSA`, `ECDSA`, `ED25519`, ...). If we can agree on a concise format output for signature checks, then maybe this format will be a good format to be used in the `git fast-export` output for users who are fine with signatures being checked. What do you think? CI tests -------- They have all passed: https://github.com/chriscool/git/actions/runs/15248563563 Range diff compared to v1 ------------------------- No range diff as this series is a completely different approach to the problem, and running range-diff shows completely different patches. Christian Couder (6): gpg-interface: simplify ssh fingerprint parsing gpg-interface: use left shift to define GPG_VERIFY_* doc/verify-commit: update and improve the whole doc gpg-interface: extract hash algorithm from signature status output gpg-interface: extract SSH key type from signature status output verify-commit: add a --summary flag Documentation/git-verify-commit.adoc | 53 +++++++++++-- builtin/verify-commit.c | 4 +- gpg-interface.c | 111 ++++++++++++++++++++++++++- gpg-interface.h | 16 +++- t/t7510-signed-commit.sh | 24 ++++++ t/t7528-signed-commit-ssh.sh | 28 +++++++ 6 files changed, 224 insertions(+), 12 deletions(-) -- 2.49.0.609.g63c55177e5 ^ permalink raw reply [flat|nested] 65+ messages in thread
* [PATCH v2 1/6] gpg-interface: simplify ssh fingerprint parsing 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder @ 2025-05-26 10:33 ` Christian Couder 2025-05-26 10:33 ` [PATCH v2 2/6] gpg-interface: use left shift to define GPG_VERIFY_* Christian Couder ` (7 subsequent siblings) 8 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:33 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder In "gpg-interface.c", the 'parse_ssh_output()' function takes a 'struct signature_check *sigc' argument and populates many members of this 'sigc' using information parsed from 'sigc->output' which contains the ouput of an `ssh-keygen -Y ...` command that was used to verify an SSH signature. When it populates 'sigc->fingerprint' though, it uses `xstrdup(strstr(line, "key ") + 4)` while `strstr(line, "key ")` has already been computed a few lines above and is already available in the `key` variable. Let's simplify this. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- gpg-interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpg-interface.c b/gpg-interface.c index 0896458de5..e7af82d123 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -431,7 +431,7 @@ static void parse_ssh_output(struct signature_check *sigc) key = strstr(line, "key "); if (key) { - sigc->fingerprint = xstrdup(strstr(line, "key ") + 4); + sigc->fingerprint = xstrdup(key + 4); sigc->key = xstrdup(sigc->fingerprint); } else { /* -- 2.49.0.609.g63c55177e5 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* [PATCH v2 2/6] gpg-interface: use left shift to define GPG_VERIFY_* 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder 2025-05-26 10:33 ` [PATCH v2 1/6] gpg-interface: simplify ssh fingerprint parsing Christian Couder @ 2025-05-26 10:33 ` Christian Couder 2025-05-26 10:33 ` [PATCH v2 3/6] doc/verify-commit: update and improve the whole doc Christian Couder ` (6 subsequent siblings) 8 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:33 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder In "gpg-interface.h", the definitions of the GPG_VERIFY_* boolean flags are currently using 1, 2 and 4 while we often prefer the bitwise left shift operator, `<<`, for that purpose to make it clearer that they are boolean. Let's use the left shift operator here too. Let's also fix an indent issue with "4" while at it. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- gpg-interface.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gpg-interface.h b/gpg-interface.h index e09f12e8d0..9a32dd6ce8 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -3,9 +3,9 @@ struct strbuf; -#define GPG_VERIFY_VERBOSE 1 -#define GPG_VERIFY_RAW 2 -#define GPG_VERIFY_OMIT_STATUS 4 +#define GPG_VERIFY_VERBOSE (1<<0) +#define GPG_VERIFY_RAW (1<<1) +#define GPG_VERIFY_OMIT_STATUS (1<<2) enum signature_trust_level { TRUST_UNDEFINED, -- 2.49.0.609.g63c55177e5 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* [PATCH v2 3/6] doc/verify-commit: update and improve the whole doc 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder 2025-05-26 10:33 ` [PATCH v2 1/6] gpg-interface: simplify ssh fingerprint parsing Christian Couder 2025-05-26 10:33 ` [PATCH v2 2/6] gpg-interface: use left shift to define GPG_VERIFY_* Christian Couder @ 2025-05-26 10:33 ` Christian Couder 2025-05-26 10:33 ` [PATCH v2 4/6] gpg-interface: extract hash algorithm from signature status output Christian Couder ` (5 subsequent siblings) 8 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:33 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder The documentation of the `git verify-commit` commands currently looks very outdated and minimal. Especially it has the following issues: - It only talks about verifying GPG signatures while the command actually supports verifying other signatures like SSH ones. - It's not clear what the exit code of the command is. - It talks about the `<commit>...` arguments only as "SHA-1 identifiers" while SHA-256 as well as any committish is actually supported. Let's fix all those issues by updating and improving the whole documentation. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- Documentation/git-verify-commit.adoc | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/Documentation/git-verify-commit.adoc b/Documentation/git-verify-commit.adoc index aee4c40eac..6a208a0c2a 100644 --- a/Documentation/git-verify-commit.adoc +++ b/Documentation/git-verify-commit.adoc @@ -3,7 +3,7 @@ git-verify-commit(1) NAME ---- -git-verify-commit - Check the GPG signature of commits +git-verify-commit - Check the signature of commits SYNOPSIS -------- @@ -12,20 +12,46 @@ SYNOPSIS DESCRIPTION ----------- -Validates the GPG signature created by 'git commit -S'. +Validates the cryptographic signature of commits. This is typically +a GPG signature created by 'git commit -S', but other signature +formats like SSH may also be verified depending on Git configuration +(see linkgit:git-config[1] and the `gpg.format` option). + +By default, the command prints human-readable verification results to +standard error. + +EXIT STATUS +----------- +If all the specified commits are successfully verified and their +signatures are good and trusted according to the configured trust +requirements, the command exits with 0. + +If any commit fails verification (e.g., due to a bad signature, a +missing or untrusted key), if a specified object cannot be found or is +not a commit object, or if another error occurs during verification, +the command exits with a non-zero status. OPTIONS ------- --raw:: - Print the raw gpg status output to standard error instead of the normal - human-readable output. + Print the raw signature verification status output to standard + error instead of the normal human-readable output. The format + of this output is specific to the signature format being used. -v:: --verbose:: Print the contents of the commit object before validating it. <commit>...:: - SHA-1 identifiers of Git commit objects. + Commit objects to verify. Can be specified using any format + accepted by linkgit:git-rev-parse[1]. + +SEE ALSO +-------- +linkgit:git-commit[1], +linkgit:git-config[1], +linkgit:git-verify-tag[1], +linkgit:git-log[1] GIT --- -- 2.49.0.609.g63c55177e5 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* [PATCH v2 4/6] gpg-interface: extract hash algorithm from signature status output 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder ` (2 preceding siblings ...) 2025-05-26 10:33 ` [PATCH v2 3/6] doc/verify-commit: update and improve the whole doc Christian Couder @ 2025-05-26 10:33 ` Christian Couder 2025-05-26 10:33 ` [PATCH v2 5/6] gpg-interface: extract SSH key type " Christian Couder ` (4 subsequent siblings) 8 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:33 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder When using GPG/GPGSM to verify OpenPGP/X.509 signatures, the verification result (good/bad/etc.), signer, and key fingerprint are extracted from the output, but not the specific hash algorithm (e.g., "sha1", "sha256") reported by GPG as having been used for the signature itself. Let's improve the `gpg-interface` parsing logic to capture this information. This information can be useful for Git commands or external tools that process signature information. For example, it could be used when displaying signature verification results to users or when working with various signature formats in tools like fast-export and fast-import. GPG provides the hash algorithm ID used for the signature within its machine-readable status output, specifically in the fields following the `VALIDSIG` and `ERRSIG` keywords, as documented in GnuPG's `doc/DETAILS`. The implementation follows RFC 4880 (OpenPGP Message Format) section 9.4 for the mapping between hash algorithm IDs and their corresponding names. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- gpg-interface.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ gpg-interface.h | 4 +++ 2 files changed, 78 insertions(+) diff --git a/gpg-interface.c b/gpg-interface.c index e7af82d123..15687ede43 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -153,6 +153,7 @@ void signature_check_clear(struct signature_check *sigc) FREE_AND_NULL(sigc->key); FREE_AND_NULL(sigc->fingerprint); FREE_AND_NULL(sigc->primary_key_fingerprint); + FREE_AND_NULL(sigc->sig_algo); } /* An exclusive status -- only one of them can appear in output */ @@ -221,6 +222,65 @@ static int parse_gpg_trust_level(const char *level, return 1; } +/* See RFC 4880: OpenPGP Message Format, section 9.4. Hash Algorithms */ +static struct sigcheck_gpg_hash_algo { + const char *id; + const char *name; +} sigcheck_gpg_hash_algo[] = { + { "1", "md5" }, /* deprecated */ + { "2", "sha1" }, /* mandatory */ + { "3", "ripemd160" }, + { "8", "sha256" }, + { "9", "sha384" }, + { "10", "sha512" }, + { "11", "sha224" }, +}; + +static const char *lookup_gpg_hash_algo(const char *algo_id) +{ + if (!algo_id) + return NULL; + + for (size_t i = 0; i < ARRAY_SIZE(sigcheck_gpg_hash_algo); i++) { + if (!strcmp(sigcheck_gpg_hash_algo[i].id, algo_id)) + return sigcheck_gpg_hash_algo[i].name; + } + + return NULL; +} + +static char *extract_gpg_hash_algo(const char *args_start, + const char *line_end, + int field_index) +{ + const char *p = args_start; + int current_field = 0; + char *result = NULL; + + while (p < line_end && current_field < field_index) { + /* Skip to the end of the current field */ + while (p < line_end && *p != ' ') + p++; + /* Skip spaces to get to the start of the next field */ + while (p < line_end && *p == ' ') { + p++; + current_field++; + } + } + + if (p < line_end && current_field == field_index) { + /* Found start of the target field */ + const char *algo_id_end = strchrnul(p, ' '); + char *algo_id = xmemdupz(p, algo_id_end - p); + const char *hash_algo = lookup_gpg_hash_algo(algo_id); + if (hash_algo) + result = xstrdup(hash_algo); + free(algo_id); + } + + return result; +} + static void parse_gpg_output(struct signature_check *sigc) { const char *buf = sigc->gpg_status; @@ -242,6 +302,18 @@ static void parse_gpg_output(struct signature_check *sigc) /* Iterate over all search strings */ for (size_t i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) { if (skip_prefix(line, sigcheck_gpg_status[i].check, &line)) { + + /* Do we have hash algorithm? */ + if (!sigc->sig_algo) { + const char *line_end = strchrnul(line, '\n'); + if (!strcmp(sigcheck_gpg_status[i].check, "VALIDSIG ")) + /* Hash algorithm is the 8th field in VALIDSIG */ + sigc->sig_algo = extract_gpg_hash_algo(line, line_end, 7); + else if (!strcmp(sigcheck_gpg_status[i].check, "ERRSIG ")) + /* Hash algorithm is the 3rd field in ERRSIG */ + sigc->sig_algo = extract_gpg_hash_algo(line, line_end, 2); + } + /* * GOODSIG, BADSIG etc. can occur only once for * each signature. Therefore, if we had more @@ -323,6 +395,7 @@ static void parse_gpg_output(struct signature_check *sigc) } } } + return; error: @@ -332,6 +405,7 @@ static void parse_gpg_output(struct signature_check *sigc) FREE_AND_NULL(sigc->fingerprint); FREE_AND_NULL(sigc->signer); FREE_AND_NULL(sigc->key); + FREE_AND_NULL(sigc->sig_algo); } static int verify_gpg_signed_buffer(struct signature_check *sigc, diff --git a/gpg-interface.h b/gpg-interface.h index 9a32dd6ce8..2b7701ca2c 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -42,6 +42,10 @@ struct signature_check { char *key; char *fingerprint; char *primary_key_fingerprint; + + /* hash algo for GPG/GPGSM, key type for SSH */ + char *sig_algo; + enum signature_trust_level trust_level; }; -- 2.49.0.609.g63c55177e5 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* [PATCH v2 5/6] gpg-interface: extract SSH key type from signature status output 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder ` (3 preceding siblings ...) 2025-05-26 10:33 ` [PATCH v2 4/6] gpg-interface: extract hash algorithm from signature status output Christian Couder @ 2025-05-26 10:33 ` Christian Couder 2025-05-26 10:33 ` [PATCH v2 6/6] verify-commit: add a --summary flag Christian Couder ` (3 subsequent siblings) 8 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:33 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder A previous commit extracted the hash algorithm from GPG/GPGSM signature status output and stored it in a new 'sig_algo' member of 'struct signature_check'. For SSH signatures, it's more interesting and easier to extract the key type (like "RSA", "ECDSA", "Ed25519", ...) rather than the hash algorithm which often depends on the key type. For example "Ed25519" has SHA-512 integrated into its design, and "ECDSA" and "RSA" are typically used with SHA-256. Let's improve the `gpg-interface` parsing logic to capture the SSH key type when parsing the SSH signature status output. Similarly as the hash algorithm for GPG/GPGSM signatures, this information can be useful for Git commands or external tools that process signature information. For example, it could be used when displaying signature verification results to users or when working with various signature formats in tools like fast-export and fast-import. As they share a common usage, we also store the SSH key type in the new 'sig_algo' member of 'struct signature_check'. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- gpg-interface.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/gpg-interface.c b/gpg-interface.c index 15687ede43..182e579769 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -456,11 +456,27 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc, return ret; } +static char *extract_ssh_key_type(const char *type_start, const char *type_end) +{ + if (!type_end || type_end <= type_start) + return NULL; + + /* Back up over any spaces before " key " */ + while (type_end > type_start && *(type_end - 1) == ' ') + type_end--; + + if (type_end <= type_start) + return NULL; + + return xmemdupz(type_start, type_end - type_start); +} + static void parse_ssh_output(struct signature_check *sigc) { const char *line, *principal, *search; char *to_free; char *key = NULL; + const char *after_last_with = NULL; /* * ssh-keygen output should be: @@ -485,8 +501,10 @@ static void parse_ssh_output(struct signature_check *sigc) principal = line; do { search = strstr(line, " with "); - if (search) + if (search) { line = search + 1; + after_last_with = search + 6; + } } while (search != NULL); if (line == principal) goto cleanup; @@ -499,6 +517,7 @@ static void parse_ssh_output(struct signature_check *sigc) /* Valid signature, but key unknown */ sigc->result = 'G'; sigc->trust_level = TRUST_UNDEFINED; + after_last_with = line; } else { goto cleanup; } @@ -507,6 +526,9 @@ static void parse_ssh_output(struct signature_check *sigc) if (key) { sigc->fingerprint = xstrdup(key + 4); sigc->key = xstrdup(sigc->fingerprint); + + if (after_last_with) + sigc->sig_algo = extract_ssh_key_type(after_last_with, key); } else { /* * Output did not match what we expected -- 2.49.0.609.g63c55177e5 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* [PATCH v2 6/6] verify-commit: add a --summary flag 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder ` (4 preceding siblings ...) 2025-05-26 10:33 ` [PATCH v2 5/6] gpg-interface: extract SSH key type " Christian Couder @ 2025-05-26 10:33 ` Christian Couder 2025-05-26 16:03 ` [PATCH v2 0/6] extract algo information from signatures Elijah Newren ` (2 subsequent siblings) 8 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-05-26 10:33 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder The current outputs, with `--raw`, `--verbose` or by default, from `git verify-commit` are all quite verbose and do not make it easy to quickly assess signature status. Let's add a new `--summary` option to `git verify-commit` that prints a concise, one-line summary of the signature verification to standard output. This compact format is useful for scripts and tools that need to quickly parse signature verification results, while still being human-readable. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- Documentation/git-verify-commit.adoc | 17 ++++++++++++++++- builtin/verify-commit.c | 4 +++- gpg-interface.c | 11 +++++++++++ gpg-interface.h | 6 ++++++ t/t7510-signed-commit.sh | 24 ++++++++++++++++++++++++ t/t7528-signed-commit-ssh.sh | 28 ++++++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 2 deletions(-) diff --git a/Documentation/git-verify-commit.adoc b/Documentation/git-verify-commit.adoc index 6a208a0c2a..fb038ae0cf 100644 --- a/Documentation/git-verify-commit.adoc +++ b/Documentation/git-verify-commit.adoc @@ -8,7 +8,7 @@ git-verify-commit - Check the signature of commits SYNOPSIS -------- [verse] -'git verify-commit' [-v | --verbose] [--raw] <commit>... +'git verify-commit' [-v | --verbose] [--raw] [--summary] <commit>... DESCRIPTION ----------- @@ -38,6 +38,21 @@ OPTIONS error instead of the normal human-readable output. The format of this output is specific to the signature format being used. +--summary:: + Print a one-line human-readable summary of the signature check + to standard output in the format: `STATUS FORMAT ALGORITHM`. ++ +STATUS is the result character (e.g., G, B, E, U, N, ...) shown by the +"%G?" pretty format specifier. See the "Pretty Formats" section in +linkgit:git-log[1]. ++ +FORMAT indicates the signature format (`openpgp`, `x509`, or `ssh`) or +`?` if unknown. ++ +ALGORITHM is the hash algorithm used for GPG/GPGSM signatures +(e.g. `sha1`, `sha256`, ...), or the key type for SSH signatures +(`RSA`, `ECDSA`, `ED25519`, ...), or `?` if unknown. + -v:: --verbose:: Print the contents of the commit object before validating it. diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index 5f749a30da..54b5b7d360 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -14,7 +14,7 @@ #include "gpg-interface.h" static const char * const verify_commit_usage[] = { - N_("git verify-commit [-v | --verbose] [--raw] <commit>..."), + N_("git verify-commit [-v | --verbose] [--raw] [--summary] <commit>..."), NULL }; @@ -27,6 +27,7 @@ static int run_gpg_verify(struct commit *commit, unsigned flags) ret = check_commit_signature(commit, &signature_check); print_signature_buffer(&signature_check, flags); + print_signature_summary(&signature_check, flags); signature_check_clear(&signature_check); return ret; @@ -60,6 +61,7 @@ int cmd_verify_commit(int argc, const struct option verify_commit_options[] = { OPT__VERBOSE(&verbose, N_("print commit contents")), OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), + OPT_BIT(0, "summary", &flags, N_("print concise signature verification summary"), GPG_VERIFY_SUMMARY), OPT_END() }; diff --git a/gpg-interface.c b/gpg-interface.c index 182e579769..fc198715c4 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -153,6 +153,7 @@ void signature_check_clear(struct signature_check *sigc) FREE_AND_NULL(sigc->key); FREE_AND_NULL(sigc->fingerprint); FREE_AND_NULL(sigc->primary_key_fingerprint); + FREE_AND_NULL(sigc->format_name); FREE_AND_NULL(sigc->sig_algo); } @@ -756,6 +757,8 @@ int check_signature(struct signature_check *sigc, if (!fmt) die(_("bad/incompatible signature '%s'"), signature); + sigc->format_name = xstrdup(fmt->name); + if (parse_payload_metadata(sigc)) return 1; @@ -782,6 +785,14 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags) fputs(output, stderr); } +void print_signature_summary(const struct signature_check *sigc, unsigned flags) +{ + if (flags & GPG_VERIFY_SUMMARY) + printf("%c %s %s\n", sigc->result, + sigc->format_name ? sigc->format_name : "?", + sigc->sig_algo ? sigc->sig_algo : "?"); +} + size_t parse_signed_buffer(const char *buf, size_t size) { size_t len = 0; diff --git a/gpg-interface.h b/gpg-interface.h index 2b7701ca2c..a9565757f6 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -6,6 +6,7 @@ struct strbuf; #define GPG_VERIFY_VERBOSE (1<<0) #define GPG_VERIFY_RAW (1<<1) #define GPG_VERIFY_OMIT_STATUS (1<<2) +#define GPG_VERIFY_SUMMARY (1<<3) enum signature_trust_level { TRUST_UNDEFINED, @@ -43,6 +44,9 @@ struct signature_check { char *fingerprint; char *primary_key_fingerprint; + /* "openpgp", "x509", "ssh" */ + char *format_name; + /* hash algo for GPG/GPGSM, key type for SSH */ char *sig_algo; @@ -95,5 +99,7 @@ int check_signature(struct signature_check *sigc, const char *signature, size_t slen); void print_signature_buffer(const struct signature_check *sigc, unsigned flags); +void print_signature_summary(const struct signature_check *sigc, + unsigned flags); #endif diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 39677e859a..47f40862f3 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -232,6 +232,30 @@ test_expect_success GPG2 'bare signature' ' test_cmp expect actual ' +test_expect_success GPG 'verify signatures with --summary' ' + # GPG-signed commit + git verify-commit --summary sixth-signed >actual && + test_grep "^G openpgp sha1" actual && + + # Non-signed commit + test_must_fail git verify-commit --summary seventh-unsigned >actual 2>&1 && + test_grep "^N ? ?" actual && + + # Trusted signature with alternate key (hash used might depend on the OS) + git verify-commit --summary eighth-signed-alt >actual && + test_grep -E "^G openpgp sha(256|512)" actual && + + # Bad signature + test_must_fail git verify-commit --summary $(cat forged1.commit) >actual 2>err && + test_grep "^B openpgp ?" actual +' + +test_expect_success GPG '--summary and --raw work together' ' + git verify-commit --summary --raw sixth-signed >actual 2>err && + test_grep "^G openpgp sha1" actual && + test_grep "GOODSIG" err +' + test_expect_success GPG 'show good signature with custom format' ' cat >expect <<-\EOF && G diff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh index 065f780636..3d0e7d7859 100755 --- a/t/t7528-signed-commit-ssh.sh +++ b/t/t7528-signed-commit-ssh.sh @@ -277,6 +277,34 @@ test_expect_success GPGSSH 'detect fudged signature with NUL' ' ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual2 ' +test_expect_success GPGSSH 'verify-commit --summary outputs format and key type for SSH signatures' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + + # SSH-signed commit with ED25519 key + git verify-commit --summary sixth-signed >actual && + test_grep "^G ssh ED25519" actual && + + # SSH-signed commit with ECDSA key + git verify-commit --summary thirteenth-signed-ecdsa >actual && + test_grep "^G ssh ECDSA" actual && + + # Non-signed commit + test_must_fail git verify-commit --summary seventh-unsigned >actual 2>&1 && + test_grep "^N ? ?" actual && + + # Bad signature + test_must_fail git verify-commit --summary $(cat forged1.commit) >actual 2>err && + test_grep "^B ssh ?" actual +' + +test_expect_success GPGSSH '--summary and --raw work together' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + + git verify-commit --summary --raw sixth-signed >actual 2>err && + test_grep "^G ssh ED25519" actual && + test_grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" err +' + test_expect_success GPGSSH 'amending already signed commit' ' test_config gpg.format ssh && test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && -- 2.49.0.609.g63c55177e5 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* Re: [PATCH v2 0/6] extract algo information from signatures 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder ` (5 preceding siblings ...) 2025-05-26 10:33 ` [PATCH v2 6/6] verify-commit: add a --summary flag Christian Couder @ 2025-05-26 16:03 ` Elijah Newren 2025-06-19 13:38 ` Christian Couder 2025-06-02 22:17 ` brian m. carlson 2025-06-18 15:18 ` [PATCH v3] fast-(import|export): improve on commit signature output format Christian Couder 8 siblings, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-05-26 16:03 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin On Mon, May 26, 2025 at 3:33 AM Christian Couder <christian.couder@gmail.com> wrote: > > Around one month ago, I sent a patch that tried to improve on how `git > fast-export` handled SSH and X.509 commit signatures: > > https://lore.kernel.org/git/20250424203904.909777-1-christian.couder@gmail.com/ > > This patch was showing a single string for the hash algorithm with the > following possible values: > > * "openpgp" for SHA-1 OpenPGP signatures, > > * "sha256" for SHA-256 OpenPGP signatures, > > * "x509" for X.509 (GPGSM) signatures, > > * "ssh", for SSH signatures, > > * "unknown" for signatures that can't be identified (a warning is > emitted). > > brian m. carlson however replied that it would be better to show two > pieces of information instead of one: one for the hash algorithm and > one for the protocol. > > I have tried to do that but there were a number of issues. First it > seems to be easier to extract information from signatures when > checking them. And if you check them, then it might be interesting to > show the result of the check. > > Also for SSH signatures, it's difficult and not so informative to get > the hash algorithm. That's because the hash algorithm is often > specified by the key type (like "RSA", "ECDSA", "Ed25519", ...). For > example "Ed25519" has SHA-512 integrated into its design, and "ECDSA" > and "RSA" are typically used with SHA-256. So for SSH signatures it > seems better to just show the key type and not the hash algorithm. > > In general I am not sure what users might want regarding commit > signatures when using fast-export. Some might not need much signature > information at all, and for them checking signatures might just slow > the export process for no benefit, while others might want more > signature information even at the expense of a slower export. I'd like to propose that the following are the possible uses that users might have regarding commit signatures with fast-export/fast-import (if anyone has additional usecases, let me know): (A) Make fast-export include signatures, and make fast-import include them unconditionally (even if invalid) (B) Similar to (A), but make *fast-import* check them and either error out or drop them if they become invalid (C) Simliar to (B), but make *fast-import* re-sign the commit if they become invalid (D) Similar to (A), but make *fast-import* re-sign the commit even if the signature would have been valid Note that in the above, there might be additional processing between when fast-export runs and when fast-import does (e.g. by filter-repo or a similar tool, or even the user editing by hand). > To address this, I decided to focus first on extracting the hash > algorithm from OpenPGP/X.509 signatures and the key type from SSH > signature when checking signatures. > > To test that, I thought that it could be interesting to add a > `--summary` option to `verify-commit` that shows a concise, one-line > summary of the signature verification to standard output in the > `STATUS FORMAT ALGORITHM` format, where: > > * STATUS is the result character (e.g., G, B, E, U, N, ...), similar > as what the "%G?" pretty format specifier shows, > > * FORMAT is the signature format (`openpgp`, `x509`, or `ssh`), > > * ALGORITHM is the hash algorithm used for GPG/GPGSM signatures > (e.g. `sha1`, `sha256`, ...), or the key type for SSH signatures > (`RSA`, `ECDSA`, `ED25519`, ...). This sounds like it might be a nice feature extension to the verify-commit builtin. I don't see how it helps implement signature handling in fast-export/fast-import, though. > If we can agree on a concise format output for signature checks, then > maybe this format will be a good format to be used in the `git > fast-export` output for users who are fine with signatures being > checked. > > What do you think? Maybe I'm missing something, but it seems to me that checking signatures *in fast-export* would be a complete waste of time. For usecases (A) & (D), checking signatures at all is a waste of time. For usecases (B) & (C), checking signatures in fast-export is throwaway work because whether or not the signatures are valid at the time fast-export runs, and even in the rare usecase where there is no additional processing between fast-export and fast-import (such as by filter-repo), the signatures would still need to be re-checked by fast-import anyway. (Note that a simple `git fast-export ... | git fast-import` is *not* guaranteed to get the same commit hashes even when there are no commit signatures; that only happens when the history is sufficiently canonical). ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v2 0/6] extract algo information from signatures 2025-05-26 16:03 ` [PATCH v2 0/6] extract algo information from signatures Elijah Newren @ 2025-06-19 13:38 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-06-19 13:38 UTC (permalink / raw) To: Elijah Newren Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin On Mon, May 26, 2025 at 6:03 PM Elijah Newren <newren@gmail.com> wrote: > > On Mon, May 26, 2025 at 3:33 AM Christian Couder > <christian.couder@gmail.com> wrote: > I'd like to propose that the following are the possible uses that > users might have regarding commit signatures with > fast-export/fast-import (if anyone has additional usecases, let me > know): > > (A) Make fast-export include signatures, and make fast-import include > them unconditionally (even if invalid) > (B) Similar to (A), but make *fast-import* check them and either error > out or drop them if they become invalid > (C) Simliar to (B), but make *fast-import* re-sign the commit if they > become invalid > (D) Similar to (A), but make *fast-import* re-sign the commit even if > the signature would have been valid > > Note that in the above, there might be additional processing between > when fast-export runs and when fast-import does (e.g. by filter-repo > or a similar tool, or even the user editing by hand). I agree that they are likely to be the most important use cases, and I am fine with working on these use cases. > > To address this, I decided to focus first on extracting the hash > > algorithm from OpenPGP/X.509 signatures and the key type from SSH > > signature when checking signatures. > > > > To test that, I thought that it could be interesting to add a > > `--summary` option to `verify-commit` that shows a concise, one-line > > summary of the signature verification to standard output in the > > `STATUS FORMAT ALGORITHM` format, where: > > > > * STATUS is the result character (e.g., G, B, E, U, N, ...), similar > > as what the "%G?" pretty format specifier shows, > > > > * FORMAT is the signature format (`openpgp`, `x509`, or `ssh`), > > > > * ALGORITHM is the hash algorithm used for GPG/GPGSM signatures > > (e.g. `sha1`, `sha256`, ...), or the key type for SSH signatures > > (`RSA`, `ECDSA`, `ED25519`, ...). > > This sounds like it might be a nice feature extension to the > verify-commit builtin. I don't see how it helps implement signature > handling in fast-export/fast-import, though. Fair enough. In the v3 and v4, I changed the approach and dropped all of this. > > If we can agree on a concise format output for signature checks, then > > maybe this format will be a good format to be used in the `git > > fast-export` output for users who are fine with signatures being > > checked. > > > > What do you think? > > Maybe I'm missing something, but it seems to me that checking > signatures *in fast-export* would be a complete waste of time. For > usecases (A) & (D), checking signatures at all is a waste of time. > For usecases (B) & (C), checking signatures in fast-export is > throwaway work because whether or not the signatures are valid at the > time fast-export runs, and even in the rare usecase where there is no > additional processing between fast-export and fast-import (such as by > filter-repo), the signatures would still need to be re-checked by > fast-import anyway. (Note that a simple `git fast-export ... | git > fast-import` is *not* guaranteed to get the same commit hashes even > when there are no commit signatures; that only happens when the > history is sufficiently canonical). Yeah, right. In v3 and v4, I dropped this in favor of something simpler similar to what was in the v1 patch, and after that I plan to work on checking signatures in fast-import soon. Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v2 0/6] extract algo information from signatures 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder ` (6 preceding siblings ...) 2025-05-26 16:03 ` [PATCH v2 0/6] extract algo information from signatures Elijah Newren @ 2025-06-02 22:17 ` brian m. carlson 2025-06-19 13:37 ` Christian Couder 2025-06-18 15:18 ` [PATCH v3] fast-(import|export): improve on commit signature output format Christian Couder 8 siblings, 1 reply; 65+ messages in thread From: brian m. carlson @ 2025-06-02 22:17 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin [-- Attachment #1: Type: text/plain, Size: 1246 bytes --] On 2025-05-26 at 10:33:08, Christian Couder wrote: > Around one month ago, I sent a patch that tried to improve on how `git > fast-export` handled SSH and X.509 commit signatures: > > https://lore.kernel.org/git/20250424203904.909777-1-christian.couder@gmail.com/ > > This patch was showing a single string for the hash algorithm with the > following possible values: > > * "openpgp" for SHA-1 OpenPGP signatures, > > * "sha256" for SHA-256 OpenPGP signatures, > > * "x509" for X.509 (GPGSM) signatures, > > * "ssh", for SSH signatures, > > * "unknown" for signatures that can't be identified (a warning is > emitted). > > brian m. carlson however replied that it would be better to show two > pieces of information instead of one: one for the hash algorithm and > one for the protocol. Actually, what I was saying is that we should have one for the hash algorithm that is used in the Git object. I don't care about the hash algorithm used in OpenPGP, X.509, or OpenSSH (that is, whether it's signed with SHA-512 or SHA-256), but we can have multiple signatures in a single commit such that there's both a SHA-1 signature and a SHA-256 signature. -- brian m. carlson (they/them) Toronto, Ontario, CA [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 325 bytes --] ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v2 0/6] extract algo information from signatures 2025-06-02 22:17 ` brian m. carlson @ 2025-06-19 13:37 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-06-19 13:37 UTC (permalink / raw) To: brian m. carlson, Christian Couder, git, Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, Johannes Schindelin On Tue, Jun 3, 2025 at 12:17 AM brian m. carlson <sandals@crustytoothpaste.net> wrote: > > On 2025-05-26 at 10:33:08, Christian Couder wrote: > > brian m. carlson however replied that it would be better to show two > > pieces of information instead of one: one for the hash algorithm and > > one for the protocol. > > Actually, what I was saying is that we should have one for the hash > algorithm that is used in the Git object. I don't care about the hash > algorithm used in OpenPGP, X.509, or OpenSSH (that is, whether it's > signed with SHA-512 or SHA-256), but we can have multiple signatures in > a single commit such that there's both a SHA-1 signature and a SHA-256 > signature. Thanks for clarifying. I think that's what I implemented in v3 and v4. ^ permalink raw reply [flat|nested] 65+ messages in thread
* [PATCH v3] fast-(import|export): improve on commit signature output format 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder ` (7 preceding siblings ...) 2025-06-02 22:17 ` brian m. carlson @ 2025-06-18 15:18 ` Christian Couder 2025-06-19 13:36 ` [PATCH v4] " Christian Couder 8 siblings, 1 reply; 65+ messages in thread From: Christian Couder @ 2025-06-18 15:18 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for signed-commits, 2025-03-10), added support for signed commits to fast-export and fast-import. When a signed commit is processed, fast-export can output either "gpgsig sha1" or "gpgsig sha256" depending on whether the signed commit uses the SHA-1 or SHA-256 Git object format. However, this implementation has a number of limitations: - the output format was not properly described in the documentation, - the output format is not very informative as it doesn't even say if the signature is an OpenPGP, an SSH, or an X509 signature, - the implementation doesn't support having both one signature on the SHA-1 object and one on the SHA-256 object. Let's improve on these limitations by improving fast-export and fast-import so that: - both one signature on the SHA-1 object and one on the SHA-256 object can be exported and imported, - if there is more than one signature on the SHA-1 object or on the SHA-256 object, a warning is emitted, - the output format is "gpgsig <git-hash-algo> <signature-format>", where <git-hash-algo> is the Git object format as before, and <signature-format> is the signature type ("openpgp", "x509", "ssh" or "unknown", - the output is properly documented. Note that it could be even better to be able to export and import more than one signature on the SHA-1 object and on the SHA-256 object, but other parts of Git don't handle that well for now, so this is left for future improvements. Helped-by: brian m. carlson <sandals@crustytoothpaste.net> Helped-by: Elijah Newren <newren@gmail.com> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- The v1 of this patch series simply replaced the generic "sha1" or "sha256" in the 'gpgsig' command that `git fast-export` produces and `git fast-import` consumes with the signature format (e.g., openpgp, ssh). In the v2, a different approach was tried in the direction of possibly making `git fast-export` check signatures to be able to get more information from them. In this v3, the approach is more similar to v1, but takes into account what was learned from the previous approaches. The signature format (e.g., openpgp, ssh) is added to the 'gpgsig' command, while both a SHA-1 and a SHA-256 signature from the same commit can be processed and a warning are emitted for each additional signature with the same hash type. There are no tests in this v3 with both a SHA-1 and a SHA-256 signature on the same commit though, as I am not sure yet how to best generate a commit with such signatures. Suggestions welcome! As the patch is very different from both v1 and v2 no range-diff is provided. I have tried to launch CI tests but they seem blocked for 24 minutes at the "config" stage. Thanks to brian, Elijah and Junio who commented on the v1 and v2. Documentation/git-fast-export.adoc | 17 +++++ Documentation/git-fast-import.adoc | 31 ++++++-- builtin/fast-export.c | 67 ++++++++++++++--- builtin/fast-import.c | 117 +++++++++++++++++++++++------ gpg-interface.c | 12 +++ gpg-interface.h | 12 +++ t/t9350-fast-export.sh | 60 ++++++++++++++- 7 files changed, 274 insertions(+), 42 deletions(-) diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 43bbb4f63c..64198f2186 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -50,6 +50,23 @@ resulting tag will have an invalid signature. is the same as how earlier versions of this command without this option behaved. + +When exported, a signature starts with: ++ +gpgsig <git-hash-algo> <signature-format> ++ +where <git-hash-algo> is the Git object hash so either "sha1" or +"sha256", and <signature-format> is the signature type, so "openpgp", +"x509", "ssh" or "unknown". ++ +For example, an OpenPGP signature on a SHA-1 commit starts with +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit +starts with `gpgsig sha256 ssh`. ++ +Currently for a given commit, at most one signature for the SHA-1 +object and one signature for the SHA-256 object are exported, each +with their respective <git-hash-algo> identifier. A warning is +emitted for each additional signature found. ++ NOTE: This is highly experimental and the format of the data stream may change in the future without compatibility guarantees. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 250d866652..db5e5c8da5 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -445,7 +445,7 @@ one). original-oid? ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? 'committer' (SP <name>)? SP LT <email> GT SP <when> LF - ('gpgsig' SP <alg> LF data)? + ('gpgsig' SP <algo> SP <format> LF data)? ('encoding' SP <encoding> LF)? data ('from' SP <commit-ish> LF)? @@ -518,13 +518,32 @@ their syntax. ^^^^^^^^ The optional `gpgsig` command is used to include a PGP/GPG signature -that signs the commit data. +or other cryptographic signature that signs the commit data. -Here <alg> specifies which hashing algorithm is used for this -signature, either `sha1` or `sha256`. +.... + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF + data +.... + +The `gpgsig` command takes two arguments: + +* `<git-hash-algo>` specifies which Git object format this signature + applies to, either `sha1` or `sha256`. + +* `<signature-format>` specifies the type of signature, such as + `openpgp`, `x509`, `ssh`, or `unknown`. + +A commit may have at most one signature for the SHA-1 object format +(stored in the "gpgsig" header) and one for the SHA-256 object format +(stored in the "gpgsig-sha256" header). + +See below for a detailed description of the `data` command which +contains the raw signature data. + +Signatures are not yet checked in the current implementation though. -NOTE: This is highly experimental and the format of the data stream may -change in the future without compatibility guarantees. +NOTE: This is highly experimental and the format of the `gpgsig` +command may change in the future without compatibility guarantees. `encoding` ^^^^^^^^^^ diff --git a/builtin/fast-export.c b/builtin/fast-export.c index fcf6b00d5f..332c036ee4 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -29,6 +29,7 @@ #include "quote.h" #include "remote.h" #include "blob.h" +#include "gpg-interface.h" static const char *const fast_export_usage[] = { N_("git fast-export [<rev-list-opts>]"), @@ -652,6 +653,30 @@ static const char *find_commit_multiline_header(const char *msg, return strbuf_detach(&val, NULL); } +static void print_signature(const char *signature, const char *object_hash) +{ + if (!signature) + return; + + printf("gpgsig %s %s\ndata %u\n%s", + object_hash, + get_signature_format(signature), + (unsigned)strlen(signature), + signature); +} + +static void warn_on_extra_sig(const char **pos, struct commit *commit, int is_sha1) +{ + const char *header = is_sha1 ? "gpgsig" : "gpgsig-sha256"; + const char *extra_sig = find_commit_multiline_header(*pos + 1, header, pos); + if (extra_sig) { + const char *hash = is_sha1 ? "SHA-1" : "SHA-256"; + warning("more than one %s signature found on commit %s, using only the first one", + hash, oid_to_hex(&commit->object.oid)); + free((char *)extra_sig); + } +} + static void handle_commit(struct commit *commit, struct rev_info *rev, struct string_list *paths_of_changed_objects) { @@ -660,7 +685,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, const char *author, *author_end, *committer, *committer_end; const char *encoding = NULL; size_t encoding_len; - const char *signature_alg = NULL, *signature = NULL; + const char *sig_sha1 = NULL; + const char *sig_sha256 = NULL; const char *message; char *reencoded = NULL; struct commit_list *p; @@ -700,10 +726,28 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, } if (*commit_buffer_cursor == '\n') { - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) - signature_alg = "sha1"; - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) - signature_alg = "sha256"; + const char *sig_cursor = commit_buffer_cursor; + const char *after_sha1 = commit_buffer_cursor; + const char *after_sha256 = commit_buffer_cursor; + + /* + * Find the first signature for each hash algorithm. + * The searches must start from the same position. + */ + sig_sha1 = find_commit_multiline_header(sig_cursor + 1, + "gpgsig", + &after_sha1); + sig_sha256 = find_commit_multiline_header(sig_cursor + 1, + "gpgsig-sha256", + &after_sha256); + + /* Warn on any additional signatures, as they will be ignored. */ + if (sig_sha1) + warn_on_extra_sig(&after_sha1, commit, 1); + if (sig_sha256) + warn_on_extra_sig(&after_sha256, commit, 0); + + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; } message = strstr(commit_buffer_cursor, "\n\n"); @@ -769,7 +813,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, printf("%.*s\n%.*s\n", (int)(author_end - author), author, (int)(committer_end - committer), committer); - if (signature) { + if (sig_sha1 || sig_sha256) { switch (signed_commit_mode) { case SIGN_ABORT: die("encountered signed commit %s; use " @@ -780,19 +824,18 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_VERBATIM: - printf("gpgsig %s\ndata %u\n%s", - signature_alg, - (unsigned)strlen(signature), - signature); + print_signature(sig_sha1, "sha1"); + print_signature(sig_sha256, "sha256"); break; case SIGN_WARN_STRIP: - warning("stripping signature from commit %s", + warning("stripping signature(s) from commit %s", oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_STRIP: break; } - free((char *)signature); + free((char *)sig_sha1); + free((char *)sig_sha256); } if (!reencoded && encoding) printf("encoding %.*s\n", (int)encoding_len, encoding); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index b2839c5f43..48ce8ebb77 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -29,6 +29,7 @@ #include "commit-reach.h" #include "khash.h" #include "date.h" +#include "gpg-interface.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -2718,15 +2719,86 @@ static struct hash_list *parse_merge(unsigned int *count) return list; } +struct signature_data { + char *hash_algo; /* "sha1" or "sha256" */ + char *sig_format; /* "openpgp", "x509", "ssh", "unknown" */ + struct strbuf data; /* The actual signature data */ +}; + +static void parse_one_signature(struct signature_data *sig, const char *v) +{ + char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */ + char *space = strchr(args, ' '); + + if (!space) + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " + "got 'gpgsig %s'", args); + *space++ = '\0'; + + sig->hash_algo = args; + sig->sig_format = space; + + /* Remove any trailing newline from format */ + space = strchr(sig->sig_format, '\n'); + if (space) + *space = '\0'; + + /* Validate hash algorithm */ + if (strcmp(sig->hash_algo, "sha1") && + strcmp(sig->hash_algo, "sha256")) + die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo); + + /* Validate signature format */ + if (!valid_signature_format(sig->sig_format)) + die("Invalid signature format in gpgsig: '%s'", sig->sig_format); + if (!strcmp(sig->sig_format, "unknown")) + warning("'unknown' signature format in gpgsig"); + + /* Read signature data */ + read_next_command(); + parse_data(&sig->data, 0, NULL); +} + +static void add_gpgsig_to_commit(struct strbuf *commit_data, + const char *header, + struct signature_data *sig) +{ + struct string_list siglines = STRING_LIST_INIT_NODUP; + + if (!sig->hash_algo) + return; + + strbuf_addstr(commit_data, header); + string_list_split_in_place(&siglines, sig->data.buf, "\n", -1); + strbuf_add_separated_string_list(commit_data, "\n ", &siglines); + strbuf_addch(commit_data, '\n'); + string_list_clear(&siglines, 1); + strbuf_release(&sig->data); + free(sig->hash_algo); +} + +static void store_signature(struct signature_data *stored_sig, + struct signature_data *new_sig, + const char *hash_type) +{ + if (stored_sig->hash_algo) { + warning("Multiple %s signatures found, ignoring additional signature", + hash_type); + strbuf_release(&new_sig->data); + free(new_sig->hash_algo); + } else { + *stored_sig = *new_sig; + } +} + static void parse_new_commit(const char *arg) { - static struct strbuf sig = STRBUF_INIT; static struct strbuf msg = STRBUF_INIT; - struct string_list siglines = STRING_LIST_INIT_NODUP; + struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT }; + struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT }; struct branch *b; char *author = NULL; char *committer = NULL; - char *sig_alg = NULL; char *encoding = NULL; struct hash_list *merge_list = NULL; unsigned int merge_count; @@ -2750,13 +2822,23 @@ static void parse_new_commit(const char *arg) } if (!committer) die("Expected committer but didn't get one"); - if (skip_prefix(command_buf.buf, "gpgsig ", &v)) { - sig_alg = xstrdup(v); - read_next_command(); - parse_data(&sig, 0, NULL); + + /* Process signatures (up to 2: one "sha1" and one "sha256") */ + while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; + + parse_one_signature(&sig, v); + + if (!strcmp(sig.hash_algo, "sha1")) + store_signature(&sig_sha1, &sig, "SHA-1"); + else if (!strcmp(sig.hash_algo, "sha256")) + store_signature(&sig_sha256, &sig, "SHA-256"); + else + BUG("parse_one_signature() returned unknown hash algo"); + read_next_command(); - } else - strbuf_setlen(&sig, 0); + } + if (skip_prefix(command_buf.buf, "encoding ", &v)) { encoding = xstrdup(v); read_next_command(); @@ -2830,23 +2912,14 @@ static void parse_new_commit(const char *arg) strbuf_addf(&new_data, "encoding %s\n", encoding); - if (sig_alg) { - if (!strcmp(sig_alg, "sha1")) - strbuf_addstr(&new_data, "gpgsig "); - else if (!strcmp(sig_alg, "sha256")) - strbuf_addstr(&new_data, "gpgsig-sha256 "); - else - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); - string_list_split_in_place(&siglines, sig.buf, "\n", -1); - strbuf_add_separated_string_list(&new_data, "\n ", &siglines); - strbuf_addch(&new_data, '\n'); - } + + add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1); + add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256); + strbuf_addch(&new_data, '\n'); strbuf_addbuf(&new_data, &msg); - string_list_clear(&siglines, 1); free(author); free(committer); - free(sig_alg); free(encoding); if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark)) diff --git a/gpg-interface.c b/gpg-interface.c index 0896458de5..6f2d87475f 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -144,6 +144,18 @@ static struct gpg_format *get_format_by_sig(const char *sig) return NULL; } +const char *get_signature_format(const char *buf) +{ + struct gpg_format *format = get_format_by_sig(buf); + return format ? format->name : "unknown"; +} + +int valid_signature_format(const char *format) +{ + return (!!get_format_by_name(format) || + !strcmp(format, "unknown")); +} + void signature_check_clear(struct signature_check *sigc) { FREE_AND_NULL(sigc->payload); diff --git a/gpg-interface.h b/gpg-interface.h index e09f12e8d0..60ddf8bbfa 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -47,6 +47,18 @@ struct signature_check { void signature_check_clear(struct signature_check *sigc); +/* + * Return the format of the signature (like "openpgp", "x509", "ssh" + * or "unknown"). + */ +const char *get_signature_format(const char *buf); + +/* + * Is the signature format valid (like "openpgp", "x509", "ssh" or + * "unknown") + */ +int valid_signature_format(const char *format); + /* * Look at a GPG signed tag object. If such a signature exists, store it in * signature and the signed content in payload. Return 1 if a signature was diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 76619765fc..74dd23cb77 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -314,7 +314,7 @@ test_expect_success GPG 'signed-commits=abort' ' test_expect_success GPG 'signed-commits=verbatim' ' git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig sha(1|256) openpgp" output && grep "encoding ISO-8859-1" output && ( cd new && @@ -328,7 +328,7 @@ test_expect_success GPG 'signed-commits=verbatim' ' test_expect_success GPG 'signed-commits=warn-verbatim' ' git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig sha(1|256) openpgp" output && grep "encoding ISO-8859-1" output && test -s err && ( @@ -369,6 +369,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' ' +test_expect_success GPGSM 'setup X.509 signed commit' ' + + git checkout -b x509-signing main && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + echo "X.509 content" >file && + git add file && + git commit -S -m "X.509 signed commit" && + X509_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSM 'round-trip X.509 signed commit' ' + + git fast-export --signed-commits=verbatim x509-signing >output && + grep "^gpgsig sha1 x509" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/x509-signing >actual && + grep "^gpgsig " actual && + IMPORTED=$(git rev-parse refs/heads/x509-signing) && + test $X509_COMMIT = $IMPORTED + ) <output + +' + +test_expect_success GPGSSH 'setup SSH signed commit' ' + + git checkout -b ssh-signing main && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + echo "SSH content" >file && + git add file && + git commit -S -m "SSH signed commit" && + SSH_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSSH 'round-trip SSH signed commit' ' + + git fast-export --signed-commits=verbatim ssh-signing >output && + grep "^gpgsig sha1 ssh" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/ssh-signing >actual && + grep "^gpgsig " actual && + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && + test $SSH_COMMIT = $IMPORTED + ) <output + +' + test_expect_success 'setup submodule' ' test_config_global protocol.file.allow always && -- 2.50.0.2.g875523421d ^ permalink raw reply related [flat|nested] 65+ messages in thread
* [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-18 15:18 ` [PATCH v3] fast-(import|export): improve on commit signature output format Christian Couder @ 2025-06-19 13:36 ` Christian Couder 2025-06-19 14:55 ` Junio C Hamano ` (3 more replies) 0 siblings, 4 replies; 65+ messages in thread From: Christian Couder @ 2025-06-19 13:36 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for signed-commits, 2025-03-10), added support for signed commits to fast-export and fast-import. When a signed commit is processed, fast-export can output either "gpgsig sha1" or "gpgsig sha256" depending on whether the signed commit uses the SHA-1 or SHA-256 Git object format. However, this implementation has a number of limitations: - the output format was not properly described in the documentation, - the output format is not very informative as it doesn't even say if the signature is an OpenPGP, an SSH, or an X509 signature, - the implementation doesn't support having both one signature on the SHA-1 object and one on the SHA-256 object. Let's improve on these limitations by improving fast-export and fast-import so that: - both one signature on the SHA-1 object and one on the SHA-256 object can be exported and imported, - if there is more than one signature on the SHA-1 object or on the SHA-256 object, a warning is emitted, - the output format is "gpgsig <git-hash-algo> <signature-format>", where <git-hash-algo> is the Git object format as before, and <signature-format> is the signature type ("openpgp", "x509", "ssh" or "unknown", - the output is properly documented. Note that it could be even better to be able to export and import more than one signature on the SHA-1 object and on the SHA-256 object, but other parts of Git don't handle that well for now, so this is left for future improvements. Helped-by: brian m. carlson <sandals@crustytoothpaste.net> Helped-by: Elijah Newren <newren@gmail.com> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- This v4 is just about fixing a few bugs in the tests using the SHA-256 object format compared to the v3. (I had issues with CI tests on v3, so I sent it without waiting for the results.) The v1 of this patch series simply replaced the generic "sha1" or "sha256" in the 'gpgsig' command that `git fast-export` produces and `git fast-import` consumes with the signature format (e.g., openpgp, ssh). In the v2, a different approach was tried in the direction of possibly making `git fast-export` check signatures to be able to get more information from them. In this v4 and in v3, the approach is more similar to v1, but takes into account what was learned from the previous approaches. The signature format (e.g., openpgp, ssh) is added to the 'gpgsig' command, while both a SHA-1 and a SHA-256 signature from the same commit can be processed and a warning are emitted for each additional signature with the same hash type. There are no tests in this v4 and in v3 with both a SHA-1 and a SHA-256 signature on the same commit though, as I am not sure yet how to best generate a commit with such signatures. Suggestions welcome! Thanks to brian, Elijah and Junio who commented on the v1 and v2. Range-diff with v3: 1: d28cf238fb ! 1: b3d8b13c0e fast-(import|export): improve on commit signature output format @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' +test_expect_success GPGSM 'round-trip X.509 signed commit' ' + + git fast-export --signed-commits=verbatim x509-signing >output && -+ grep "^gpgsig sha1 x509" output && ++ test_grep -E "^gpgsig sha(1|256) x509" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/x509-signing >actual && -+ grep "^gpgsig " actual && ++ grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/x509-signing) && + test $X509_COMMIT = $IMPORTED + ) <output @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' +test_expect_success GPGSSH 'round-trip SSH signed commit' ' + + git fast-export --signed-commits=verbatim ssh-signing >output && -+ grep "^gpgsig sha1 ssh" output && ++ test_grep -E "^gpgsig sha(1|256) ssh" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/ssh-signing >actual && -+ grep "^gpgsig " actual && ++ grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && + test $SSH_COMMIT = $IMPORTED + ) <output CI tests: They have all passed: https://github.com/chriscool/git/actions/runs/15758360209 Documentation/git-fast-export.adoc | 17 +++++ Documentation/git-fast-import.adoc | 31 ++++++-- builtin/fast-export.c | 67 ++++++++++++++--- builtin/fast-import.c | 117 +++++++++++++++++++++++------ gpg-interface.c | 12 +++ gpg-interface.h | 12 +++ t/t9350-fast-export.sh | 60 ++++++++++++++- 7 files changed, 274 insertions(+), 42 deletions(-) diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 43bbb4f63c..64198f2186 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -50,6 +50,23 @@ resulting tag will have an invalid signature. is the same as how earlier versions of this command without this option behaved. + +When exported, a signature starts with: ++ +gpgsig <git-hash-algo> <signature-format> ++ +where <git-hash-algo> is the Git object hash so either "sha1" or +"sha256", and <signature-format> is the signature type, so "openpgp", +"x509", "ssh" or "unknown". ++ +For example, an OpenPGP signature on a SHA-1 commit starts with +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit +starts with `gpgsig sha256 ssh`. ++ +Currently for a given commit, at most one signature for the SHA-1 +object and one signature for the SHA-256 object are exported, each +with their respective <git-hash-algo> identifier. A warning is +emitted for each additional signature found. ++ NOTE: This is highly experimental and the format of the data stream may change in the future without compatibility guarantees. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 250d866652..db5e5c8da5 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -445,7 +445,7 @@ one). original-oid? ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? 'committer' (SP <name>)? SP LT <email> GT SP <when> LF - ('gpgsig' SP <alg> LF data)? + ('gpgsig' SP <algo> SP <format> LF data)? ('encoding' SP <encoding> LF)? data ('from' SP <commit-ish> LF)? @@ -518,13 +518,32 @@ their syntax. ^^^^^^^^ The optional `gpgsig` command is used to include a PGP/GPG signature -that signs the commit data. +or other cryptographic signature that signs the commit data. -Here <alg> specifies which hashing algorithm is used for this -signature, either `sha1` or `sha256`. +.... + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF + data +.... + +The `gpgsig` command takes two arguments: + +* `<git-hash-algo>` specifies which Git object format this signature + applies to, either `sha1` or `sha256`. + +* `<signature-format>` specifies the type of signature, such as + `openpgp`, `x509`, `ssh`, or `unknown`. + +A commit may have at most one signature for the SHA-1 object format +(stored in the "gpgsig" header) and one for the SHA-256 object format +(stored in the "gpgsig-sha256" header). + +See below for a detailed description of the `data` command which +contains the raw signature data. + +Signatures are not yet checked in the current implementation though. -NOTE: This is highly experimental and the format of the data stream may -change in the future without compatibility guarantees. +NOTE: This is highly experimental and the format of the `gpgsig` +command may change in the future without compatibility guarantees. `encoding` ^^^^^^^^^^ diff --git a/builtin/fast-export.c b/builtin/fast-export.c index fcf6b00d5f..332c036ee4 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -29,6 +29,7 @@ #include "quote.h" #include "remote.h" #include "blob.h" +#include "gpg-interface.h" static const char *const fast_export_usage[] = { N_("git fast-export [<rev-list-opts>]"), @@ -652,6 +653,30 @@ static const char *find_commit_multiline_header(const char *msg, return strbuf_detach(&val, NULL); } +static void print_signature(const char *signature, const char *object_hash) +{ + if (!signature) + return; + + printf("gpgsig %s %s\ndata %u\n%s", + object_hash, + get_signature_format(signature), + (unsigned)strlen(signature), + signature); +} + +static void warn_on_extra_sig(const char **pos, struct commit *commit, int is_sha1) +{ + const char *header = is_sha1 ? "gpgsig" : "gpgsig-sha256"; + const char *extra_sig = find_commit_multiline_header(*pos + 1, header, pos); + if (extra_sig) { + const char *hash = is_sha1 ? "SHA-1" : "SHA-256"; + warning("more than one %s signature found on commit %s, using only the first one", + hash, oid_to_hex(&commit->object.oid)); + free((char *)extra_sig); + } +} + static void handle_commit(struct commit *commit, struct rev_info *rev, struct string_list *paths_of_changed_objects) { @@ -660,7 +685,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, const char *author, *author_end, *committer, *committer_end; const char *encoding = NULL; size_t encoding_len; - const char *signature_alg = NULL, *signature = NULL; + const char *sig_sha1 = NULL; + const char *sig_sha256 = NULL; const char *message; char *reencoded = NULL; struct commit_list *p; @@ -700,10 +726,28 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, } if (*commit_buffer_cursor == '\n') { - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) - signature_alg = "sha1"; - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) - signature_alg = "sha256"; + const char *sig_cursor = commit_buffer_cursor; + const char *after_sha1 = commit_buffer_cursor; + const char *after_sha256 = commit_buffer_cursor; + + /* + * Find the first signature for each hash algorithm. + * The searches must start from the same position. + */ + sig_sha1 = find_commit_multiline_header(sig_cursor + 1, + "gpgsig", + &after_sha1); + sig_sha256 = find_commit_multiline_header(sig_cursor + 1, + "gpgsig-sha256", + &after_sha256); + + /* Warn on any additional signatures, as they will be ignored. */ + if (sig_sha1) + warn_on_extra_sig(&after_sha1, commit, 1); + if (sig_sha256) + warn_on_extra_sig(&after_sha256, commit, 0); + + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; } message = strstr(commit_buffer_cursor, "\n\n"); @@ -769,7 +813,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, printf("%.*s\n%.*s\n", (int)(author_end - author), author, (int)(committer_end - committer), committer); - if (signature) { + if (sig_sha1 || sig_sha256) { switch (signed_commit_mode) { case SIGN_ABORT: die("encountered signed commit %s; use " @@ -780,19 +824,18 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_VERBATIM: - printf("gpgsig %s\ndata %u\n%s", - signature_alg, - (unsigned)strlen(signature), - signature); + print_signature(sig_sha1, "sha1"); + print_signature(sig_sha256, "sha256"); break; case SIGN_WARN_STRIP: - warning("stripping signature from commit %s", + warning("stripping signature(s) from commit %s", oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_STRIP: break; } - free((char *)signature); + free((char *)sig_sha1); + free((char *)sig_sha256); } if (!reencoded && encoding) printf("encoding %.*s\n", (int)encoding_len, encoding); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index b2839c5f43..48ce8ebb77 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -29,6 +29,7 @@ #include "commit-reach.h" #include "khash.h" #include "date.h" +#include "gpg-interface.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -2718,15 +2719,86 @@ static struct hash_list *parse_merge(unsigned int *count) return list; } +struct signature_data { + char *hash_algo; /* "sha1" or "sha256" */ + char *sig_format; /* "openpgp", "x509", "ssh", "unknown" */ + struct strbuf data; /* The actual signature data */ +}; + +static void parse_one_signature(struct signature_data *sig, const char *v) +{ + char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */ + char *space = strchr(args, ' '); + + if (!space) + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " + "got 'gpgsig %s'", args); + *space++ = '\0'; + + sig->hash_algo = args; + sig->sig_format = space; + + /* Remove any trailing newline from format */ + space = strchr(sig->sig_format, '\n'); + if (space) + *space = '\0'; + + /* Validate hash algorithm */ + if (strcmp(sig->hash_algo, "sha1") && + strcmp(sig->hash_algo, "sha256")) + die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo); + + /* Validate signature format */ + if (!valid_signature_format(sig->sig_format)) + die("Invalid signature format in gpgsig: '%s'", sig->sig_format); + if (!strcmp(sig->sig_format, "unknown")) + warning("'unknown' signature format in gpgsig"); + + /* Read signature data */ + read_next_command(); + parse_data(&sig->data, 0, NULL); +} + +static void add_gpgsig_to_commit(struct strbuf *commit_data, + const char *header, + struct signature_data *sig) +{ + struct string_list siglines = STRING_LIST_INIT_NODUP; + + if (!sig->hash_algo) + return; + + strbuf_addstr(commit_data, header); + string_list_split_in_place(&siglines, sig->data.buf, "\n", -1); + strbuf_add_separated_string_list(commit_data, "\n ", &siglines); + strbuf_addch(commit_data, '\n'); + string_list_clear(&siglines, 1); + strbuf_release(&sig->data); + free(sig->hash_algo); +} + +static void store_signature(struct signature_data *stored_sig, + struct signature_data *new_sig, + const char *hash_type) +{ + if (stored_sig->hash_algo) { + warning("Multiple %s signatures found, ignoring additional signature", + hash_type); + strbuf_release(&new_sig->data); + free(new_sig->hash_algo); + } else { + *stored_sig = *new_sig; + } +} + static void parse_new_commit(const char *arg) { - static struct strbuf sig = STRBUF_INIT; static struct strbuf msg = STRBUF_INIT; - struct string_list siglines = STRING_LIST_INIT_NODUP; + struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT }; + struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT }; struct branch *b; char *author = NULL; char *committer = NULL; - char *sig_alg = NULL; char *encoding = NULL; struct hash_list *merge_list = NULL; unsigned int merge_count; @@ -2750,13 +2822,23 @@ static void parse_new_commit(const char *arg) } if (!committer) die("Expected committer but didn't get one"); - if (skip_prefix(command_buf.buf, "gpgsig ", &v)) { - sig_alg = xstrdup(v); - read_next_command(); - parse_data(&sig, 0, NULL); + + /* Process signatures (up to 2: one "sha1" and one "sha256") */ + while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; + + parse_one_signature(&sig, v); + + if (!strcmp(sig.hash_algo, "sha1")) + store_signature(&sig_sha1, &sig, "SHA-1"); + else if (!strcmp(sig.hash_algo, "sha256")) + store_signature(&sig_sha256, &sig, "SHA-256"); + else + BUG("parse_one_signature() returned unknown hash algo"); + read_next_command(); - } else - strbuf_setlen(&sig, 0); + } + if (skip_prefix(command_buf.buf, "encoding ", &v)) { encoding = xstrdup(v); read_next_command(); @@ -2830,23 +2912,14 @@ static void parse_new_commit(const char *arg) strbuf_addf(&new_data, "encoding %s\n", encoding); - if (sig_alg) { - if (!strcmp(sig_alg, "sha1")) - strbuf_addstr(&new_data, "gpgsig "); - else if (!strcmp(sig_alg, "sha256")) - strbuf_addstr(&new_data, "gpgsig-sha256 "); - else - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); - string_list_split_in_place(&siglines, sig.buf, "\n", -1); - strbuf_add_separated_string_list(&new_data, "\n ", &siglines); - strbuf_addch(&new_data, '\n'); - } + + add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1); + add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256); + strbuf_addch(&new_data, '\n'); strbuf_addbuf(&new_data, &msg); - string_list_clear(&siglines, 1); free(author); free(committer); - free(sig_alg); free(encoding); if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark)) diff --git a/gpg-interface.c b/gpg-interface.c index 0896458de5..6f2d87475f 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -144,6 +144,18 @@ static struct gpg_format *get_format_by_sig(const char *sig) return NULL; } +const char *get_signature_format(const char *buf) +{ + struct gpg_format *format = get_format_by_sig(buf); + return format ? format->name : "unknown"; +} + +int valid_signature_format(const char *format) +{ + return (!!get_format_by_name(format) || + !strcmp(format, "unknown")); +} + void signature_check_clear(struct signature_check *sigc) { FREE_AND_NULL(sigc->payload); diff --git a/gpg-interface.h b/gpg-interface.h index e09f12e8d0..60ddf8bbfa 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -47,6 +47,18 @@ struct signature_check { void signature_check_clear(struct signature_check *sigc); +/* + * Return the format of the signature (like "openpgp", "x509", "ssh" + * or "unknown"). + */ +const char *get_signature_format(const char *buf); + +/* + * Is the signature format valid (like "openpgp", "x509", "ssh" or + * "unknown") + */ +int valid_signature_format(const char *format); + /* * Look at a GPG signed tag object. If such a signature exists, store it in * signature and the signed content in payload. Return 1 if a signature was diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 76619765fc..3559a33c72 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -314,7 +314,7 @@ test_expect_success GPG 'signed-commits=abort' ' test_expect_success GPG 'signed-commits=verbatim' ' git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig sha(1|256) openpgp" output && grep "encoding ISO-8859-1" output && ( cd new && @@ -328,7 +328,7 @@ test_expect_success GPG 'signed-commits=verbatim' ' test_expect_success GPG 'signed-commits=warn-verbatim' ' git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig sha(1|256) openpgp" output && grep "encoding ISO-8859-1" output && test -s err && ( @@ -369,6 +369,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' ' +test_expect_success GPGSM 'setup X.509 signed commit' ' + + git checkout -b x509-signing main && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + echo "X.509 content" >file && + git add file && + git commit -S -m "X.509 signed commit" && + X509_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSM 'round-trip X.509 signed commit' ' + + git fast-export --signed-commits=verbatim x509-signing >output && + test_grep -E "^gpgsig sha(1|256) x509" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/x509-signing >actual && + grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/x509-signing) && + test $X509_COMMIT = $IMPORTED + ) <output + +' + +test_expect_success GPGSSH 'setup SSH signed commit' ' + + git checkout -b ssh-signing main && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + echo "SSH content" >file && + git add file && + git commit -S -m "SSH signed commit" && + SSH_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSSH 'round-trip SSH signed commit' ' + + git fast-export --signed-commits=verbatim ssh-signing >output && + test_grep -E "^gpgsig sha(1|256) ssh" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/ssh-signing >actual && + grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && + test $SSH_COMMIT = $IMPORTED + ) <output + +' + test_expect_success 'setup submodule' ' test_config_global protocol.file.allow always && -- 2.50.0.2.g875523421d ^ permalink raw reply related [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-19 13:36 ` [PATCH v4] " Christian Couder @ 2025-06-19 14:55 ` Junio C Hamano 2025-07-08 9:16 ` Christian Couder 2025-06-19 21:44 ` Elijah Newren ` (2 subsequent siblings) 3 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-06-19 14:55 UTC (permalink / raw) To: Christian Couder Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > This v4 is just about fixing a few bugs in the tests using the SHA-256 > object format compared to the v3. (I had issues with CI tests on v3, > so I sent it without waiting for the results.) Thanks. I am not sure if "I am happy is either 1 or 256" is what you really want, though. The test presumably knows what algorithm is being used during its run, so wouldn't you want to say more like "I know I used sha256, and I expect seeing sha256, ah, I see sha256 and even better I see no sha1, so I am very happy"? > There are no tests in this v4 and in v3 with both a SHA-1 and a > SHA-256 signature on the same commit though, as I am not sure yet how > to best generate a commit with such signatures. Suggestions welcome! Good point to fill potential gaps. If we had such a commit, then would these tests say "I know I want both 1 and 256, and I do see one instance each of 1 and 256, so I am happy"? ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-19 14:55 ` Junio C Hamano @ 2025-07-08 9:16 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-08 9:16 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Thu, Jun 19, 2025 at 4:55 PM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > > This v4 is just about fixing a few bugs in the tests using the SHA-256 > > object format compared to the v3. (I had issues with CI tests on v3, > > so I sent it without waiting for the results.) > > Thanks. > > I am not sure if "I am happy is either 1 or 256" is what you really > want, though. The test presumably knows what algorithm is being > used during its run, so wouldn't you want to say more like "I know I > used sha256, and I expect seeing sha256, ah, I see sha256 and even > better I see no sha1, so I am very happy"? Yeah, I agree it's better to have tests say things like "I know I used sha256, and I expect to see sha256". So in v5 tests now use: test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output as "$GIT_DEFAULT_HASH" should be either "sha1" or "sha256" depending on the current hash. I am not sure "and even better I see no sha1" is worth it then though, so I haven't added that. > > There are no tests in this v4 and in v3 with both a SHA-1 and a > > SHA-256 signature on the same commit though, as I am not sure yet how > > to best generate a commit with such signatures. Suggestions welcome! > > Good point to fill potential gaps. If we had such a commit, then > would these tests say "I know I want both 1 and 256, and I do see > one instance each of 1 and 256, so I am happy"? There is a test with such a commit in the v5 I am about to send and yeah it checks that there is one instance of 1 and 256. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-19 13:36 ` [PATCH v4] " Christian Couder 2025-06-19 14:55 ` Junio C Hamano @ 2025-06-19 21:44 ` Elijah Newren 2025-06-20 16:12 ` Christian Couder 2025-07-07 22:58 ` Junio C Hamano 2025-07-08 9:17 ` [PATCH v5] " Christian Couder 3 siblings, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-06-19 21:44 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Thu, Jun 19, 2025 at 6:36 AM Christian Couder <christian.couder@gmail.com> wrote: > > A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for > signed-commits, 2025-03-10), added support for signed commits to > fast-export and fast-import. > > When a signed commit is processed, fast-export can output either > "gpgsig sha1" or "gpgsig sha256" depending on whether the signed > commit uses the SHA-1 or SHA-256 Git object format. > > However, this implementation has a number of limitations: > > - the output format was not properly described in the documentation, Thanks for working on fixing this. > - the output format is not very informative as it doesn't even say > if the signature is an OpenPGP, an SSH, or an X509 signature, Why would it need to say what type of signature it is? Don't the ascii armor lines have e.g. "----BEGIN PGP SIGNATURE----" and "----END PGP SIGNATURE----" around it, which fast-import can read as well as fast-export? Is the idea that we strip those lines and now need to replace the information we lost? > - the implementation doesn't support having both one signature on > the SHA-1 object and one on the SHA-256 object. Not sure I understand this; more questions around this later. > Let's improve on these limitations by improving fast-export and > fast-import so that: > > - both one signature on the SHA-1 object and one on the SHA-256 > object can be exported and imported, > - if there is more than one signature on the SHA-1 object or on > the SHA-256 object, a warning is emitted, > - the output format is "gpgsig <git-hash-algo> <signature-format>", > where <git-hash-algo> is the Git object format as before, and > <signature-format> is the signature type ("openpgp", "x509", > "ssh" or "unknown", > - the output is properly documented. Perhaps this was discussed in an earlier round, but if so I either forgot or missed it. What value does <git-hash-algo> and <signature-format> provide? How are they intended to be used? Is the <signature-format> merely self-inflicted pain from stripping the ascii armor lines? If so, would it make more sense to just include those armor lines as-is in the fast-export stream and let fast-import process it? Then we wouldn't have to introduce all the outputting and parsing of this new <signature-format> field and worry about the new special "unknown" status. Is the <git-hash-algo> due to the fact that we have separate `gpgsig` and `gpgsig-sha256` commit headers and we want to use that information to avoid writing these headers to the wrong-sized objects (and/or to avoid checking whether the signature is valid on the wrong-sized objects)? If so, could that be spelled out in the docs as well, especially since it appears that the intent of these headers is left unimplemented due to not changing fast-import to do anything with them? And if <git-hash-algo>'s purpose is to ensure they are only used when writing same-sized object as what was exported, then...isn't that a bug? This series was started because people wanted to be able to do things like keeping signature even when they are no longer valid or resigning commits that have a no longer commit signature (among other uses), but that would mean that if someone exports a sha1 repository and imports it as sha256, we don't want to ignore the fact that the sha1 commit was signed for those usecases. If, however, the <git-hash-algo>'s purpose is merely as a performance optimization that fast-import can employ in the cases where it checks for signatures being valid, so it can avoid checking when it know the hash size isn't even the same, then it could make sense. But, short of that performance optimization, it's unclear to me whether we'd lose anything by simply exporting "gpgsig <contents of signature header from original object, including the armor lines>", and dropping the <git-hash-algo> and <signature-format> lines entirely. Am I missing something? (I may well be; I don't know much about signing stuff beyond the very basics, and don't mess with signed commits or tags much myself.) [...] > There are no tests in this v4 and in v3 with both a SHA-1 and a > SHA-256 signature on the same commit though, as I am not sure yet how > to best generate a commit with such signatures. Suggestions welcome! If no suggestions are forthcoming, it feels odd to implement this with no tests. Would it make sense to leave it out until we know how to test it? (More questions on this below...) [...] > diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc > index 43bbb4f63c..64198f2186 100644 > --- a/Documentation/git-fast-export.adoc > +++ b/Documentation/git-fast-export.adoc > @@ -50,6 +50,23 @@ resulting tag will have an invalid signature. > is the same as how earlier versions of this command without > this option behaved. > + > +When exported, a signature starts with: > ++ > +gpgsig <git-hash-algo> <signature-format> > ++ > +where <git-hash-algo> is the Git object hash so either "sha1" or > +"sha256", and <signature-format> is the signature type, so "openpgp", > +"x509", "ssh" or "unknown". > ++ > +For example, an OpenPGP signature on a SHA-1 commit starts with > +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit > +starts with `gpgsig sha256 ssh`. I had a number of comments/questions on this above already. > ++ > +Currently for a given commit, at most one signature for the SHA-1 > +object and one signature for the SHA-256 object are exported, each > +with their respective <git-hash-algo> identifier. Wait..does this mean fast-export is obligated to walk over both all sha1 commits and all "equivalent" sha256 commits when exporting a repo? I thought most operations on the repo would walk over only one or the other; walking over both seems to be against the spirit of the "fast" in "fast-export". Am I missing something? (Possibly related question: Does "git log" bother walking over both, or does it only walk over one?) Even if this really is wanted by some users, shouldn't they manually request it rather than making exports slow for everyone else by default? > +A warning is > +emitted for each additional signature found. Why? This seems odd to me. Why not merely export them all, and let fast-import throw warnings or errors if it sees more than one and is not yet prepared to handle multiple signatures? > ++ > NOTE: This is highly experimental and the format of the data stream may > change in the future without compatibility guarantees. > > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc > index 250d866652..db5e5c8da5 100644 > --- a/Documentation/git-fast-import.adoc > +++ b/Documentation/git-fast-import.adoc > @@ -445,7 +445,7 @@ one). > original-oid? > ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? > 'committer' (SP <name>)? SP LT <email> GT SP <when> LF > - ('gpgsig' SP <alg> LF data)? > + ('gpgsig' SP <algo> SP <format> LF data)? > ('encoding' SP <encoding> LF)? > data > ('from' SP <commit-ish> LF)? > @@ -518,13 +518,32 @@ their syntax. > ^^^^^^^^ > > The optional `gpgsig` command is used to include a PGP/GPG signature > -that signs the commit data. > +or other cryptographic signature that signs the commit data. Good catch. > > -Here <alg> specifies which hashing algorithm is used for this > -signature, either `sha1` or `sha256`. > +.... > + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF > + data > +.... > + > +The `gpgsig` command takes two arguments: > + > +* `<git-hash-algo>` specifies which Git object format this signature > + applies to, either `sha1` or `sha256`. > + > +* `<signature-format>` specifies the type of signature, such as > + `openpgp`, `x509`, `ssh`, or `unknown`. > + > +A commit may have at most one signature for the SHA-1 object format > +(stored in the "gpgsig" header) and one for the SHA-256 object format > +(stored in the "gpgsig-sha256" header). Why? Does this mean hg-fast-export or hg-fast-import (or a jj-fast-export or jj-fast-import) wouldn't be allowed to specify multiple signatures? The fast-export and fast-import streams are often used for interoperation with other VCSes, but as far as I can tell, you're encoding a restriction on what's allowed that isn't an actual problem for git but just a not-yet-implemented state. If I've understood correctly that the restriction is merely due to the current implementation, perhaps the wording could be changed to not list it as an encoding restriction, but as a current fast-import limitation? Also, I'm slightly uncomfortable with "the SHA-1 object format" and "the SHA-256 object format" because of the fact that these tools are used for interoperability with similarly-named tools from other VCSes. I think it'd be better to just treat them equally as "here was/were some signature(s) on the object in the original repo; importers can choose what to do with them". > + > +See below for a detailed description of the `data` command which > +contains the raw signature data. > + > +Signatures are not yet checked in the current implementation though. ...but what does happen with those signatures? Dropped? Kept as-is? Can we just spell this out a bit more clearly? e.g. "Signatures are not checked in the current implementation; they are used as-is, which may mean the signatures are invalid in the imported repository." > -NOTE: This is highly experimental and the format of the data stream may > -change in the future without compatibility guarantees. > +NOTE: This is highly experimental and the format of the `gpgsig` > +command may change in the future without compatibility guarantees. Good clarification. I briefly skimmed the implementation and test files, and didn't see any problems...but I think it probably makes more sense to get aligned on the goals of the format and how these fields are meant to be used before diving into those details closer. Thanks for working on this topic. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-19 21:44 ` Elijah Newren @ 2025-06-20 16:12 ` Christian Couder 2025-06-20 19:20 ` Junio C Hamano 2025-06-26 19:11 ` Elijah Newren 0 siblings, 2 replies; 65+ messages in thread From: Christian Couder @ 2025-06-20 16:12 UTC (permalink / raw) To: Elijah Newren Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Thu, Jun 19, 2025 at 11:44 PM Elijah Newren <newren@gmail.com> wrote: > > On Thu, Jun 19, 2025 at 6:36 AM Christian Couder > <christian.couder@gmail.com> wrote: > > However, this implementation has a number of limitations: > > > > - the output format was not properly described in the documentation, > > Thanks for working on fixing this. > > > - the output format is not very informative as it doesn't even say > > if the signature is an OpenPGP, an SSH, or an X509 signature, > > Why would it need to say what type of signature it is? Don't the > ascii armor lines have e.g. "----BEGIN PGP SIGNATURE----" and "----END > PGP SIGNATURE----" around it, which fast-import can read as well as > fast-export? Is the idea that we strip those lines and now need to > replace the information we lost? In https://lore.kernel.org/git/aAq1nvcPRlIPal5l@tapette.crustytoothpaste.net/ brian said: "These should be separate fields: one for the hash algorithm and one for the protocol. Alternatively, we can just keep the hash algorithm field and parse the protocol by reading the first line, which will differ for different protocols." It would have been nice if you had then said that you prefer not to have the protocol. My opinion was that it was better for tools processing fast-export output to have the protocol as they have to parse the "gppsig ..." line anyway. So it should be easier for them than to parse the ascii armor lines. > > - the implementation doesn't support having both one signature on > > the SHA-1 object and one on the SHA-256 object. > > Not sure I understand this; more questions around this later. In https://lore.kernel.org/git/aD4i7YhUnT5Kgew-@tapette.crustytoothpaste.net/ brian said: "Actually, what I was saying is that we should have one for the hash algorithm that is used in the Git object. I don't care about the hash algorithm used in OpenPGP, X.509, or OpenSSH (that is, whether it's signed with SHA-512 or SHA-256), but we can have multiple signatures in a single commit such that there's both a SHA-1 signature and a SHA-256 signature." so I implemented the possibility that there's both a SHA-1 signature and a SHA-256 signature in a single commit. If you disagreed with what brian suggested, it would have been nice to reply to brian then. > > Let's improve on these limitations by improving fast-export and > > fast-import so that: > > > > - both one signature on the SHA-1 object and one on the SHA-256 > > object can be exported and imported, > > - if there is more than one signature on the SHA-1 object or on > > the SHA-256 object, a warning is emitted, > > - the output format is "gpgsig <git-hash-algo> <signature-format>", > > where <git-hash-algo> is the Git object format as before, and > > <signature-format> is the signature type ("openpgp", "x509", > > "ssh" or "unknown", > > - the output is properly documented. > > Perhaps this was discussed in an earlier round, but if so I either > forgot or missed it. What value does <git-hash-algo> and > <signature-format> provide? How are they intended to be used? My opinion on this is that in cases like this we might not always know what could be useful for tools or users in general, so it might be better to provide more information that can be easily discarded if not useful rather than not enough information. brian seemed to say that <git-hash-algo> and <signature-format> are important so I just prefered to have both, especially as they are not costly to get. > Is the <signature-format> merely self-inflicted pain from stripping > the ascii armor lines? If so, would it make more sense to just > include those armor lines as-is in the fast-export stream and let > fast-import process it? The ascii armor lines are kept in the fast-export stream, but fast-export's ouput is supposed to be processed somehow sometimes before being fed back to fast-import, so I think we should make it easy for tools or people processing the output to get signature information. > Then we wouldn't have to introduce all the > outputting and parsing of this new <signature-format> field and worry > about the new special "unknown" status. I think it's a trade-off. If there is a consensus that it's very unlikely to help anyone, I am fine with removing it. > Is the <git-hash-algo> due to the fact that we have separate `gpgsig` > and `gpgsig-sha256` commit headers and we want to use that information > to avoid writing these headers to the wrong-sized objects (and/or to > avoid checking whether the signature is valid on the wrong-sized > objects)? Yeah, I think it could help with that, but brian might better answer that. > If so, could that be spelled out in the docs as well, > especially since it appears that the intent of these headers is left > unimplemented due to not changing fast-import to do anything with > them? fast-import puts back the `gpgsig` or `gpgsig-sha256` headers depending on <git-hash-algo>, so it's useful at least for that. I will improve the docs about it. > And if <git-hash-algo>'s purpose is to ensure they are only used when > writing same-sized object as what was exported, then...isn't that a > bug? I am not sure I understand what would be the bug. > This series was started because people wanted to be able to do > things like keeping signature even when they are no longer valid or > resigning commits that have a no longer commit signature (among other > uses), but that would mean that if someone exports a sha1 repository > and imports it as sha256, we don't want to ignore the fact that the > sha1 commit was signed for those usecases. > > If, however, the <git-hash-algo>'s purpose is merely as a performance > optimization that fast-import can employ in the cases where it checks > for signatures being valid, so it can avoid checking when it know the > hash size isn't even the same, then it could make sense. I think it could be used for that, but I'd like brian's opinion about this. > But, short of that performance optimization, it's unclear to me > whether we'd lose anything by simply exporting "gpgsig <contents of > signature header from original object, including the armor lines>", > and dropping the <git-hash-algo> and <signature-format> lines > entirely. Am I missing something? (I may well be; I don't know much > about signing stuff beyond the very basics, and don't mess with signed > commits or tags much myself.) I don't know much about signing either, that's why I have tried to rely on brian's suggestion on this. > [...] > > There are no tests in this v4 and in v3 with both a SHA-1 and a > > SHA-256 signature on the same commit though, as I am not sure yet how > > to best generate a commit with such signatures. Suggestions welcome! > > If no suggestions are forthcoming, it feels odd to implement this with > no tests. Would it make sense to leave it out until we know how to > test it? (More questions on this below...) I had hoped that implementing this and then asking how best to test with both a SHA-1 and a SHA-256 signature on the same commit would be better to move things forward. > > +Currently for a given commit, at most one signature for the SHA-1 > > +object and one signature for the SHA-256 object are exported, each > > +with their respective <git-hash-algo> identifier. > > Wait..does this mean fast-export is obligated to walk over both all > sha1 commits and all "equivalent" sha256 commits when exporting a > repo? I thought most operations on the repo would walk over only one > or the other; walking over both seems to be against the spirit of the > "fast" in "fast-export". Am I missing something? (Possibly related > question: Does "git log" bother walking over both, or does it only > walk over one?) Even if this really is wanted by some users, > shouldn't they manually request it rather than making exports slow for > everyone else by default? No fast-export doesn't walk over both the sha1 commits and all "equivalent" sha256 commits, it can just process at most 2 signatures for a given commit, one with the `gpgsig` header and one with the `gpgsig-sha256` header. I will try to reword the doc to make it clearer. > > +A warning is > > +emitted for each additional signature found. > > Why? This seems odd to me. Why not merely export them all, and let > fast-import throw warnings or errors if it sees more than one and is > not yet prepared to handle multiple signatures? I am not against implementing both export and import support for any number of signatures, but it seems that Git in general doesn't support more than one `gpgsig` and one `gpgsig-sha256` signature well, and that's also what brian suggested supporting. It's also better than just 1 signature max which is the current state. > > -Here <alg> specifies which hashing algorithm is used for this > > -signature, either `sha1` or `sha256`. > > +.... > > + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF > > + data > > +.... > > + > > +The `gpgsig` command takes two arguments: > > + > > +* `<git-hash-algo>` specifies which Git object format this signature > > + applies to, either `sha1` or `sha256`. > > + > > +* `<signature-format>` specifies the type of signature, such as > > + `openpgp`, `x509`, `ssh`, or `unknown`. > > + > > +A commit may have at most one signature for the SHA-1 object format > > +(stored in the "gpgsig" header) and one for the SHA-256 object format > > +(stored in the "gpgsig-sha256" header). > > Why? Does this mean hg-fast-export or hg-fast-import (or a > jj-fast-export or jj-fast-import) wouldn't be allowed to specify > multiple signatures? The fast-export and fast-import streams are > often used for interoperation with other VCSes, but as far as I can > tell, you're encoding a restriction on what's allowed that isn't an > actual problem for git but just a not-yet-implemented state. I would be fine with rewording this to say that the current implementation allows at most one signature for the SHA-1 object format and one for the SHA-256 object format to be imported. But as far as I understand additional signatures on top of that might not be very well supported by Git in general, so maybe I should mention that too? > If I've > understood correctly that the restriction is merely due to the current > implementation, perhaps the wording could be changed to not list it as > an encoding restriction, but as a current fast-import limitation? If that current fast-import limitation was lifted, we should probably still say that additional signatures might not be very well supported by Git in general. > Also, I'm slightly uncomfortable with "the SHA-1 object format" and > "the SHA-256 object format" because of the fact that these tools are > used for interoperability with similarly-named tools from other VCSes. > I think it'd be better to just treat them equally as "here was/were > some signature(s) on the object in the original repo; importers can > choose what to do with them". I don't think giving that information prevents tools from other VCSes from doing what they want with the signatures. > > +See below for a detailed description of the `data` command which > > +contains the raw signature data. > > + > > +Signatures are not yet checked in the current implementation though. > > ...but what does happen with those signatures? Dropped? Kept as-is? This section (about the `gpgsig` command) starts with: "The optional `gpgsig` command is used to include a PGP/GPG signature that signs the commit data." so yeah they are kept as-is. > Can we just spell this out a bit more clearly? e.g. > > "Signatures are not checked in the current implementation; they are > used as-is, which may mean the signatures are invalid in the imported > repository." It feels a bit redundant but, yeah, the part about possibly invalid signatures might clarify things, so I am fine with spelling it out like this. Thanks. [...] > I briefly skimmed the implementation and test files, and didn't see > any problems...but I think it probably makes more sense to get aligned > on the goals of the format and how these fields are meant to be used > before diving into those details closer. Yeah, I would have been happy if we could have been aligned with the goals of the format and the fields earlier, but better late than never. > Thanks for working on this topic. Thanks for reviewing. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-20 16:12 ` Christian Couder @ 2025-06-20 19:20 ` Junio C Hamano 2025-07-08 9:16 ` Christian Couder 2025-06-26 19:11 ` Elijah Newren 1 sibling, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-06-20 19:20 UTC (permalink / raw) To: Christian Couder Cc: Elijah Newren, git, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: tldr; 2 brief requests. Please - Be gentle to people and expect that it is normal for them to be off of the list for a few weeks (or even more), not able to give a timely comments; - Fully stand behind your own patch (unless it is an RFC), even if some of the idea came from elsewhere. >> Why would it need to say what type of signature it is? Don't the >> ascii armor lines have e.g. "----BEGIN PGP SIGNATURE----" and "----END >> PGP SIGNATURE----" around it, which fast-import can read as well as >> fast-export? Is the idea that we strip those lines and now need to >> replace the information we lost? > > In https://lore.kernel.org/git/aAq1nvcPRlIPal5l@tapette.crustytoothpaste.net/ > brian said: > > "These should be separate fields: one for the hash algorithm and one for > the protocol. Alternatively, we can just keep the hash algorithm field > and parse the protocol by reading the first line, which will differ for > different protocols." > > It would have been nice if you had then said that you prefer not to > have the protocol. Let's remember to be gentle for those who give varluable feedbacks but may not be always on this list. A late comment on a topic that has not hit 'next' is much better than a late comment after the topic hits 'next', or no comment at all. Also, even if the idea came from somebody else, if you agreed to the idea and made it part of your submission, then it would be better to explain it in your own words, in the most appropriate way to answer the question asked (e.g. the original from Brian and the question by Elijah may have stress on different aspect of the problem). > My opinion was that it was better for tools processing fast-export > output to have the protocol as they have to parse the "gppsig ..." > line anyway. So it should be easier for them than to parse the ascii > armor lines. And if you do not agree with Brian's, perhaps discuss a bit more to (1) either convince yourself that Brian's idea is better and rewrite your code to adopt the idea, or (2) explain the reason why your "the importer reads and parses anyway" is better design and stick to it. > ... > Yeah, I would have been happy if we could have been aligned with the > goals of the format and the fields earlier, but better late than > never. > >> Thanks for working on this topic. > > Thanks for reviewing. Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-20 19:20 ` Junio C Hamano @ 2025-07-08 9:16 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-08 9:16 UTC (permalink / raw) To: Junio C Hamano Cc: Elijah Newren, git, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Fri, Jun 20, 2025 at 9:20 PM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > tldr; 2 brief requests. Please > > - Be gentle to people and expect that it is normal for them to be > off of the list for a few weeks (or even more), not able to give > a timely comments; > > - Fully stand behind your own patch (unless it is an RFC), even if > some of the idea came from elsewhere. Ok I will keep these in mind. > >> Why would it need to say what type of signature it is? Don't the > >> ascii armor lines have e.g. "----BEGIN PGP SIGNATURE----" and "----END > >> PGP SIGNATURE----" around it, which fast-import can read as well as > >> fast-export? Is the idea that we strip those lines and now need to > >> replace the information we lost? > > > > In https://lore.kernel.org/git/aAq1nvcPRlIPal5l@tapette.crustytoothpaste.net/ > > brian said: > > > > "These should be separate fields: one for the hash algorithm and one for > > the protocol. Alternatively, we can just keep the hash algorithm field > > and parse the protocol by reading the first line, which will differ for > > different protocols." > > > > It would have been nice if you had then said that you prefer not to > > have the protocol. > > Let's remember to be gentle for those who give varluable feedbacks > but may not be always on this list. A late comment on a topic that > has not hit 'next' is much better than a late comment after the > topic hits 'next', or no comment at all. I agree that the comment was valuable, sorry if it appeared otherwise. > Also, even if the idea came from somebody else, if you agreed to the > idea and made it part of your submission, then it would be better to > explain it in your own words, in the most appropriate way to answer > the question asked (e.g. the original from Brian and the question by > Elijah may have stress on different aspect of the problem). > > > My opinion was that it was better for tools processing fast-export > > output to have the protocol as they have to parse the "gppsig ..." > > line anyway. So it should be easier for them than to parse the ascii > > armor lines. > > And if you do not agree with Brian's, perhaps discuss a bit more to > (1) either convince yourself that Brian's idea is better and rewrite > your code to adopt the idea, or (2) explain the reason why your "the > importer reads and parses anyway" is better design and stick to it. I actually agreed with brian about "These should be separate fields: one for the hash algorithm and one for the protocol." Anyway it seems that we all agree now on this. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-20 16:12 ` Christian Couder 2025-06-20 19:20 ` Junio C Hamano @ 2025-06-26 19:11 ` Elijah Newren 2025-07-08 9:16 ` Christian Couder 1 sibling, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-06-26 19:11 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Fri, Jun 20, 2025 at 9:12 AM Christian Couder <christian.couder@gmail.com> wrote: > > On Thu, Jun 19, 2025 at 11:44 PM Elijah Newren <newren@gmail.com> wrote: > > > > On Thu, Jun 19, 2025 at 6:36 AM Christian Couder > > <christian.couder@gmail.com> wrote: > > > > However, this implementation has a number of limitations: > > > > > > - the output format was not properly described in the documentation, > > > > Thanks for working on fixing this. > > > > > - the output format is not very informative as it doesn't even say > > > if the signature is an OpenPGP, an SSH, or an X509 signature, > > > > Why would it need to say what type of signature it is? Don't the > > ascii armor lines have e.g. "----BEGIN PGP SIGNATURE----" and "----END > > PGP SIGNATURE----" around it, which fast-import can read as well as > > fast-export? Is the idea that we strip those lines and now need to > > replace the information we lost? > > In https://lore.kernel.org/git/aAq1nvcPRlIPal5l@tapette.crustytoothpaste.net/ > brian said: > > "These should be separate fields: one for the hash algorithm and one for > the protocol. Alternatively, we can just keep the hash algorithm field > and parse the protocol by reading the first line, which will differ for > different protocols." > > It would have been nice if you had then said that you prefer not to > have the protocol. > > My opinion was that it was better for tools processing fast-export > output to have the protocol as they have to parse the "gppsig ..." > line anyway. So it should be easier for them than to parse the ascii > armor lines. > > > > - the implementation doesn't support having both one signature on > > > the SHA-1 object and one on the SHA-256 object. > > > > Not sure I understand this; more questions around this later. > > In https://lore.kernel.org/git/aD4i7YhUnT5Kgew-@tapette.crustytoothpaste.net/ > brian said: > > "Actually, what I was saying is that we should have one for the hash > algorithm that is used in the Git object. I don't care about the hash > algorithm used in OpenPGP, X.509, or OpenSSH (that is, whether it's > signed with SHA-512 or SHA-256), but we can have multiple signatures in > a single commit such that there's both a SHA-1 signature and a SHA-256 > signature." > > so I implemented the possibility that there's both a SHA-1 signature > and a SHA-256 signature in a single commit. > > If you disagreed with what brian suggested, it would have been nice to > reply to brian then. Sorry about that. I hadn't read the whole thread; further, I was previously trying to avoid the details of signatures beyond the basics and hoping to leave those details to others, but between v2 and some words you had in this version plus deciding to just take a closer look while on vacation (as I was last week), I decided to try to look a bit closer. Turns out, though, that despite my attempts to read a bit more documentation and code in the project, I still had a fundamental misunderstanding last week when I was responding. I had been assuming that the gpgsig headers would appear on sha1-commits and gpgsig-sha256 headers would appear on sha256 commits. I didn't understand that both types of headers can appear on both types of commits (so that, for example, gpgsig-sha256 would be use the sha256-commit as the source and sign that data but write the result to a sha1-commit). I talked with brian this morning to ask a few questions and he clarified this for me. (Side note: It might be nice to clarify this in Documentation/technical/hash-function-transition.adoc; that document did not dispel this confusion when I read it last week.) > > > Let's improve on these limitations by improving fast-export and > > > fast-import so that: > > > > > > - both one signature on the SHA-1 object and one on the SHA-256 > > > object can be exported and imported, And the part I didn't understand the first time around is that e.g. the SHA-1 commit can include both the signatures on the SHA-1 object and on the SHA-256 object. (Similarly, the SHA-256 commit can include both signatures as well.) > > > - if there is more than one signature on the SHA-1 object or on > > > the SHA-256 object, a warning is emitted, This is now ambiguous to me. more than one signature using the SHA-1 as source for signing, or more than one signature recorded within the SHA-1 commit? I think you mean the former. > > > - the output format is "gpgsig <git-hash-algo> <signature-format>", > > > where <git-hash-algo> is the Git object format as before, and > > > <signature-format> is the signature type ("openpgp", "x509", > > > "ssh" or "unknown", > > > - the output is properly documented. > > > > Perhaps this was discussed in an earlier round, but if so I either > > forgot or missed it. What value does <git-hash-algo> and > > <signature-format> provide? How are they intended to be used? > > My opinion on this is that in cases like this we might not always know > what could be useful for tools or users in general, so it might be > better to provide more information that can be easily discarded if not > useful rather than not enough information. > > brian seemed to say that <git-hash-algo> and <signature-format> are > important so I just prefered to have both, especially as they are not > costly to get. So, with my new understanding, <git-hash-algo> is necessary because if you only had the gpg signature on a commit object, you don't know what it was a signature of -- it might be a signature of that commit object, or it might be a signature of it's "compatibility" object created with a different git hashing algorithm. So, fast-import -- regardless of whether it is writing into a sha1 repo or a sha256 repo -- won't be able to know how to check the validity of the gpg signature unless it knows whether to check the signature of the sha1 commit or the sha256 commit. And if the repository that fast-import is writing to isn't writing compatibility objects for interoperation at all, then it won't be able to check any compatibility signatures at all; it'll only be able to check signatures of the actual object it wants to write. > > Is the <signature-format> merely self-inflicted pain from stripping > > the ascii armor lines? If so, would it make more sense to just > > include those armor lines as-is in the fast-export stream and let > > fast-import process it? > > The ascii armor lines are kept in the fast-export stream, but > fast-export's ouput is supposed to be processed somehow sometimes > before being fed back to fast-import, so I think we should make it > easy for tools or people processing the output to get signature > information. Ah, just for convenience of the importer/processor; fair enough. > > Is the <git-hash-algo> due to the fact that we have separate `gpgsig` > > and `gpgsig-sha256` commit headers and we want to use that information > > to avoid writing these headers to the wrong-sized objects (and/or to > > avoid checking whether the signature is valid on the wrong-sized > > objects)? > > Yeah, I think it could help with that, but brian might better answer that. I think the answer is actually no, this isn't at all what these are about. These are about knowing how to check the validity of the signatures, because the signatures aren't necessarily associated with the object they were read from; they could be a signature of its compatibility object instead. > > If so, could that be spelled out in the docs as well, > > especially since it appears that the intent of these headers is left > > unimplemented due to not changing fast-import to do anything with > > them? > > fast-import puts back the `gpgsig` or `gpgsig-sha256` headers > depending on <git-hash-algo>, so it's useful at least for that. I will > improve the docs about it. > > > And if <git-hash-algo>'s purpose is to ensure they are only used when > > writing same-sized object as what was exported, then...isn't that a > > bug? > > I am not sure I understand what would be the bug. It's not relevant given my misunderstanding, but to explain what I was thinking last week (note that the first two bullets are my mistake): * gpgsig headers only occur on sha1 commits and are the signatures of the sha1 commit sans the gpgsig header * gpgsig-sha256 headers are similar with respect to sha256 commits * we were using <git-hash-algo> as an optimization to only sign commits in fast-import if the repository we were writing to was using the same hash function as the repo we were writing from In such a case, that'd be a bug for anyone that wanted a 'resign-commits-that-were-previously-signed-if-the-signature-becomes-invalid'. Although a signature of a sha256 commit clearly won't verify if we exported a sha256 repo and imported it as a sha1 repo and tried to verify that signature using a sha1 commit, that doesn't mean we should just ignore the fact that the sha256 commit had a signature. The user clearly expressed the intent to resign any commits that had a signature before but where the signature is no longer valid. > > This series was started because people wanted to be able to do > > things like keeping signature even when they are no longer valid or > > resigning commits that have a no longer commit signature (among other > > uses), but that would mean that if someone exports a sha1 repository > > and imports it as sha256, we don't want to ignore the fact that the > > sha1 commit was signed for those usecases. > > > > If, however, the <git-hash-algo>'s purpose is merely as a performance > > optimization that fast-import can employ in the cases where it checks > > for signatures being valid, so it can avoid checking when it know the > > hash size isn't even the same, then it could make sense. > > I think it could be used for that, but I'd like brian's opinion about this. No, I was just wrong; it can't be used as such a performance optimization. A signature of a sha256 commit stored in a sha1 commit (or a signature of a sha1 commit stored in a sha256 commit) isn't accidental, it's part of the intentional design of the hash-function transition. But, it might mean that the rules for what to do with signatures in fast-import need to be more complex than what I previously wrote...which I'll comment on a bit below. > > Wait..does this mean fast-export is obligated to walk over both all > > sha1 commits and all "equivalent" sha256 commits when exporting a > > repo? I thought most operations on the repo would walk over only one > > or the other; walking over both seems to be against the spirit of the > > "fast" in "fast-export". Am I missing something? (Possibly related > > question: Does "git log" bother walking over both, or does it only > > walk over one?) Even if this really is wanted by some users, > > shouldn't they manually request it rather than making exports slow for > > everyone else by default? > > No fast-export doesn't walk over both the sha1 commits and all > "equivalent" sha256 commits, it can just process at most 2 signatures > for a given commit, one with the `gpgsig` header and one with the > `gpgsig-sha256` header. I will try to reword the doc to make it > clearer. Right, but fast-import might need to *write* both sha1 commits and sha256 commits (or at least write one of them and keep a mapping between the two in memory) in order to correctly verify and process both gpgsig headers...or at least, it will be unable to verify signatures of compatibility objects if people do not have the appropriate extensions.compatObjectFormat config setting set within the repository they are importing to. This actually expands the usecases I mentioned previously a bit. Those usecases were: (A) Make fast-export include signatures, and make fast-import include them unconditionally (even if invalid) (B) Similar to (A), but make *fast-import* check them and either error out or drop them if they become invalid (C) Simliar to (B), but make *fast-import* re-sign the commit if they become invalid (D) Similar to (A), but make *fast-import* re-sign the commit even if the signature would have been valid Cases (B) and (C) might either need special care or need to bifurcate into additional options given that we can have two signatures on a commit. "Validity of gpg signature stored in the commit" now becomes "Validity of gpg signatureS stored in the commit". Whereas before we only considered the cases of "no valid signatures" and "all valid signatures", we also need to worry about the case where one signature is valid and the other is either known to be invalid or simply cannot be checked because this repo isn't set up to write compatibility objects. > > > +A warning is > > > +emitted for each additional signature found. > > > > Why? This seems odd to me. Why not merely export them all, and let > > fast-import throw warnings or errors if it sees more than one and is > > not yet prepared to handle multiple signatures? > > I am not against implementing both export and import support for any > number of signatures, but it seems that Git in general doesn't support > more than one `gpgsig` and one `gpgsig-sha256` signature well, and > that's also what brian suggested supporting. It's also better than > just 1 signature max which is the current state. [...] > > If I've > > understood correctly that the restriction is merely due to the current > > implementation, perhaps the wording could be changed to not list it as > > an encoding restriction, but as a current fast-import limitation? > > If that current fast-import limitation was lifted, we should probably > still say that additional signatures might not be very well supported > by Git in general. Ah! I thought you were saying that fast-import specifically didn't have good support for more than one signature (and I was assuming the rest of Git did) and were using that as the reason to avoid handling more than 1. Sorry about that, I'm following you on this point now. > > Also, I'm slightly uncomfortable with "the SHA-1 object format" and > > "the SHA-256 object format" because of the fact that these tools are > > used for interoperability with similarly-named tools from other VCSes. > > I think it'd be better to just treat them equally as "here was/were > > some signature(s) on the object in the original repo; importers can > > choose what to do with them". > > I don't think giving that information prevents tools from other VCSes > from doing what they want with the signatures. Yeah, sorry, this was fallout from my misunderstanding about commits having signatures from their compatibility object. That's just kind of fundamental to this hash-function transition, so ignore what I wrote here. > [...] > > > I briefly skimmed the implementation and test files, and didn't see > > any problems...but I think it probably makes more sense to get aligned > > on the goals of the format and how these fields are meant to be used > > before diving into those details closer. > > Yeah, I would have been happy if we could have been aligned with the > goals of the format and the fields earlier, but better late than > never. I think the proposal for the fields in this version make sense now, but it might have been easier to get alignment if (1) we could get real testcases of the multiple signature case, and (2) we could see the actual code for fast-import to deal with the signatures (trying to guess the needs of the importer and the meanings of the fields we export is supposed to be is harder when their usage has been left unimplemented). That said, I also understand you wanting to avoid implementing too much and throwing things away if we disagreed on early decisions. Anyway, I'm on board with these new fields and their purpose, though I'm still curious if we start diverging when we run across surprises as we dig further into the implementation. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-26 19:11 ` Elijah Newren @ 2025-07-08 9:16 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-08 9:16 UTC (permalink / raw) To: Elijah Newren Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Thu, Jun 26, 2025 at 9:12 PM Elijah Newren <newren@gmail.com> wrote: > > On Fri, Jun 20, 2025 at 9:12 AM Christian Couder > <christian.couder@gmail.com> wrote: > > In https://lore.kernel.org/git/aD4i7YhUnT5Kgew-@tapette.crustytoothpaste.net/ > > brian said: > > > > "Actually, what I was saying is that we should have one for the hash > > algorithm that is used in the Git object. I don't care about the hash > > algorithm used in OpenPGP, X.509, or OpenSSH (that is, whether it's > > signed with SHA-512 or SHA-256), but we can have multiple signatures in > > a single commit such that there's both a SHA-1 signature and a SHA-256 > > signature." > > > > so I implemented the possibility that there's both a SHA-1 signature > > and a SHA-256 signature in a single commit. > > > > If you disagreed with what brian suggested, it would have been nice to > > reply to brian then. > > Sorry about that. I hadn't read the whole thread; further, I was > previously trying to avoid the details of signatures beyond the basics > and hoping to leave those details to others, but between v2 and some > words you had in this version plus deciding to just take a closer look > while on vacation (as I was last week), I decided to try to look a bit > closer. Thanks for taking a closer look. > Turns out, though, that despite my attempts to read a bit more > documentation and code in the project, I still had a fundamental > misunderstanding last week when I was responding. I had been assuming > that the gpgsig headers would appear on sha1-commits and gpgsig-sha256 > headers would appear on sha256 commits. I didn't understand that both > types of headers can appear on both types of commits (so that, for > example, gpgsig-sha256 would be use the sha256-commit as the source > and sign that data but write the result to a sha1-commit). I talked > with brian this morning to ask a few questions and he clarified this > for me. > > (Side note: It might be nice to clarify this in > Documentation/technical/hash-function-transition.adoc; that document > did not dispel this confusion when I read it last week.) I agree that it would help to improve that document. I think it's a separate topic though. > And the part I didn't understand the first time around is that e.g. > the SHA-1 commit can include both the signatures on the SHA-1 object > and on the SHA-256 object. (Similarly, the SHA-256 commit can include > both signatures as well.) > > > > > - if there is more than one signature on the SHA-1 object or on > > > > the SHA-256 object, a warning is emitted, > > This is now ambiguous to me. more than one signature using the SHA-1 > as source for signing, or more than one signature recorded within the > SHA-1 commit? I think you mean the former. Yes, it's the former. I am not sure there is a simple and short way to disambiguate these meanings. > > My opinion on this is that in cases like this we might not always know > > what could be useful for tools or users in general, so it might be > > better to provide more information that can be easily discarded if not > > useful rather than not enough information. > > > > brian seemed to say that <git-hash-algo> and <signature-format> are > > important so I just prefered to have both, especially as they are not > > costly to get. > > So, with my new understanding, <git-hash-algo> is necessary because if > you only had the gpg signature on a commit object, you don't know what > it was a signature of -- it might be a signature of that commit > object, or it might be a signature of it's "compatibility" object > created with a different git hashing algorithm. I agree that it's important for this reason. > So, fast-import -- > regardless of whether it is writing into a sha1 repo or a sha256 repo > -- won't be able to know how to check the validity of the gpg > signature unless it knows whether to check the signature of the sha1 > commit or the sha256 commit. And if the repository that fast-import> is writing to isn't writing compatibility objects for interoperation > at all, then it won't be able to check any compatibility signatures at > all; it'll only be able to check signatures of the actual object it > wants to write. Yeah, right. > > > Is the <signature-format> merely self-inflicted pain from stripping > > > the ascii armor lines? If so, would it make more sense to just > > > include those armor lines as-is in the fast-export stream and let > > > fast-import process it? > > > > The ascii armor lines are kept in the fast-export stream, but > > fast-export's ouput is supposed to be processed somehow sometimes > > before being fed back to fast-import, so I think we should make it > > easy for tools or people processing the output to get signature > > information. > > Ah, just for convenience of the importer/processor; fair enough. Yeah, right. > > > Is the <git-hash-algo> due to the fact that we have separate `gpgsig` > > > and `gpgsig-sha256` commit headers and we want to use that information > > > to avoid writing these headers to the wrong-sized objects (and/or to > > > avoid checking whether the signature is valid on the wrong-sized > > > objects)? > > > > Yeah, I think it could help with that, but brian might better answer that. > > I think the answer is actually no, this isn't at all what these are > about. These are about knowing how to check the validity of the > signatures, because the signatures aren't necessarily associated with > the object they were read from; they could be a signature of its > compatibility object instead. First I am now not sure what "wrong-sized objects" meant in your previous sentence. I thought it meant "object using SHA-1 vs object using SHA-256". In that case, the <git-hash-algo> could indeed help check the signature on the right object. Also git fast-import has to recreate the `gpgsig` and `gpgsig-sha256` commit headers when importing signatures, so <git-hash-algo> can help it with that too. > > > If so, could that be spelled out in the docs as well, > > > especially since it appears that the intent of these headers is left > > > unimplemented due to not changing fast-import to do anything with > > > them? > > > > fast-import puts back the `gpgsig` or `gpgsig-sha256` headers > > depending on <git-hash-algo>, so it's useful at least for that. I will > > improve the docs about it. In v5, I have improved the fast-import documentation with the following: * `<git-hash-algo>` specifies which Git object format this signature applies to, either `sha1` or `sha256`. This allows to know which representation of the commit was signed (the SHA-1 or the SHA-256 version) which helps with both signature verification and interoperability between repos with different hash functions. * `<signature-format>` specifies the type of signature, such as `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for tools that process the stream, so they don't have to parse the ASCII armor to identify the signature type. The commit message is improved too. > > > And if <git-hash-algo>'s purpose is to ensure they are only used when > > > writing same-sized object as what was exported, then...isn't that a > > > bug? > > > > I am not sure I understand what would be the bug. > > It's not relevant given my misunderstanding, but to explain what I was > thinking last week (note that the first two bullets are my mistake): > > * gpgsig headers only occur on sha1 commits and are the signatures > of the sha1 commit sans the gpgsig header > * gpgsig-sha256 headers are similar with respect to sha256 commits > * we were using <git-hash-algo> as an optimization to only sign > commits in fast-import if the repository we were writing to was using > the same hash function as the repo we were writing from > > In such a case, that'd be a bug for anyone that wanted a > 'resign-commits-that-were-previously-signed-if-the-signature-becomes-invalid'. > Although a signature of a sha256 commit clearly won't verify if we > exported a sha256 repo and imported it as a sha1 repo and tried to > verify that signature using a sha1 commit, that doesn't mean we should > just ignore the fact that the sha256 commit had a signature. The user > clearly expressed the intent to resign any commits that had a > signature before but where the signature is no longer valid. I see. > > > This series was started because people wanted to be able to do > > > things like keeping signature even when they are no longer valid or > > > resigning commits that have a no longer commit signature (among other > > > uses), but that would mean that if someone exports a sha1 repository > > > and imports it as sha256, we don't want to ignore the fact that the > > > sha1 commit was signed for those usecases. > > > > > > If, however, the <git-hash-algo>'s purpose is merely as a performance > > > optimization that fast-import can employ in the cases where it checks > > > for signatures being valid, so it can avoid checking when it know the > > > hash size isn't even the same, then it could make sense. > > > > I think it could be used for that, but I'd like brian's opinion about this. > > No, I was just wrong; it can't be used as such a performance > optimization. A signature of a sha256 commit stored in a sha1 commit > (or a signature of a sha1 commit stored in a sha256 commit) isn't > accidental, it's part of the intentional design of the hash-function > transition. > > But, it might mean that the rules for what to do with signatures in > fast-import need to be more complex than what I previously > wrote...which I'll comment on a bit below. Ok. > > > Wait..does this mean fast-export is obligated to walk over both all > > > sha1 commits and all "equivalent" sha256 commits when exporting a > > > repo? I thought most operations on the repo would walk over only one > > > or the other; walking over both seems to be against the spirit of the > > > "fast" in "fast-export". Am I missing something? (Possibly related > > > question: Does "git log" bother walking over both, or does it only > > > walk over one?) Even if this really is wanted by some users, > > > shouldn't they manually request it rather than making exports slow for > > > everyone else by default? > > > > No fast-export doesn't walk over both the sha1 commits and all > > "equivalent" sha256 commits, it can just process at most 2 signatures > > for a given commit, one with the `gpgsig` header and one with the > > `gpgsig-sha256` header. I will try to reword the doc to make it > > clearer. In v5 all the signatures are exported, so the doc is now: "While all the signatures of a commit are exported, an importer may choose to accept only some of them. For example linkgit:git-fast-import[1] currently stores at most one signature per Git hash algorithm in each commit." I hope it avoids misunderstandings. > Right, but fast-import might need to *write* both sha1 commits and > sha256 commits (or at least write one of them and keep a mapping > between the two in memory) in order to correctly verify and process > both gpgsig headers...or at least, it will be unable to verify > signatures of compatibility objects if people do not have the > appropriate extensions.compatObjectFormat config setting set within > the repository they are importing to. Yeah, it seems to me that when extensions.compatObjectFormat is set, then a compatibility mapping between objects can be used. I don't think using it should be part of this patch though. When I will work on making it possible for fast-import to check signatures, I will try to use it to write both sha1 commits and sha256 commits, and check both signatures. > This actually expands the usecases I mentioned previously a bit. > Those usecases were: > > (A) Make fast-export include signatures, and make fast-import include > them unconditionally (even if invalid) > (B) Similar to (A), but make *fast-import* check them and either error > out or drop them if they become invalid > (C) Simliar to (B), but make *fast-import* re-sign the commit if they > become invalid > (D) Similar to (A), but make *fast-import* re-sign the commit even if > the signature would have been valid > > Cases (B) and (C) might either need special care or need to bifurcate > into additional options given that we can have two signatures on a > commit. "Validity of gpg signature stored in the commit" now becomes > "Validity of gpg signatureS stored in the commit". Whereas before we > only considered the cases of "no valid signatures" and "all valid > signatures", we also need to worry about the case where one signature > is valid and the other is either known to be invalid or simply cannot > be checked because this repo isn't set up to write compatibility > objects. Yeah, I agree that there might be new cases to consider. I don't think this affects this patch though as long as it allows one signature for each hash to be exported and imported which is the case. > > Yeah, I would have been happy if we could have been aligned with the > > goals of the format and the fields earlier, but better late than > > never. > > I think the proposal for the fields in this version make sense now, > but it might have been easier to get alignment if (1) we could get > real testcases of the multiple signature case, There is such a multiple signature test case in v5. > and (2) we could see > the actual code for fast-import to deal with the signatures (trying to > guess the needs of the importer and the meanings of the fields we > export is supposed to be is harder when their usage has been left > unimplemented). That said, I also understand you wanting to avoid > implementing too much and throwing things away if we disagreed on > early decisions. Yeah, I prefer to move forward step by step on this. > Anyway, I'm on board with these new fields and their purpose, though > I'm still curious if we start diverging when we run across surprises > as we dig further into the implementation. As we have clearly warned that the current implementation is experimental, I think we should have more flexibility to adapt the formats and behaviors if we run across surprises in the next steps. Thanks for your thoughts and reviews. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-06-19 13:36 ` [PATCH v4] " Christian Couder 2025-06-19 14:55 ` Junio C Hamano 2025-06-19 21:44 ` Elijah Newren @ 2025-07-07 22:58 ` Junio C Hamano 2025-07-08 3:35 ` Christian Couder 2025-07-08 9:17 ` [PATCH v5] " Christian Couder 3 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-07-07 22:58 UTC (permalink / raw) To: git Cc: Christian Couder, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > This v4 is just about fixing a few bugs in the tests using the SHA-256 > object format compared to the v3. (I had issues with CI tests on v3, > so I sent it without waiting for the results.) We haven't heard much after a few comments were posted on this latest round, since Elijah's <20250619133630.727274-1-christian.couder@gmail.com>; I understand that it would be the author's turn to respond (the response does not necessarily have to be with an updated iteration). If so, let me mark the topic as Stalled in the draft of the latest issue of the "What's cooking" report. Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-07 22:58 ` Junio C Hamano @ 2025-07-08 3:35 ` Christian Couder 2025-07-08 5:03 ` Junio C Hamano 0 siblings, 1 reply; 65+ messages in thread From: Christian Couder @ 2025-07-08 3:35 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Tue, Jul 8, 2025 at 12:58 AM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > > This v4 is just about fixing a few bugs in the tests using the SHA-256 > > object format compared to the v3. (I had issues with CI tests on v3, > > so I sent it without waiting for the results.) > > We haven't heard much after a few comments were posted on this > latest round, since Elijah's > <20250619133630.727274-1-christian.couder@gmail.com>; I understand > that it would be the author's turn to respond (the response does not > necessarily have to be with an updated iteration). If so, let me > mark the topic as Stalled in the draft of the latest issue of the > "What's cooking" report. I will hopefully send a v5 later today. Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-08 3:35 ` Christian Couder @ 2025-07-08 5:03 ` Junio C Hamano 2025-07-08 6:38 ` Patrick Steinhardt 2025-07-08 10:17 ` Christian Couder 0 siblings, 2 replies; 65+ messages in thread From: Junio C Hamano @ 2025-07-08 5:03 UTC (permalink / raw) To: Christian Couder Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > On Tue, Jul 8, 2025 at 12:58 AM Junio C Hamano <gitster@pobox.com> wrote: >> >> Christian Couder <christian.couder@gmail.com> writes: >> >> > This v4 is just about fixing a few bugs in the tests using the SHA-256 >> > object format compared to the v3. (I had issues with CI tests on v3, >> > so I sent it without waiting for the results.) >> >> We haven't heard much after a few comments were posted on this >> latest round, since Elijah's >> <20250619133630.727274-1-christian.couder@gmail.com>; I understand >> that it would be the author's turn to respond (the response does not >> necessarily have to be with an updated iteration). If so, let me >> mark the topic as Stalled in the draft of the latest issue of the >> "What's cooking" report. > > I will hopefully send a v5 later today. Thanks. By the way, I noticed that you often do not respond to reviews until the last minute, at the same time as when you send your next iteration, or even soon after doing so. That is quite different from how other contributors operate, i.e. respond and engage in discussions triggered by the reviews, and after people involved in discussion got an (even rough) idea of what the right next step would be, if not a total consensus, send the next iteration. I do not know which style is more efficient form of cooperation, but it somewhat makes my job harder, if I do not hear much _heartbeats_ after I see review comments on the list. I do not mind waiting for seeing the next round for quite a while---after all, any substantial (re)work takes time. And responding to reviews may need thinking things through carefully, which may take some time, so I would not demand an immediate response, either. But it would be nearly impossible to feel the current status of such a topic---a few review comments are seen, the author goes silent for a while, we cannot tell if the author is working on a new iteration or where the author and reviewers agree and disagree. Also a review response that comes at the same time or immediately after a new iteration is already sent out makes it look like the author is refusing to continue discussion and reviewers are not welcome to make follow-up suggestions during such a discussion. Instead, the next iteration comes as a fait accompli, and makes it less useful to continue the review discussion on the previous round by responding to such a late response. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-08 5:03 ` Junio C Hamano @ 2025-07-08 6:38 ` Patrick Steinhardt 2025-07-08 11:08 ` Christian Couder 2025-07-08 10:17 ` Christian Couder 1 sibling, 1 reply; 65+ messages in thread From: Patrick Steinhardt @ 2025-07-08 6:38 UTC (permalink / raw) To: Junio C Hamano Cc: Christian Couder, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Mon, Jul 07, 2025 at 10:03:12PM -0700, Junio C Hamano wrote: > Christian Couder <christian.couder@gmail.com> writes: > > > On Tue, Jul 8, 2025 at 12:58 AM Junio C Hamano <gitster@pobox.com> wrote: > >> > >> Christian Couder <christian.couder@gmail.com> writes: > >> > >> > This v4 is just about fixing a few bugs in the tests using the SHA-256 > >> > object format compared to the v3. (I had issues with CI tests on v3, > >> > so I sent it without waiting for the results.) > >> > >> We haven't heard much after a few comments were posted on this > >> latest round, since Elijah's > >> <20250619133630.727274-1-christian.couder@gmail.com>; I understand > >> that it would be the author's turn to respond (the response does not > >> necessarily have to be with an updated iteration). If so, let me > >> mark the topic as Stalled in the draft of the latest issue of the > >> "What's cooking" report. > > > > I will hopefully send a v5 later today. > > Thanks. > > By the way, I noticed that you often do not respond to reviews until > the last minute, at the same time as when you send your next > iteration, or even soon after doing so. > > That is quite different from how other contributors operate, i.e. > respond and engage in discussions triggered by the reviews, and > after people involved in discussion got an (even rough) idea of what > the right next step would be, if not a total consensus, send the > next iteration. > > I do not know which style is more efficient form of cooperation, but > it somewhat makes my job harder, if I do not hear much _heartbeats_ > after I see review comments on the list. I do not mind waiting for > seeing the next round for quite a while---after all, any substantial > (re)work takes time. And responding to reviews may need thinking > things through carefully, which may take some time, so I would not > demand an immediate response, either. But it would be nearly > impossible to feel the current status of such a topic---a few review > comments are seen, the author goes silent for a while, we cannot > tell if the author is working on a new iteration or where the author > and reviewers agree and disagree. > > Also a review response that comes at the same time or immediately > after a new iteration is already sent out makes it look like the > author is refusing to continue discussion and reviewers are not > welcome to make follow-up suggestions during such a discussion. > > Instead, the next iteration comes as a fait accompli, and makes it > less useful to continue the review discussion on the previous round > by responding to such a late response. I agree with your points. Overall, a fast response cycle is key to good collaboration from my point of view. I think it not only makes your life as a maintainer easier, but it also makes the reviewer feel like they are being heard and is the prerequisite for good discussion. On our team's handbook page [1] we have the following couple of bullet points regarding how to respond to reviews: * Respond to feedback that you have received as fast as possible. A fast exchange is a prerequisite for a fruitful discussion and ensures that you keep momentum. * On the other hand, it shouldn’t be necessary to respond to every small typo correction in case you will send out the next version soon anyway. If it will take a while before you send the next version though it is nice to acknowledge nits, but mention that you will hold off sending a new version of the series until you got more feedback. * Consider the viewpoint of the other person and be ready to disagree and commit. Do not ignore feedback that you have received, as that will lead to frustration and a decreased likelihood for that person to review your future patch series. Patrick [1]: https://handbook.gitlab.com/handbook/engineering/infrastructure-platforms/data-access/git/#iteration ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-08 6:38 ` Patrick Steinhardt @ 2025-07-08 11:08 ` Christian Couder 2025-07-08 16:38 ` Junio C Hamano 0 siblings, 1 reply; 65+ messages in thread From: Christian Couder @ 2025-07-08 11:08 UTC (permalink / raw) To: Patrick Steinhardt Cc: Junio C Hamano, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Tue, Jul 8, 2025 at 8:38 AM Patrick Steinhardt <ps@pks.im> wrote: > I agree with your points. Overall, a fast response cycle is key to good > collaboration from my point of view. I think it not only makes your life > as a maintainer easier, but it also makes the reviewer feel like they > are being heard and is the prerequisite for good discussion. I don't agree with "is the prerequisite for good discussion". I think it's perfectly possible to have good long running discussions even when people's replies are delayed. And people can have vacations or weekends or private issues or work on other things which can delay some replies. And when people work significantly for a long time on a topic before replying, I really think it can increase the quality of the discussion. Also if a contributor comes back with improved patches that try to follow closely what a reviewer suggested, then I think it can (and should) make a reviewer feel like they have really been heard better than just a hollow reply right away followed later by less well thought out patches. This doesn't mean that I think there is no value in a fast response cycle. But I think it depends a lot on the circumstances. Yeah, for someone new in the community I think it can often help a lot. And I encourage the contributors I mentor to respond soon. When someone has been part of the community for a long time, I think it's different though. It can happen, but it's much less likely that they are going to disappear tomorrow or otherwise not follow up on feedback they got from reviewers. It doesn't mean that I think oldtimers should have some kind of privilege, and yeah they should also try to give a good example. But we should allow people to not always behave in a very formatted way. As I mentioned above and in my reply to Junio, many things outside Git development can happen, and people can behave differently, have different preferences in the way they work, and especially not like or want to switch topics very often for example to keep a better mental focus on what they are currently doing. > On our team's handbook page [1] we have the following couple of bullet > points regarding how to respond to reviews: Yeah, I think they are likely to be good for newcomers. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-08 11:08 ` Christian Couder @ 2025-07-08 16:38 ` Junio C Hamano 2025-07-09 0:19 ` Christian Couder 0 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-07-08 16:38 UTC (permalink / raw) To: Christian Couder Cc: Patrick Steinhardt, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > Also if a contributor comes back with improved patches that try to > follow closely what a reviewer suggested, then I think it can (and > should) make a reviewer feel like they have really been heard better > than just a hollow reply right away followed later by less well > thought out patches. That is kind of "better late than never". I would expect better than that from more senior prominent contributors ;-) And I totally agree with you that reviews often deserve very well reasoned responses, which take time to prepare; a response that comes as spinal reflex without much thought is often not very useful. It really depends on the definition of "fast" in "fast response". If we need a week to come up with a newer iteration, it would be fair to expect that we can say something like "I agree, I'll fix", "I am not convinced because ...", "I am skeptical but let me first see how it pans out", etc. by day #2 or #3, wouldn't it? Upon recieving a response at the same time or soon after an updated iteration was sent, especially when the response is "no, I do not think so", what is the reviewer expected to do? Saying "you may not think so but here is another point that may make you reconsider" would be too late, so it would actively discourage continued discussion. > It doesn't mean that I think oldtimers should have some kind of > privilege, and yeah they should also try to give a good example. But > we should allow people to not always behave in a very formatted way. Old timers learn from experience how other old timers operate ;-) and I have learned to ignore the usual signal when anticipating what your next iteration may look like (in other words, interim responses or lack thereof is usually a good signal for most developers, but not for you---you tend to come back with your next iteration without much interim interactions). But other contributors shouldn't be forced to. That is what we need some community norm for. >> On our team's handbook page [1] we have the following couple of bullet >> points regarding how to respond to reviews: > > Yeah, I think they are likely to be good for newcomers. The handbook here is gitlab's team handbook, and it may not apply to open source Git development community, but "this rule applies only to newcomers, I am above that rule" is the same thing as saying "oldtimers like me should have some kind of privilege". I do not know what to think about this and what you said above. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-08 16:38 ` Junio C Hamano @ 2025-07-09 0:19 ` Christian Couder 2025-07-09 15:35 ` Junio C Hamano 2025-07-10 8:25 ` Patrick Steinhardt 0 siblings, 2 replies; 65+ messages in thread From: Christian Couder @ 2025-07-09 0:19 UTC (permalink / raw) To: Junio C Hamano Cc: Patrick Steinhardt, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Tue, Jul 8, 2025 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > > Also if a contributor comes back with improved patches that try to > > follow closely what a reviewer suggested, then I think it can (and > > should) make a reviewer feel like they have really been heard better > > than just a hollow reply right away followed later by less well > > thought out patches. > > That is kind of "better late than never". I would expect better > than that from more senior prominent contributors ;-) > > And I totally agree with you that reviews often deserve very well > reasoned responses, which take time to prepare; a response that > comes as spinal reflex without much thought is often not very > useful. > > It really depends on the definition of "fast" in "fast response". > > If we need a week to come up with a newer iteration, The issue is that whatever the time we could set as a norm, like "a week" here or 2 or 3 days, or one month, or whatever, we actually don't know what happens in the life of contributors. Maybe some have health issues, maybe some work only a few days per week, maybe some oare working on their free time and don't have much free time, maybe they are asked to work a lot by their company on other internal urgent things, maybe they have to take care of dependent people in their family, etc... So setting any norm here that everyone should try to respect might just not work for some people. For example there was a former Outreachy intern who continued working for about 2 years on their project after the internship was over. She was a young mother so didn't have much time to work on Git and would come back to the mailing list only every few months with new patches and replies to reviewers. What would we have gained exactly by imposing a norm on her? > it would be > fair to expect that we can say something like "I agree, I'll fix", > "I am not convinced because ...", "I am skeptical but let me first > see how it pans out", etc. by day #2 or #3, wouldn't it? Upon > recieving a response at the same time or soon after an updated > iteration was sent, especially when the response is "no, I do not > think so", what is the reviewer expected to do? Saying "you may not > think so but here is another point that may make you reconsider" > would be too late, so it would actively discourage continued > discussion. > > > It doesn't mean that I think oldtimers should have some kind of > > privilege, and yeah they should also try to give a good example. But > > we should allow people to not always behave in a very formatted way. > > Old timers learn from experience how other old timers operate ;-) > and I have learned to ignore the usual signal when anticipating what > your next iteration may look like (in other words, interim responses > or lack thereof is usually a good signal for most developers, but > not for you---you tend to come back with your next iteration without > much interim interactions). But other contributors shouldn't be > forced to. That is what we need some community norm for. > > >> On our team's handbook page [1] we have the following couple of bullet > >> points regarding how to respond to reviews: > > > > Yeah, I think they are likely to be good for newcomers. > > The handbook here is gitlab's team handbook, and it may not apply to > open source Git development community, but "this rule applies only > to newcomers, I am above that rule" is the same thing as saying > "oldtimers like me should have some kind of privilege". I do not > know what to think about this and what you said above. I think it's fair to say that oldtimers are less likely to disappear tomorrow or to not follow up on reviewer feedback. So arguments like "replying fast makes reviewers confident that they have been heard" are just less true for oldtimers than for newcomers. Another argument is that oldtimers are more likely to burn out or have mental health issues related to their Git work than newcomers. Adding a norm that would put pressure on them to work more, or at times they would prefer to do other things, significantly increases the burnout and mental health risks for them. So putting pressure on oldtimers to follow a norm that is less relevant for them but puts them at greater risk is just a bad idea in my opinion. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-09 0:19 ` Christian Couder @ 2025-07-09 15:35 ` Junio C Hamano 2025-07-10 8:25 ` Patrick Steinhardt 1 sibling, 0 replies; 65+ messages in thread From: Junio C Hamano @ 2025-07-09 15:35 UTC (permalink / raw) To: Christian Couder Cc: Patrick Steinhardt, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: >> And I totally agree with you that reviews often deserve very well >> reasoned responses, which take time to prepare; a response that >> comes as spinal reflex without much thought is often not very >> useful. >> >> It really depends on the definition of "fast" in "fast response". >> >> If we need a week to come up with a newer iteration, > > The issue is that whatever the time we could set as a norm, like "a > week" here or 2 or 3 days, or one month, or whatever, Yeah, topic sizes varies. Historically, summer is a slower season and these messages I sent are primarily for me to keep track of topics on flight. "I am on vacation for a few more weeks so expect response time longer than usual" would have been a perfectly fine response. Not even acknowledging receipt of review comments was what I primarily saw as a communication gap. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-09 0:19 ` Christian Couder 2025-07-09 15:35 ` Junio C Hamano @ 2025-07-10 8:25 ` Patrick Steinhardt 2025-07-10 15:29 ` Christian Couder 2025-07-10 15:33 ` Junio C Hamano 1 sibling, 2 replies; 65+ messages in thread From: Patrick Steinhardt @ 2025-07-10 8:25 UTC (permalink / raw) To: Christian Couder Cc: Junio C Hamano, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Wed, Jul 09, 2025 at 02:19:06AM +0200, Christian Couder wrote: > On Tue, Jul 8, 2025 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > > > > Also if a contributor comes back with improved patches that try to > > > follow closely what a reviewer suggested, then I think it can (and > > > should) make a reviewer feel like they have really been heard better > > > than just a hollow reply right away followed later by less well > > > thought out patches. > > > > That is kind of "better late than never". I would expect better > > than that from more senior prominent contributors ;-) > > > > And I totally agree with you that reviews often deserve very well > > reasoned responses, which take time to prepare; a response that > > comes as spinal reflex without much thought is often not very > > useful. > > > > It really depends on the definition of "fast" in "fast response". > > > > If we need a week to come up with a newer iteration, > > The issue is that whatever the time we could set as a norm, like "a > week" here or 2 or 3 days, or one month, or whatever, we actually > don't know what happens in the life of contributors. Maybe some have > health issues, maybe some work only a few days per week, maybe some > oare working on their free time and don't have much free time, maybe > they are asked to work a lot by their company on other internal urgent > things, maybe they have to take care of dependent people in their > family, etc... So setting any norm here that everyone should try to > respect might just not work for some people. > > For example there was a former Outreachy intern who continued working > for about 2 years on their project after the internship was over. She > was a young mother so didn't have much time to work on Git and would > come back to the mailing list only every few months with new patches > and replies to reviewers. What would we have gained exactly by > imposing a norm on her? There are always going to be exceptions, that is of course true. But I also think that long-time contributors that are employed to work on Git are somewhat special and don't (typically) fall into the mentioned groups. From my perspective, it's especially this group of people that should lead by example and encourage others to behave in a way that is good for the overall Git community. And leading by example in this context also means that they should encourage healthy discussions. That of course doesn't mean that such people should always respond within an hour, or even within a day. We all have to context switch, and context switches are costly, so it's entirely reasonable to try and minimize them. But outside of any special circumstances (vacations, health or similar) I think it should be possible to engage in such discussions within a small handful of days. In any case, there of course is a distinction between people employed to work on Git and those that do it in their own free time. The expectation that is extended towards people who work on Git is way higher than the expectation extended towards people who don't. > > it would be > > fair to expect that we can say something like "I agree, I'll fix", > > "I am not convinced because ...", "I am skeptical but let me first > > see how it pans out", etc. by day #2 or #3, wouldn't it? Upon > > recieving a response at the same time or soon after an updated > > iteration was sent, especially when the response is "no, I do not > > think so", what is the reviewer expected to do? Saying "you may not > > think so but here is another point that may make you reconsider" > > would be too late, so it would actively discourage continued > > discussion. > > > > > It doesn't mean that I think oldtimers should have some kind of > > > privilege, and yeah they should also try to give a good example. But > > > we should allow people to not always behave in a very formatted way. > > > > Old timers learn from experience how other old timers operate ;-) > > and I have learned to ignore the usual signal when anticipating what > > your next iteration may look like (in other words, interim responses > > or lack thereof is usually a good signal for most developers, but > > not for you---you tend to come back with your next iteration without > > much interim interactions). But other contributors shouldn't be > > forced to. That is what we need some community norm for. > > > > >> On our team's handbook page [1] we have the following couple of bullet > > >> points regarding how to respond to reviews: > > > > > > Yeah, I think they are likely to be good for newcomers. > > > > The handbook here is gitlab's team handbook, and it may not apply to > > open source Git development community, but "this rule applies only > > to newcomers, I am above that rule" is the same thing as saying > > "oldtimers like me should have some kind of privilege". I do not > > know what to think about this and what you said above. > > I think it's fair to say that oldtimers are less likely to disappear > tomorrow or to not follow up on reviewer feedback. So arguments like > "replying fast makes reviewers confident that they have been heard" > are just less true for oldtimers than for newcomers. I think these are two different things. It's probably true that you get some privileges by being an old timer. But I think it's more in the sense of "You get to tackle bigger things that may not be done in a single patch series, and we trust you to not just disappear". But with that privilege also comes responsibility. It's those old timers that newcomers look to, so they need to lead by example. > Another argument is that oldtimers are more likely to burn out or have > mental health issues related to their Git work than newcomers. Adding > a norm that would put pressure on them to work more, or at times they > would prefer to do other things, significantly increases the burnout > and mental health risks for them. > > So putting pressure on oldtimers to follow a norm that is less > relevant for them but puts them at greater risk is just a bad idea in > my opinion. This is of course something we must avoid. Nobody is being helped in case people burn out. There needs to be a middle ground that works for everyone. Patrick ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-10 8:25 ` Patrick Steinhardt @ 2025-07-10 15:29 ` Christian Couder 2025-07-10 15:33 ` Junio C Hamano 1 sibling, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-10 15:29 UTC (permalink / raw) To: Patrick Steinhardt Cc: Junio C Hamano, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Thu, Jul 10, 2025 at 2:01 PM Patrick Steinhardt <ps@pks.im> wrote: > > On Wed, Jul 09, 2025 at 02:19:06AM +0200, Christian Couder wrote: > > On Tue, Jul 8, 2025 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote: > > > If we need a week to come up with a newer iteration, > > > > The issue is that whatever the time we could set as a norm, like "a > > week" here or 2 or 3 days, or one month, or whatever, we actually > > don't know what happens in the life of contributors. Maybe some have > > health issues, maybe some work only a few days per week, maybe some > > oare working on their free time and don't have much free time, maybe > > they are asked to work a lot by their company on other internal urgent > > things, maybe they have to take care of dependent people in their > > family, etc... So setting any norm here that everyone should try to > > respect might just not work for some people. > > > > For example there was a former Outreachy intern who continued working > > for about 2 years on their project after the internship was over. She > > was a young mother so didn't have much time to work on Git and would > > come back to the mailing list only every few months with new patches > > and replies to reviewers. What would we have gained exactly by > > imposing a norm on her? > > There are always going to be exceptions, that is of course true. Ok, so how are we going to manage those exceptions? Are we going to ask people publicly why they take a long time to reply, and expect that they are going to tell everyone things that might be quite personal? How will they feel if they are asked repeatedly by different reviewers, in the case some reviewers didn't notice that other reviewers have already asked? > But I > also think that long-time contributors that are employed to work on Git > are somewhat special and don't (typically) fall into the mentioned > groups. They could fall into similar groups. They could have a child they take care of, they could have health issues (including burnout) or they could just have a few bad days sometimes. They could also stop being employed to work on Git and still want to contribute. Are we going to have a public list of people who are allowed to reply late, so that every reviewer can check if it's Ok for the contributor to reply late? > From my perspective, it's especially this group of people that > should lead by example and encourage others to behave in a way that is > good for the overall Git community. So you think it would be good for the community if people start to keep count of how fast others reply and annoy them when they don't reply fast enough? In my opinion, insisting on this is just a bad idea that could backfire in many ways. I am not saying that it wouldn't be good if people in general tried to reply soon to reviewers when they receive feedback. I am saying that it's not worth the possible costs to insist on this small aspect of what makes reviewers happy. > And leading by example in this > context also means that they should encourage healthy discussions. Why is it not healthy when contributors reply when they think it's best for them to reply? It seems to me that, on the contrary, it's not healthy to put pressure on them to reply when they might not think it's best for them to reply. Responding soon is in my opinion a very small part of what makes discussions healthy. There is also the tone, the thankfulness, the constructiveness, the technical accuracy of the information shared, the work that might have been done to try solutions, the willingness to share knowledge, the effort to try to understand and act on what others say, and so on. Reducing the quality or healthiness of discussions to just how fast a person replies, is just not a good metric. It's like measuring how productive a person is by counting the lines of code produced. > That of course doesn't mean that such people should always respond > within an hour, or even within a day. We all have to context switch, and > context switches are costly, so it's entirely reasonable to try and > minimize them. But outside of any special circumstances (vacations, > health or similar) I think it should be possible to engage in such > discussions within a small handful of days. You could also say "it should be possible for most people outside any special circumstances to 'produce' 20 lines of tested code per working day". Would that make it worth measuring and annoying people in case the measure is not up to the norm? > In any case, there of course is a distinction between people employed to > work on Git and those that do it in their own free time. The expectation > that is extended towards people who work on Git is way higher than the > expectation extended towards people who don't. What kind of expectation? Do you mean that the resulting code after review, or even before review, should be way better too? Even when the person employed might be a junior programmer while there are very experienced contributors working on Git in their free time? And if for example someone retires from work and then starts contributing to Git 10 hours per day every day of the year, then should people employed to work on Git try to match that amount of working hours? If someone working on Git in their free time has an especially good mastery of the English language and starts to write awesome commit messages that are on average 100 lines long, then are we going to expect people employed to work on Git especially those who are not native speakers to do as well in this regard? People are just different in many different ways, and what you might very well expect trivially from some, might be very difficult for others. > > I think it's fair to say that oldtimers are less likely to disappear > > tomorrow or to not follow up on reviewer feedback. So arguments like > > "replying fast makes reviewers confident that they have been heard" > > are just less true for oldtimers than for newcomers. > > I think these are two different things. It's probably true that you get > some privileges by being an old timer. But I think it's more in the > sense of "You get to tackle bigger things that may not be done in a > single patch series, and we trust you to not just disappear". > > But with that privilege also comes responsibility. It's those old timers > that newcomers look to, so they need to lead by example. People often are good in some ways and bad in others. You cannot expect all old timers to be perfect examples in every way. And that should be fine. > > Another argument is that oldtimers are more likely to burn out or have > > mental health issues related to their Git work than newcomers. Adding > > a norm that would put pressure on them to work more, or at times they > > would prefer to do other things, significantly increases the burnout > > and mental health risks for them. > > > > So putting pressure on oldtimers to follow a norm that is less > > relevant for them but puts them at greater risk is just a bad idea in > > my opinion. > > This is of course something we must avoid. Nobody is being helped in > case people burn out. There needs to be a middle ground that works for > everyone. My opinion is that in cases like this, there is no norm that works for everyone. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-10 8:25 ` Patrick Steinhardt 2025-07-10 15:29 ` Christian Couder @ 2025-07-10 15:33 ` Junio C Hamano 1 sibling, 0 replies; 65+ messages in thread From: Junio C Hamano @ 2025-07-10 15:33 UTC (permalink / raw) To: Patrick Steinhardt Cc: Christian Couder, git, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Patrick Steinhardt <ps@pks.im> writes: > There are always going to be exceptions, that is of course true. But I > also think that long-time contributors that are employed to work on Git > are somewhat special and don't (typically) fall into the mentioned > groups. From my perspective, it's especially this group of people that > should lead by example and encourage others to behave in a way that is > good for the overall Git community. And leading by example in this > context also means that they should encourage healthy discussions. Thanks. I do not have very much to add to what you already have said. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v4] fast-(import|export): improve on commit signature output format 2025-07-08 5:03 ` Junio C Hamano 2025-07-08 6:38 ` Patrick Steinhardt @ 2025-07-08 10:17 ` Christian Couder 1 sibling, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-08 10:17 UTC (permalink / raw) To: Junio C Hamano Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Tue, Jul 8, 2025 at 7:03 AM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > > On Tue, Jul 8, 2025 at 12:58 AM Junio C Hamano <gitster@pobox.com> wrote: > >> We haven't heard much after a few comments were posted on this > >> latest round, since Elijah's > >> <20250619133630.727274-1-christian.couder@gmail.com>; I understand > >> that it would be the author's turn to respond (the response does not > >> necessarily have to be with an updated iteration). If so, let me > >> mark the topic as Stalled in the draft of the latest issue of the > >> "What's cooking" report. > > > > I will hopefully send a v5 later today. I just sent it and replied to the pending reviews about v4. > Thanks. > > By the way, I noticed that you often do not respond to reviews until > the last minute, at the same time as when you send your next > iteration, or even soon after doing so. Yeah, right. > That is quite different from how other contributors operate, i.e. > respond and engage in discussions triggered by the reviews, and > after people involved in discussion got an (even rough) idea of what > the right next step would be, if not a total consensus, send the > next iteration. > > I do not know which style is more efficient form of cooperation, but > it somewhat makes my job harder, if I do not hear much _heartbeats_ > after I see review comments on the list. I do not mind waiting for > seeing the next round for quite a while---after all, any substantial > (re)work takes time. And responding to reviews may need thinking > things through carefully, which may take some time, so I would not > demand an immediate response, either. But it would be nearly > impossible to feel the current status of such a topic---a few review > comments are seen, the author goes silent for a while, we cannot > tell if the author is working on a new iteration or where the author > and reviewers agree and disagree. Sorry if it makes your job harder. When I work on a number of different things, I alternate between topics. Just after I send a new version of some series to the mailing list, I usually start working on a different topic. These days for example I alternate between this topic and the promisor-remote capability topic. So it seems to me that if I were to respond to reviews right away, I could be switching topics all the time if there are discussions happening on several topics I work on. I know I still have to switch often anyway between topics because I might be pinged internally at GitLab about some issues or because someone I mentor asks me a question privately, etc. And maybe for you or others switching topics often is not an issue, but when topics are quite complex I feel it makes it much harder for me to focus on what I am doing. I don't think I am the only one in this case by the way. > Also a review response that comes at the same time or immediately > after a new iteration is already sent out makes it look like the > author is refusing to continue discussion and reviewers are not > welcome to make follow-up suggestions during such a discussion. Sorry if it looks like this. I am not refusing any discussion or follow up suggestions. As you say above, responding to reviews may need thinking and often working to try things out, and often it seems to me that I cannot really reply properly if I haven't worked enough to try some ideas. Let me take for example the v5 I just sent. It's only by researching and trying different ideas without knowing if they would work that I found (after a long time) a way to write a proper test with both a SHA-1 and a SHA-256 signature on the same commit. It was the same for using "$GIT_DEFAULT_HASH" instead of "sha(1|256)" in the tests. So yeah, I could have replied early with "I will do it in v5." or "I will try to do it in v5." or "Ok" or "I will think about it." to most suggestions I got, but what would have really been the value of a response with mostly those kinds of sentences in it? > Instead, the next iteration comes as a fait accompli, Even if I had replied with mostly "I will do it in v5." or "I will try to do it in v5.", etc, to many suggestions, I could still have found or decided for some reasons to actually implement something else and use those reasons to justify it. Would it have been less of a fait accompli? > and makes it > less useful to continue the review discussion on the previous round > by responding to such a late response. In my opinion the discussion can continue with more useful and higher quality information, as I have worked significantly to think through and try to implement the suggestions that were made or to research and then often implement other solutions. Yeah, it doesn't continue on the previous round, but hopefully the new round is better, so ... ^ permalink raw reply [flat|nested] 65+ messages in thread
* [PATCH v5] fast-(import|export): improve on commit signature output format 2025-06-19 13:36 ` [PATCH v4] " Christian Couder ` (2 preceding siblings ...) 2025-07-07 22:58 ` Junio C Hamano @ 2025-07-08 9:17 ` Christian Couder 2025-07-08 21:58 ` Junio C Hamano ` (2 more replies) 3 siblings, 3 replies; 65+ messages in thread From: Christian Couder @ 2025-07-08 9:17 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for signed-commits, 2025-03-10), added support for signed commits to fast-export and fast-import. When a signed commit is processed, fast-export can output either "gpgsig sha1" or "gpgsig sha256" depending on whether the signed commit uses the SHA-1 or SHA-256 Git object format. However, this implementation has a number of limitations: - the output format was not properly described in the documentation, - the output format is not very informative as it doesn't even say if the signature is an OpenPGP, an SSH, or an X509 signature, - the implementation doesn't support having both one signature on the SHA-1 object and one on the SHA-256 object. Let's improve on these limitations by improving fast-export and fast-import so that: - all the signatures are exported, - at most one signature on the SHA-1 object and one on the SHA-256 are imported, - if there is more than one signature on the SHA-1 object or on the SHA-256 object, fast-import emits a warning for each additional signature, - the output format is "gpgsig <git-hash-algo> <signature-format>", where <git-hash-algo> is the Git object format as before, and <signature-format> is the signature type ("openpgp", "x509", "ssh" or "unknown"), - the output is properly documented. About the output format: - <git-hash-algo> allows to know which representation of the commit was signed (the SHA-1 or the SHA-256 version) which helps with both signature verification and interoperability between repos with different hash functions, - <signature-format> helps tools that process the fast-export stream, so they don't have to parse the ASCII armor to identify the signature type. It could be even better to be able to import more than one signature on the SHA-1 object and on the SHA-256 object, but other parts of Git don't handle that well for now, so this is left for future improvements. Helped-by: brian m. carlson <sandals@crustytoothpaste.net> Helped-by: Elijah Newren <newren@gmail.com> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- This v5 is similar in spirit to v4, especially the format of the "gpgsig ..." command didn't change, but there are a number of improvements: - All the signatures are now exported. On the import side, still at most one signature for SHA-1 and one for SHA-256 are imported (and a warning is still emitted for additional signatures) though. - The code makes sure each signature ends with a LF character. While this is not mandatory, it is encouraged and makes the output more human readable and 'grep'able. - A test with both a SHA-1 and a SHA-256 signature on the same commit has been added. - Some tests check that either "sha1" or "sha256" is in the "gpgsig ..." command instead of matching "sha(1|256)". - The format of the "gpgsig ..." command is better explained both in the commit message and in the fast-import documentation. - There are some typo fixes, lines wrapped, and a few other such small changes. Thanks to brian, Elijah and Junio who commented on the v1 and v2. CI tests: They have all passed: https://github.com/chriscool/git/actions/runs/16136587558 Range-diff with v4: 1: b3d8b13c0e ! 1: 5791617d7d fast-(import|export): improve on commit signature output format @@ Commit message Let's improve on these limitations by improving fast-export and fast-import so that: - - both one signature on the SHA-1 object and one on the SHA-256 - object can be exported and imported, + - all the signatures are exported, + - at most one signature on the SHA-1 object and one on the SHA-256 + are imported, - if there is more than one signature on the SHA-1 object or on - the SHA-256 object, a warning is emitted, + the SHA-256 object, fast-import emits a warning for each + additional signature, - the output format is "gpgsig <git-hash-algo> <signature-format>", where <git-hash-algo> is the Git object format as before, and <signature-format> is the signature type ("openpgp", "x509", - "ssh" or "unknown", + "ssh" or "unknown"), - the output is properly documented. - Note that it could be even better to be able to export and import - more than one signature on the SHA-1 object and on the SHA-256 - object, but other parts of Git don't handle that well for now, so - this is left for future improvements. + About the output format: + + - <git-hash-algo> allows to know which representation of the commit + was signed (the SHA-1 or the SHA-256 version) which helps with + both signature verification and interoperability between repos + with different hash functions, + + - <signature-format> helps tools that process the fast-export + stream, so they don't have to parse the ASCII armor to identify + the signature type. + + It could be even better to be able to import more than one signature + on the SHA-1 object and on the SHA-256 object, but other parts of + Git don't handle that well for now, so this is left for future + improvements. Helped-by: brian m. carlson <sandals@crustytoothpaste.net> Helped-by: Elijah Newren <newren@gmail.com> @@ Documentation/git-fast-export.adoc: resulting tag will have an invalid signature +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit +starts with `gpgsig sha256 ssh`. ++ -+Currently for a given commit, at most one signature for the SHA-1 -+object and one signature for the SHA-256 object are exported, each -+with their respective <git-hash-algo> identifier. A warning is -+emitted for each additional signature found. ++While all the signatures of a commit are exported, an importer may ++choose to accept only some of them. For example ++linkgit:git-fast-import[1] currently stores at most one signature per ++Git hash algorithm in each commit. ++ NOTE: This is highly experimental and the format of the data stream may change in the future without compatibility guarantees. @@ Documentation/git-fast-import.adoc: their syntax. +The `gpgsig` command takes two arguments: + +* `<git-hash-algo>` specifies which Git object format this signature -+ applies to, either `sha1` or `sha256`. ++ applies to, either `sha1` or `sha256`. This allows to know which ++ representation of the commit was signed (the SHA-1 or the SHA-256 ++ version) which helps with both signature verification and ++ interoperability between repos with different hash functions. + +* `<signature-format>` specifies the type of signature, such as -+ `openpgp`, `x509`, `ssh`, or `unknown`. ++ `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for ++ tools that process the stream, so they don't have to parse the ASCII ++ armor to identify the signature type. + +A commit may have at most one signature for the SHA-1 object format +(stored in the "gpgsig" header) and one for the SHA-256 object format @@ builtin/fast-export.c: static const char *find_commit_multiline_header(const cha + if (!signature) + return; + -+ printf("gpgsig %s %s\ndata %u\n%s", ++ printf("gpgsig %s %s\ndata %u\n%s\n", + object_hash, + get_signature_format(signature), + (unsigned)strlen(signature), + signature); +} + -+static void warn_on_extra_sig(const char **pos, struct commit *commit, int is_sha1) ++static const char *append_signatures_for_header(struct string_list *signatures, ++ const char *pos, ++ const char *header, ++ const char *object_hash) +{ -+ const char *header = is_sha1 ? "gpgsig" : "gpgsig-sha256"; -+ const char *extra_sig = find_commit_multiline_header(*pos + 1, header, pos); -+ if (extra_sig) { -+ const char *hash = is_sha1 ? "SHA-1" : "SHA-256"; -+ warning("more than one %s signature found on commit %s, using only the first one", -+ hash, oid_to_hex(&commit->object.oid)); -+ free((char *)extra_sig); ++ const char *signature; ++ const char *start = pos; ++ const char *end = pos; ++ ++ while ((signature = find_commit_multiline_header(start + 1, ++ header, ++ &end))) { ++ string_list_append(signatures, signature)->util = (void *)object_hash; ++ free((char *)signature); ++ start = end; + } ++ ++ return end; +} + static void handle_commit(struct commit *commit, struct rev_info *rev, @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r const char *encoding = NULL; size_t encoding_len; - const char *signature_alg = NULL, *signature = NULL; -+ const char *sig_sha1 = NULL; -+ const char *sig_sha256 = NULL; ++ struct string_list signatures = STRING_LIST_INIT_DUP; const char *message; char *reencoded = NULL; struct commit_list *p; @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r - signature_alg = "sha1"; - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) - signature_alg = "sha256"; -+ const char *sig_cursor = commit_buffer_cursor; -+ const char *after_sha1 = commit_buffer_cursor; -+ const char *after_sha256 = commit_buffer_cursor; -+ -+ /* -+ * Find the first signature for each hash algorithm. -+ * The searches must start from the same position. -+ */ -+ sig_sha1 = find_commit_multiline_header(sig_cursor + 1, -+ "gpgsig", -+ &after_sha1); -+ sig_sha256 = find_commit_multiline_header(sig_cursor + 1, -+ "gpgsig-sha256", -+ &after_sha256); -+ -+ /* Warn on any additional signatures, as they will be ignored. */ -+ if (sig_sha1) -+ warn_on_extra_sig(&after_sha1, commit, 1); -+ if (sig_sha256) -+ warn_on_extra_sig(&after_sha256, commit, 0); -+ ++ const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor, ++ "gpgsig", "sha1"); ++ const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor, ++ "gpgsig-sha256", "sha256"); + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; } @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r (int)(author_end - author), author, (int)(committer_end - committer), committer); - if (signature) { -+ if (sig_sha1 || sig_sha256) { ++ if (signatures.nr) { switch (signed_commit_mode) { case SIGN_ABORT: die("encountered signed commit %s; use " -@@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev, - oid_to_hex(&commit->object.oid)); + "--signed-commits=<mode> to handle it", + oid_to_hex(&commit->object.oid)); + case SIGN_WARN_VERBATIM: +- warning("exporting signed commit %s", +- oid_to_hex(&commit->object.oid)); ++ warning("exporting %"PRIuMAX" signature(s) for commit %s", ++ (uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_VERBATIM: - printf("gpgsig %s\ndata %u\n%s", - signature_alg, - (unsigned)strlen(signature), - signature); -+ print_signature(sig_sha1, "sha1"); -+ print_signature(sig_sha256, "sha256"); ++ for (size_t i = 0; i < signatures.nr; i++) { ++ struct string_list_item *item = &signatures.items[i]; ++ print_signature(item->string, item->util); ++ } break; case SIGN_WARN_STRIP: - warning("stripping signature from commit %s", @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r break; } - free((char *)signature); -+ free((char *)sig_sha1); -+ free((char *)sig_sha256); ++ string_list_clear(&signatures, 0); } if (!reencoded && encoding) printf("encoding %.*s\n", (int)encoding_len, encoding); @@ builtin/fast-import.c: static struct hash_list *parse_merge(unsigned int *count) + const char *hash_type) +{ + if (stored_sig->hash_algo) { -+ warning("Multiple %s signatures found, ignoring additional signature", ++ warning("multiple %s signatures found, " ++ "ignoring additional signature", + hash_type); + strbuf_release(&new_sig->data); + free(new_sig->hash_algo); @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=abort' ' git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && - grep "^gpgsig sha" output && -+ test_grep -E "^gpgsig sha(1|256) openpgp" output && ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && grep "encoding ISO-8859-1" output && ( cd new && @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=verbatim' ' git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && - grep "^gpgsig sha" output && -+ test_grep -E "^gpgsig sha(1|256) openpgp" output && ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && grep "encoding ISO-8859-1" output && test -s err && ( @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' +test_expect_success GPGSM 'round-trip X.509 signed commit' ' + + git fast-export --signed-commits=verbatim x509-signing >output && -+ test_grep -E "^gpgsig sha(1|256) x509" output && ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output && + ( + cd new && + git fast-import && @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' +test_expect_success GPGSSH 'round-trip SSH signed commit' ' + + git fast-export --signed-commits=verbatim ssh-signing >output && -+ test_grep -E "^gpgsig sha(1|256) ssh" output && ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output && + ( + cd new && + git fast-import && @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' test_expect_success 'setup submodule' ' test_config_global protocol.file.allow always && +@@ t/t9350-fast-export.sh: test_expect_success 'fast-export handles --end-of-options' ' + test_cmp expect actual + ' + ++test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' ++ # Create a signed SHA-256 commit ++ git init --object-format=sha256 explicit-sha256 && ++ git -C explicit-sha256 config extensions.compatObjectFormat sha1 && ++ git -C explicit-sha256 checkout -b dual-signed && ++ test_commit -C explicit-sha256 A && ++ echo B >explicit-sha256/B && ++ git -C explicit-sha256 add B && ++ test_tick && ++ git -C explicit-sha256 commit -S -m "signed" B && ++ SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && ++ ++ # Create the corresponding SHA-1 commit ++ SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && ++ ++ # Check that the resulting SHA-1 commit has both signatures ++ echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out && ++ test_grep -E "^gpgsig " out && ++ test_grep -E "^gpgsig-sha256 " out ++' ++ ++test_expect_success GPG 'export and import of doubly signed commit' ' ++ git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && ++ test_grep -E "^gpgsig sha1 openpgp" output && ++ test_grep -E "^gpgsig sha256 openpgp" output && ++ ++ ( ++ cd new && ++ git fast-import && ++ git cat-file commit refs/heads/dual-signed >actual && ++ test_grep -E "^gpgsig " actual && ++ test_grep -E "^gpgsig-sha256 " actual && ++ IMPORTED=$(git rev-parse refs/heads/dual-signed) && ++ if test "$GIT_DEFAULT_HASH" = "sha1" ++ then ++ test $SHA1_B = $IMPORTED ++ else ++ test $SHA256_B = $IMPORTED ++ fi ++ ) <output ++' ++ + test_done Documentation/git-fast-export.adoc | 17 +++++ Documentation/git-fast-import.adoc | 36 +++++++-- builtin/fast-export.c | 62 +++++++++++---- builtin/fast-import.c | 118 +++++++++++++++++++++++------ gpg-interface.c | 12 +++ gpg-interface.h | 12 +++ t/t9350-fast-export.sh | 102 ++++++++++++++++++++++++- 7 files changed, 315 insertions(+), 44 deletions(-) diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 43bbb4f63c..297b57bb2e 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -50,6 +50,23 @@ resulting tag will have an invalid signature. is the same as how earlier versions of this command without this option behaved. + +When exported, a signature starts with: ++ +gpgsig <git-hash-algo> <signature-format> ++ +where <git-hash-algo> is the Git object hash so either "sha1" or +"sha256", and <signature-format> is the signature type, so "openpgp", +"x509", "ssh" or "unknown". ++ +For example, an OpenPGP signature on a SHA-1 commit starts with +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit +starts with `gpgsig sha256 ssh`. ++ +While all the signatures of a commit are exported, an importer may +choose to accept only some of them. For example +linkgit:git-fast-import[1] currently stores at most one signature per +Git hash algorithm in each commit. ++ NOTE: This is highly experimental and the format of the data stream may change in the future without compatibility guarantees. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 250d866652..89dec1108f 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -445,7 +445,7 @@ one). original-oid? ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? 'committer' (SP <name>)? SP LT <email> GT SP <when> LF - ('gpgsig' SP <alg> LF data)? + ('gpgsig' SP <algo> SP <format> LF data)? ('encoding' SP <encoding> LF)? data ('from' SP <commit-ish> LF)? @@ -518,13 +518,37 @@ their syntax. ^^^^^^^^ The optional `gpgsig` command is used to include a PGP/GPG signature -that signs the commit data. +or other cryptographic signature that signs the commit data. -Here <alg> specifies which hashing algorithm is used for this -signature, either `sha1` or `sha256`. +.... + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF + data +.... + +The `gpgsig` command takes two arguments: + +* `<git-hash-algo>` specifies which Git object format this signature + applies to, either `sha1` or `sha256`. This allows to know which + representation of the commit was signed (the SHA-1 or the SHA-256 + version) which helps with both signature verification and + interoperability between repos with different hash functions. + +* `<signature-format>` specifies the type of signature, such as + `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for + tools that process the stream, so they don't have to parse the ASCII + armor to identify the signature type. + +A commit may have at most one signature for the SHA-1 object format +(stored in the "gpgsig" header) and one for the SHA-256 object format +(stored in the "gpgsig-sha256" header). + +See below for a detailed description of the `data` command which +contains the raw signature data. + +Signatures are not yet checked in the current implementation though. -NOTE: This is highly experimental and the format of the data stream may -change in the future without compatibility guarantees. +NOTE: This is highly experimental and the format of the `gpgsig` +command may change in the future without compatibility guarantees. `encoding` ^^^^^^^^^^ diff --git a/builtin/fast-export.c b/builtin/fast-export.c index fcf6b00d5f..7b4e6a6e41 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -29,6 +29,7 @@ #include "quote.h" #include "remote.h" #include "blob.h" +#include "gpg-interface.h" static const char *const fast_export_usage[] = { N_("git fast-export [<rev-list-opts>]"), @@ -652,6 +653,38 @@ static const char *find_commit_multiline_header(const char *msg, return strbuf_detach(&val, NULL); } +static void print_signature(const char *signature, const char *object_hash) +{ + if (!signature) + return; + + printf("gpgsig %s %s\ndata %u\n%s\n", + object_hash, + get_signature_format(signature), + (unsigned)strlen(signature), + signature); +} + +static const char *append_signatures_for_header(struct string_list *signatures, + const char *pos, + const char *header, + const char *object_hash) +{ + const char *signature; + const char *start = pos; + const char *end = pos; + + while ((signature = find_commit_multiline_header(start + 1, + header, + &end))) { + string_list_append(signatures, signature)->util = (void *)object_hash; + free((char *)signature); + start = end; + } + + return end; +} + static void handle_commit(struct commit *commit, struct rev_info *rev, struct string_list *paths_of_changed_objects) { @@ -660,7 +693,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, const char *author, *author_end, *committer, *committer_end; const char *encoding = NULL; size_t encoding_len; - const char *signature_alg = NULL, *signature = NULL; + struct string_list signatures = STRING_LIST_INIT_DUP; const char *message; char *reencoded = NULL; struct commit_list *p; @@ -700,10 +733,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, } if (*commit_buffer_cursor == '\n') { - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) - signature_alg = "sha1"; - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) - signature_alg = "sha256"; + const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor, + "gpgsig", "sha1"); + const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor, + "gpgsig-sha256", "sha256"); + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; } message = strstr(commit_buffer_cursor, "\n\n"); @@ -769,30 +803,30 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, printf("%.*s\n%.*s\n", (int)(author_end - author), author, (int)(committer_end - committer), committer); - if (signature) { + if (signatures.nr) { switch (signed_commit_mode) { case SIGN_ABORT: die("encountered signed commit %s; use " "--signed-commits=<mode> to handle it", oid_to_hex(&commit->object.oid)); case SIGN_WARN_VERBATIM: - warning("exporting signed commit %s", - oid_to_hex(&commit->object.oid)); + warning("exporting %"PRIuMAX" signature(s) for commit %s", + (uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_VERBATIM: - printf("gpgsig %s\ndata %u\n%s", - signature_alg, - (unsigned)strlen(signature), - signature); + for (size_t i = 0; i < signatures.nr; i++) { + struct string_list_item *item = &signatures.items[i]; + print_signature(item->string, item->util); + } break; case SIGN_WARN_STRIP: - warning("stripping signature from commit %s", + warning("stripping signature(s) from commit %s", oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_STRIP: break; } - free((char *)signature); + string_list_clear(&signatures, 0); } if (!reencoded && encoding) printf("encoding %.*s\n", (int)encoding_len, encoding); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index b2839c5f43..332073d0f6 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -29,6 +29,7 @@ #include "commit-reach.h" #include "khash.h" #include "date.h" +#include "gpg-interface.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -2718,15 +2719,87 @@ static struct hash_list *parse_merge(unsigned int *count) return list; } +struct signature_data { + char *hash_algo; /* "sha1" or "sha256" */ + char *sig_format; /* "openpgp", "x509", "ssh", "unknown" */ + struct strbuf data; /* The actual signature data */ +}; + +static void parse_one_signature(struct signature_data *sig, const char *v) +{ + char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */ + char *space = strchr(args, ' '); + + if (!space) + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " + "got 'gpgsig %s'", args); + *space++ = '\0'; + + sig->hash_algo = args; + sig->sig_format = space; + + /* Remove any trailing newline from format */ + space = strchr(sig->sig_format, '\n'); + if (space) + *space = '\0'; + + /* Validate hash algorithm */ + if (strcmp(sig->hash_algo, "sha1") && + strcmp(sig->hash_algo, "sha256")) + die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo); + + /* Validate signature format */ + if (!valid_signature_format(sig->sig_format)) + die("Invalid signature format in gpgsig: '%s'", sig->sig_format); + if (!strcmp(sig->sig_format, "unknown")) + warning("'unknown' signature format in gpgsig"); + + /* Read signature data */ + read_next_command(); + parse_data(&sig->data, 0, NULL); +} + +static void add_gpgsig_to_commit(struct strbuf *commit_data, + const char *header, + struct signature_data *sig) +{ + struct string_list siglines = STRING_LIST_INIT_NODUP; + + if (!sig->hash_algo) + return; + + strbuf_addstr(commit_data, header); + string_list_split_in_place(&siglines, sig->data.buf, "\n", -1); + strbuf_add_separated_string_list(commit_data, "\n ", &siglines); + strbuf_addch(commit_data, '\n'); + string_list_clear(&siglines, 1); + strbuf_release(&sig->data); + free(sig->hash_algo); +} + +static void store_signature(struct signature_data *stored_sig, + struct signature_data *new_sig, + const char *hash_type) +{ + if (stored_sig->hash_algo) { + warning("multiple %s signatures found, " + "ignoring additional signature", + hash_type); + strbuf_release(&new_sig->data); + free(new_sig->hash_algo); + } else { + *stored_sig = *new_sig; + } +} + static void parse_new_commit(const char *arg) { - static struct strbuf sig = STRBUF_INIT; static struct strbuf msg = STRBUF_INIT; - struct string_list siglines = STRING_LIST_INIT_NODUP; + struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT }; + struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT }; struct branch *b; char *author = NULL; char *committer = NULL; - char *sig_alg = NULL; char *encoding = NULL; struct hash_list *merge_list = NULL; unsigned int merge_count; @@ -2750,13 +2823,23 @@ static void parse_new_commit(const char *arg) } if (!committer) die("Expected committer but didn't get one"); - if (skip_prefix(command_buf.buf, "gpgsig ", &v)) { - sig_alg = xstrdup(v); - read_next_command(); - parse_data(&sig, 0, NULL); + + /* Process signatures (up to 2: one "sha1" and one "sha256") */ + while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; + + parse_one_signature(&sig, v); + + if (!strcmp(sig.hash_algo, "sha1")) + store_signature(&sig_sha1, &sig, "SHA-1"); + else if (!strcmp(sig.hash_algo, "sha256")) + store_signature(&sig_sha256, &sig, "SHA-256"); + else + BUG("parse_one_signature() returned unknown hash algo"); + read_next_command(); - } else - strbuf_setlen(&sig, 0); + } + if (skip_prefix(command_buf.buf, "encoding ", &v)) { encoding = xstrdup(v); read_next_command(); @@ -2830,23 +2913,14 @@ static void parse_new_commit(const char *arg) strbuf_addf(&new_data, "encoding %s\n", encoding); - if (sig_alg) { - if (!strcmp(sig_alg, "sha1")) - strbuf_addstr(&new_data, "gpgsig "); - else if (!strcmp(sig_alg, "sha256")) - strbuf_addstr(&new_data, "gpgsig-sha256 "); - else - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); - string_list_split_in_place(&siglines, sig.buf, "\n", -1); - strbuf_add_separated_string_list(&new_data, "\n ", &siglines); - strbuf_addch(&new_data, '\n'); - } + + add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1); + add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256); + strbuf_addch(&new_data, '\n'); strbuf_addbuf(&new_data, &msg); - string_list_clear(&siglines, 1); free(author); free(committer); - free(sig_alg); free(encoding); if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark)) diff --git a/gpg-interface.c b/gpg-interface.c index 0896458de5..6f2d87475f 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -144,6 +144,18 @@ static struct gpg_format *get_format_by_sig(const char *sig) return NULL; } +const char *get_signature_format(const char *buf) +{ + struct gpg_format *format = get_format_by_sig(buf); + return format ? format->name : "unknown"; +} + +int valid_signature_format(const char *format) +{ + return (!!get_format_by_name(format) || + !strcmp(format, "unknown")); +} + void signature_check_clear(struct signature_check *sigc) { FREE_AND_NULL(sigc->payload); diff --git a/gpg-interface.h b/gpg-interface.h index e09f12e8d0..60ddf8bbfa 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -47,6 +47,18 @@ struct signature_check { void signature_check_clear(struct signature_check *sigc); +/* + * Return the format of the signature (like "openpgp", "x509", "ssh" + * or "unknown"). + */ +const char *get_signature_format(const char *buf); + +/* + * Is the signature format valid (like "openpgp", "x509", "ssh" or + * "unknown") + */ +int valid_signature_format(const char *format); + /* * Look at a GPG signed tag object. If such a signature exists, store it in * signature and the signed content in payload. Return 1 if a signature was diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 76619765fc..46700dbc40 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -314,7 +314,7 @@ test_expect_success GPG 'signed-commits=abort' ' test_expect_success GPG 'signed-commits=verbatim' ' git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && grep "encoding ISO-8859-1" output && ( cd new && @@ -328,7 +328,7 @@ test_expect_success GPG 'signed-commits=verbatim' ' test_expect_success GPG 'signed-commits=warn-verbatim' ' git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && grep "encoding ISO-8859-1" output && test -s err && ( @@ -369,6 +369,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' ' +test_expect_success GPGSM 'setup X.509 signed commit' ' + + git checkout -b x509-signing main && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + echo "X.509 content" >file && + git add file && + git commit -S -m "X.509 signed commit" && + X509_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSM 'round-trip X.509 signed commit' ' + + git fast-export --signed-commits=verbatim x509-signing >output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/x509-signing >actual && + grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/x509-signing) && + test $X509_COMMIT = $IMPORTED + ) <output + +' + +test_expect_success GPGSSH 'setup SSH signed commit' ' + + git checkout -b ssh-signing main && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + echo "SSH content" >file && + git add file && + git commit -S -m "SSH signed commit" && + SSH_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSSH 'round-trip SSH signed commit' ' + + git fast-export --signed-commits=verbatim ssh-signing >output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/ssh-signing >actual && + grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && + test $SSH_COMMIT = $IMPORTED + ) <output + +' + test_expect_success 'setup submodule' ' test_config_global protocol.file.allow always && @@ -905,4 +961,46 @@ test_expect_success 'fast-export handles --end-of-options' ' test_cmp expect actual ' +test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' + # Create a signed SHA-256 commit + git init --object-format=sha256 explicit-sha256 && + git -C explicit-sha256 config extensions.compatObjectFormat sha1 && + git -C explicit-sha256 checkout -b dual-signed && + test_commit -C explicit-sha256 A && + echo B >explicit-sha256/B && + git -C explicit-sha256 add B && + test_tick && + git -C explicit-sha256 commit -S -m "signed" B && + SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && + + # Create the corresponding SHA-1 commit + SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && + + # Check that the resulting SHA-1 commit has both signatures + echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out && + test_grep -E "^gpgsig " out && + test_grep -E "^gpgsig-sha256 " out +' + +test_expect_success GPG 'export and import of doubly signed commit' ' + git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && + test_grep -E "^gpgsig sha1 openpgp" output && + test_grep -E "^gpgsig sha256 openpgp" output && + + ( + cd new && + git fast-import && + git cat-file commit refs/heads/dual-signed >actual && + test_grep -E "^gpgsig " actual && + test_grep -E "^gpgsig-sha256 " actual && + IMPORTED=$(git rev-parse refs/heads/dual-signed) && + if test "$GIT_DEFAULT_HASH" = "sha1" + then + test $SHA1_B = $IMPORTED + else + test $SHA256_B = $IMPORTED + fi + ) <output +' + test_done -- 2.50.0.174.gcf836c1ec7 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* Re: [PATCH v5] fast-(import|export): improve on commit signature output format 2025-07-08 9:17 ` [PATCH v5] " Christian Couder @ 2025-07-08 21:58 ` Junio C Hamano 2025-07-08 23:08 ` Elijah Newren 2025-07-09 14:12 ` [PATCH v6] " Christian Couder 2 siblings, 0 replies; 65+ messages in thread From: Junio C Hamano @ 2025-07-08 21:58 UTC (permalink / raw) To: Christian Couder Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for > signed-commits, 2025-03-10), added support for signed commits to > fast-export and fast-import. > ... > It could be even better to be able to import more than one signature > on the SHA-1 object and on the SHA-256 object, but other parts of > Git don't handle that well for now, so this is left for future > improvements. > > Helped-by: brian m. carlson <sandals@crustytoothpaste.net> > Helped-by: Elijah Newren <newren@gmail.com> > Signed-off-by: Christian Couder <chriscool@tuxfamily.org> > --- > > This v5 is similar in spirit to v4, especially the format of the > "gpgsig ..." command didn't change, but there are a number of > improvements: > > - All the signatures are now exported. On the import side, still at > most one signature for SHA-1 and one for SHA-256 are imported (and > a warning is still emitted for additional signatures) though. > > - The code makes sure each signature ends with a LF character. While > this is not mandatory, it is encouraged and makes the output more > human readable and 'grep'able. > > - A test with both a SHA-1 and a SHA-256 signature on the same > commit has been added. > > - Some tests check that either "sha1" or "sha256" is in the "gpgsig > ..." command instead of matching "sha(1|256)". > > - The format of the "gpgsig ..." command is better explained both in > the commit message and in the fast-import documentation. > > - There are some typo fixes, lines wrapped, and a few other such > small changes. Thanks. Will queue. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v5] fast-(import|export): improve on commit signature output format 2025-07-08 9:17 ` [PATCH v5] " Christian Couder 2025-07-08 21:58 ` Junio C Hamano @ 2025-07-08 23:08 ` Elijah Newren 2025-07-09 0:03 ` Junio C Hamano 2025-07-09 10:15 ` Christian Couder 2025-07-09 14:12 ` [PATCH v6] " Christian Couder 2 siblings, 2 replies; 65+ messages in thread From: Elijah Newren @ 2025-07-08 23:08 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Tue, Jul 8, 2025 at 2:18 AM Christian Couder <christian.couder@gmail.com> wrote: > > A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for > signed-commits, 2025-03-10), added support for signed commits to > fast-export and fast-import. > > When a signed commit is processed, fast-export can output either > "gpgsig sha1" or "gpgsig sha256" depending on whether the signed > commit uses the SHA-1 or SHA-256 Git object format. > > However, this implementation has a number of limitations: > > - the output format was not properly described in the documentation, > - the output format is not very informative as it doesn't even say > if the signature is an OpenPGP, an SSH, or an X509 signature, > - the implementation doesn't support having both one signature on > the SHA-1 object and one on the SHA-256 object. > > Let's improve on these limitations by improving fast-export and > fast-import so that: > > - all the signatures are exported, > - at most one signature on the SHA-1 object and one on the SHA-256 > are imported, > - if there is more than one signature on the SHA-1 object or on > the SHA-256 object, fast-import emits a warning for each > additional signature, > - the output format is "gpgsig <git-hash-algo> <signature-format>", > where <git-hash-algo> is the Git object format as before, and > <signature-format> is the signature type ("openpgp", "x509", > "ssh" or "unknown"), > - the output is properly documented. > > About the output format: > > - <git-hash-algo> allows to know which representation of the commit > was signed (the SHA-1 or the SHA-256 version) which helps with > both signature verification and interoperability between repos > with different hash functions, > > - <signature-format> helps tools that process the fast-export > stream, so they don't have to parse the ASCII armor to identify > the signature type. > > It could be even better to be able to import more than one signature > on the SHA-1 object and on the SHA-256 object, but other parts of > Git don't handle that well for now, so this is left for future > improvements. > > Helped-by: brian m. carlson <sandals@crustytoothpaste.net> > Helped-by: Elijah Newren <newren@gmail.com> > Signed-off-by: Christian Couder <chriscool@tuxfamily.org> > --- > > This v5 is similar in spirit to v4, especially the format of the > "gpgsig ..." command didn't change, but there are a number of > improvements: > > - All the signatures are now exported. On the import side, still at > most one signature for SHA-1 and one for SHA-256 are imported (and > a warning is still emitted for additional signatures) though. > > - The code makes sure each signature ends with a LF character. While > this is not mandatory, it is encouraged and makes the output more > human readable and 'grep'able. > > - A test with both a SHA-1 and a SHA-256 signature on the same > commit has been added. > > - Some tests check that either "sha1" or "sha256" is in the "gpgsig > ..." command instead of matching "sha(1|256)". > > - The format of the "gpgsig ..." command is better explained both in > the commit message and in the fast-import documentation. > > - There are some typo fixes, lines wrapped, and a few other such > small changes. > > Thanks to brian, Elijah and Junio who commented on the v1 and v2. > > CI tests: > > They have all passed: > > https://github.com/chriscool/git/actions/runs/16136587558 > > Range-diff with v4: > > 1: b3d8b13c0e ! 1: 5791617d7d fast-(import|export): improve on commit signature output format > @@ Commit message > Let's improve on these limitations by improving fast-export and > fast-import so that: > > - - both one signature on the SHA-1 object and one on the SHA-256 > - object can be exported and imported, > + - all the signatures are exported, > + - at most one signature on the SHA-1 object and one on the SHA-256 > + are imported, > - if there is more than one signature on the SHA-1 object or on > - the SHA-256 object, a warning is emitted, > + the SHA-256 object, fast-import emits a warning for each > + additional signature, > - the output format is "gpgsig <git-hash-algo> <signature-format>", > where <git-hash-algo> is the Git object format as before, and > <signature-format> is the signature type ("openpgp", "x509", > - "ssh" or "unknown", > + "ssh" or "unknown"), > - the output is properly documented. > > - Note that it could be even better to be able to export and import > - more than one signature on the SHA-1 object and on the SHA-256 > - object, but other parts of Git don't handle that well for now, so > - this is left for future improvements. > + About the output format: > + > + - <git-hash-algo> allows to know which representation of the commit > + was signed (the SHA-1 or the SHA-256 version) which helps with > + both signature verification and interoperability between repos > + with different hash functions, > + > + - <signature-format> helps tools that process the fast-export > + stream, so they don't have to parse the ASCII armor to identify > + the signature type. > + > + It could be even better to be able to import more than one signature > + on the SHA-1 object and on the SHA-256 object, but other parts of > + Git don't handle that well for now, so this is left for future > + improvements. > > Helped-by: brian m. carlson <sandals@crustytoothpaste.net> > Helped-by: Elijah Newren <newren@gmail.com> > @@ Documentation/git-fast-export.adoc: resulting tag will have an invalid signature > +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit > +starts with `gpgsig sha256 ssh`. > ++ > -+Currently for a given commit, at most one signature for the SHA-1 > -+object and one signature for the SHA-256 object are exported, each > -+with their respective <git-hash-algo> identifier. A warning is > -+emitted for each additional signature found. > ++While all the signatures of a commit are exported, an importer may > ++choose to accept only some of them. For example > ++linkgit:git-fast-import[1] currently stores at most one signature per > ++Git hash algorithm in each commit. > ++ > NOTE: This is highly experimental and the format of the data stream may > change in the future without compatibility guarantees. > @@ Documentation/git-fast-import.adoc: their syntax. > +The `gpgsig` command takes two arguments: > + > +* `<git-hash-algo>` specifies which Git object format this signature > -+ applies to, either `sha1` or `sha256`. > ++ applies to, either `sha1` or `sha256`. This allows to know which > ++ representation of the commit was signed (the SHA-1 or the SHA-256 > ++ version) which helps with both signature verification and > ++ interoperability between repos with different hash functions. > + > +* `<signature-format>` specifies the type of signature, such as > -+ `openpgp`, `x509`, `ssh`, or `unknown`. > ++ `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for > ++ tools that process the stream, so they don't have to parse the ASCII > ++ armor to identify the signature type. > + > +A commit may have at most one signature for the SHA-1 object format > +(stored in the "gpgsig" header) and one for the SHA-256 object format > @@ builtin/fast-export.c: static const char *find_commit_multiline_header(const cha > + if (!signature) > + return; > + > -+ printf("gpgsig %s %s\ndata %u\n%s", > ++ printf("gpgsig %s %s\ndata %u\n%s\n", > + object_hash, > + get_signature_format(signature), > + (unsigned)strlen(signature), > + signature); > +} > + > -+static void warn_on_extra_sig(const char **pos, struct commit *commit, int is_sha1) > ++static const char *append_signatures_for_header(struct string_list *signatures, > ++ const char *pos, > ++ const char *header, > ++ const char *object_hash) > +{ > -+ const char *header = is_sha1 ? "gpgsig" : "gpgsig-sha256"; > -+ const char *extra_sig = find_commit_multiline_header(*pos + 1, header, pos); > -+ if (extra_sig) { > -+ const char *hash = is_sha1 ? "SHA-1" : "SHA-256"; > -+ warning("more than one %s signature found on commit %s, using only the first one", > -+ hash, oid_to_hex(&commit->object.oid)); > -+ free((char *)extra_sig); > ++ const char *signature; > ++ const char *start = pos; > ++ const char *end = pos; > ++ > ++ while ((signature = find_commit_multiline_header(start + 1, > ++ header, > ++ &end))) { > ++ string_list_append(signatures, signature)->util = (void *)object_hash; > ++ free((char *)signature); > ++ start = end; > + } > ++ > ++ return end; > +} > + > static void handle_commit(struct commit *commit, struct rev_info *rev, > @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r > const char *encoding = NULL; > size_t encoding_len; > - const char *signature_alg = NULL, *signature = NULL; > -+ const char *sig_sha1 = NULL; > -+ const char *sig_sha256 = NULL; > ++ struct string_list signatures = STRING_LIST_INIT_DUP; > const char *message; > char *reencoded = NULL; > struct commit_list *p; > @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r > - signature_alg = "sha1"; > - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) > - signature_alg = "sha256"; > -+ const char *sig_cursor = commit_buffer_cursor; > -+ const char *after_sha1 = commit_buffer_cursor; > -+ const char *after_sha256 = commit_buffer_cursor; > -+ > -+ /* > -+ * Find the first signature for each hash algorithm. > -+ * The searches must start from the same position. > -+ */ > -+ sig_sha1 = find_commit_multiline_header(sig_cursor + 1, > -+ "gpgsig", > -+ &after_sha1); > -+ sig_sha256 = find_commit_multiline_header(sig_cursor + 1, > -+ "gpgsig-sha256", > -+ &after_sha256); > -+ > -+ /* Warn on any additional signatures, as they will be ignored. */ > -+ if (sig_sha1) > -+ warn_on_extra_sig(&after_sha1, commit, 1); > -+ if (sig_sha256) > -+ warn_on_extra_sig(&after_sha256, commit, 0); > -+ > ++ const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor, > ++ "gpgsig", "sha1"); > ++ const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor, > ++ "gpgsig-sha256", "sha256"); > + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; > } > > @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r > (int)(author_end - author), author, > (int)(committer_end - committer), committer); > - if (signature) { > -+ if (sig_sha1 || sig_sha256) { > ++ if (signatures.nr) { > switch (signed_commit_mode) { > case SIGN_ABORT: > die("encountered signed commit %s; use " > -@@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct rev_info *rev, > - oid_to_hex(&commit->object.oid)); > + "--signed-commits=<mode> to handle it", > + oid_to_hex(&commit->object.oid)); > + case SIGN_WARN_VERBATIM: > +- warning("exporting signed commit %s", > +- oid_to_hex(&commit->object.oid)); > ++ warning("exporting %"PRIuMAX" signature(s) for commit %s", > ++ (uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid)); > /* fallthru */ > case SIGN_VERBATIM: > - printf("gpgsig %s\ndata %u\n%s", > - signature_alg, > - (unsigned)strlen(signature), > - signature); > -+ print_signature(sig_sha1, "sha1"); > -+ print_signature(sig_sha256, "sha256"); > ++ for (size_t i = 0; i < signatures.nr; i++) { > ++ struct string_list_item *item = &signatures.items[i]; > ++ print_signature(item->string, item->util); > ++ } > break; > case SIGN_WARN_STRIP: > - warning("stripping signature from commit %s", > @@ builtin/fast-export.c: static void handle_commit(struct commit *commit, struct r > break; > } > - free((char *)signature); > -+ free((char *)sig_sha1); > -+ free((char *)sig_sha256); > ++ string_list_clear(&signatures, 0); > } > if (!reencoded && encoding) > printf("encoding %.*s\n", (int)encoding_len, encoding); > @@ builtin/fast-import.c: static struct hash_list *parse_merge(unsigned int *count) > + const char *hash_type) > +{ > + if (stored_sig->hash_algo) { > -+ warning("Multiple %s signatures found, ignoring additional signature", > ++ warning("multiple %s signatures found, " > ++ "ignoring additional signature", > + hash_type); > + strbuf_release(&new_sig->data); > + free(new_sig->hash_algo); > @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=abort' ' > > git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && > - grep "^gpgsig sha" output && > -+ test_grep -E "^gpgsig sha(1|256) openpgp" output && > ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && > grep "encoding ISO-8859-1" output && > ( > cd new && > @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=verbatim' ' > > git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && > - grep "^gpgsig sha" output && > -+ test_grep -E "^gpgsig sha(1|256) openpgp" output && > ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && > grep "encoding ISO-8859-1" output && > test -s err && > ( > @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' > +test_expect_success GPGSM 'round-trip X.509 signed commit' ' > + > + git fast-export --signed-commits=verbatim x509-signing >output && > -+ test_grep -E "^gpgsig sha(1|256) x509" output && > ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output && > + ( > + cd new && > + git fast-import && > @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' > +test_expect_success GPGSSH 'round-trip SSH signed commit' ' > + > + git fast-export --signed-commits=verbatim ssh-signing >output && > -+ test_grep -E "^gpgsig sha(1|256) ssh" output && > ++ test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output && > + ( > + cd new && > + git fast-import && > @@ t/t9350-fast-export.sh: test_expect_success GPG 'signed-commits=warn-strip' ' > test_expect_success 'setup submodule' ' > > test_config_global protocol.file.allow always && > +@@ t/t9350-fast-export.sh: test_expect_success 'fast-export handles --end-of-options' ' > + test_cmp expect actual > + ' > + > ++test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' > ++ # Create a signed SHA-256 commit > ++ git init --object-format=sha256 explicit-sha256 && > ++ git -C explicit-sha256 config extensions.compatObjectFormat sha1 && > ++ git -C explicit-sha256 checkout -b dual-signed && > ++ test_commit -C explicit-sha256 A && > ++ echo B >explicit-sha256/B && > ++ git -C explicit-sha256 add B && > ++ test_tick && > ++ git -C explicit-sha256 commit -S -m "signed" B && > ++ SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && > ++ > ++ # Create the corresponding SHA-1 commit > ++ SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && > ++ > ++ # Check that the resulting SHA-1 commit has both signatures > ++ echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out && > ++ test_grep -E "^gpgsig " out && > ++ test_grep -E "^gpgsig-sha256 " out > ++' > ++ > ++test_expect_success GPG 'export and import of doubly signed commit' ' > ++ git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && > ++ test_grep -E "^gpgsig sha1 openpgp" output && > ++ test_grep -E "^gpgsig sha256 openpgp" output && > ++ > ++ ( > ++ cd new && > ++ git fast-import && > ++ git cat-file commit refs/heads/dual-signed >actual && > ++ test_grep -E "^gpgsig " actual && > ++ test_grep -E "^gpgsig-sha256 " actual && > ++ IMPORTED=$(git rev-parse refs/heads/dual-signed) && > ++ if test "$GIT_DEFAULT_HASH" = "sha1" > ++ then > ++ test $SHA1_B = $IMPORTED > ++ else > ++ test $SHA256_B = $IMPORTED > ++ fi > ++ ) <output > ++' > ++ > + test_done > > > Documentation/git-fast-export.adoc | 17 +++++ > Documentation/git-fast-import.adoc | 36 +++++++-- > builtin/fast-export.c | 62 +++++++++++---- > builtin/fast-import.c | 118 +++++++++++++++++++++++------ > gpg-interface.c | 12 +++ > gpg-interface.h | 12 +++ > t/t9350-fast-export.sh | 102 ++++++++++++++++++++++++- > 7 files changed, 315 insertions(+), 44 deletions(-) > > diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc > index 43bbb4f63c..297b57bb2e 100644 > --- a/Documentation/git-fast-export.adoc > +++ b/Documentation/git-fast-export.adoc > @@ -50,6 +50,23 @@ resulting tag will have an invalid signature. > is the same as how earlier versions of this command without > this option behaved. > + > +When exported, a signature starts with: > ++ > +gpgsig <git-hash-algo> <signature-format> > ++ > +where <git-hash-algo> is the Git object hash so either "sha1" or > +"sha256", and <signature-format> is the signature type, so "openpgp", > +"x509", "ssh" or "unknown". > ++ > +For example, an OpenPGP signature on a SHA-1 commit starts with > +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit > +starts with `gpgsig sha256 ssh`. > ++ > +While all the signatures of a commit are exported, an importer may > +choose to accept only some of them. For example > +linkgit:git-fast-import[1] currently stores at most one signature per > +Git hash algorithm in each commit. > ++ > NOTE: This is highly experimental and the format of the data stream may > change in the future without compatibility guarantees. > > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc > index 250d866652..89dec1108f 100644 > --- a/Documentation/git-fast-import.adoc > +++ b/Documentation/git-fast-import.adoc > @@ -445,7 +445,7 @@ one). > original-oid? > ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? > 'committer' (SP <name>)? SP LT <email> GT SP <when> LF > - ('gpgsig' SP <alg> LF data)? > + ('gpgsig' SP <algo> SP <format> LF data)? > ('encoding' SP <encoding> LF)? > data > ('from' SP <commit-ish> LF)? > @@ -518,13 +518,37 @@ their syntax. > ^^^^^^^^ > > The optional `gpgsig` command is used to include a PGP/GPG signature > -that signs the commit data. > +or other cryptographic signature that signs the commit data. > > -Here <alg> specifies which hashing algorithm is used for this > -signature, either `sha1` or `sha256`. > +.... > + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF > + data Should the `data` be moved to the line above, just to make it clear it's associated with it? (Similar to the first line you changed in git-fast-import.adoc?) > +.... > + > +The `gpgsig` command takes two arguments: > + > +* `<git-hash-algo>` specifies which Git object format this signature > + applies to, either `sha1` or `sha256`. This allows to know which > + representation of the commit was signed (the SHA-1 or the SHA-256 > + version) which helps with both signature verification and > + interoperability between repos with different hash functions. Should there also be a note added that fast-import is limited on what signatures it can verify if extensions.compatObjectFormat is not set? > + > +* `<signature-format>` specifies the type of signature, such as > + `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for > + tools that process the stream, so they don't have to parse the ASCII > + armor to identify the signature type. > + > +A commit may have at most one signature for the SHA-1 object format > +(stored in the "gpgsig" header) and one for the SHA-256 object format > +(stored in the "gpgsig-sha256" header). > + > +See below for a detailed description of the `data` command which > +contains the raw signature data. > + > +Signatures are not yet checked in the current implementation though. ...or maybe that extra note could be added as a parenthetical comment here for now? > -NOTE: This is highly experimental and the format of the data stream may > -change in the future without compatibility guarantees. > +NOTE: This is highly experimental and the format of the `gpgsig` > +command may change in the future without compatibility guarantees. > > `encoding` > ^^^^^^^^^^ > diff --git a/builtin/fast-export.c b/builtin/fast-export.c > index fcf6b00d5f..7b4e6a6e41 100644 > --- a/builtin/fast-export.c > +++ b/builtin/fast-export.c > @@ -29,6 +29,7 @@ > #include "quote.h" > #include "remote.h" > #include "blob.h" > +#include "gpg-interface.h" > > static const char *const fast_export_usage[] = { > N_("git fast-export [<rev-list-opts>]"), > @@ -652,6 +653,38 @@ static const char *find_commit_multiline_header(const char *msg, > return strbuf_detach(&val, NULL); > } > > +static void print_signature(const char *signature, const char *object_hash) > +{ > + if (!signature) > + return; > + > + printf("gpgsig %s %s\ndata %u\n%s\n", > + object_hash, > + get_signature_format(signature), > + (unsigned)strlen(signature), > + signature); > +} > + > +static const char *append_signatures_for_header(struct string_list *signatures, > + const char *pos, > + const char *header, > + const char *object_hash) > +{ > + const char *signature; > + const char *start = pos; > + const char *end = pos; > + > + while ((signature = find_commit_multiline_header(start + 1, > + header, > + &end))) { > + string_list_append(signatures, signature)->util = (void *)object_hash; > + free((char *)signature); > + start = end; > + } > + > + return end; > +} > + > static void handle_commit(struct commit *commit, struct rev_info *rev, > struct string_list *paths_of_changed_objects) > { > @@ -660,7 +693,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, > const char *author, *author_end, *committer, *committer_end; > const char *encoding = NULL; > size_t encoding_len; > - const char *signature_alg = NULL, *signature = NULL; > + struct string_list signatures = STRING_LIST_INIT_DUP; > const char *message; > char *reencoded = NULL; > struct commit_list *p; > @@ -700,10 +733,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, > } > > if (*commit_buffer_cursor == '\n') { > - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) > - signature_alg = "sha1"; > - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) > - signature_alg = "sha256"; > + const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor, > + "gpgsig", "sha1"); > + const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor, > + "gpgsig-sha256", "sha256"); > + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; > } > > message = strstr(commit_buffer_cursor, "\n\n"); > @@ -769,30 +803,30 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, > printf("%.*s\n%.*s\n", > (int)(author_end - author), author, > (int)(committer_end - committer), committer); > - if (signature) { > + if (signatures.nr) { > switch (signed_commit_mode) { > case SIGN_ABORT: > die("encountered signed commit %s; use " > "--signed-commits=<mode> to handle it", > oid_to_hex(&commit->object.oid)); > case SIGN_WARN_VERBATIM: > - warning("exporting signed commit %s", > - oid_to_hex(&commit->object.oid)); > + warning("exporting %"PRIuMAX" signature(s) for commit %s", > + (uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid)); > /* fallthru */ > case SIGN_VERBATIM: > - printf("gpgsig %s\ndata %u\n%s", > - signature_alg, > - (unsigned)strlen(signature), > - signature); > + for (size_t i = 0; i < signatures.nr; i++) { > + struct string_list_item *item = &signatures.items[i]; > + print_signature(item->string, item->util); > + } > break; > case SIGN_WARN_STRIP: > - warning("stripping signature from commit %s", > + warning("stripping signature(s) from commit %s", Nice catch; that's the kind of thing that could be easily overlooked. > oid_to_hex(&commit->object.oid)); > /* fallthru */ > case SIGN_STRIP: > break; > } > - free((char *)signature); > + string_list_clear(&signatures, 0); > } > if (!reencoded && encoding) > printf("encoding %.*s\n", (int)encoding_len, encoding); > diff --git a/builtin/fast-import.c b/builtin/fast-import.c > index b2839c5f43..332073d0f6 100644 > --- a/builtin/fast-import.c > +++ b/builtin/fast-import.c > @@ -29,6 +29,7 @@ > #include "commit-reach.h" > #include "khash.h" > #include "date.h" > +#include "gpg-interface.h" > > #define PACK_ID_BITS 16 > #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) > @@ -2718,15 +2719,87 @@ static struct hash_list *parse_merge(unsigned int *count) > return list; > } > > +struct signature_data { > + char *hash_algo; /* "sha1" or "sha256" */ > + char *sig_format; /* "openpgp", "x509", "ssh", "unknown" */ > + struct strbuf data; /* The actual signature data */ > +}; > + > +static void parse_one_signature(struct signature_data *sig, const char *v) > +{ > + char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */ > + char *space = strchr(args, ' '); > + > + if (!space) > + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " > + "got 'gpgsig %s'", args); > + *space++ = '\0'; > + > + sig->hash_algo = args; > + sig->sig_format = space; Really minor nitpick, but it might be clearer to pre-increment space here than to increment it above. > + > + /* Remove any trailing newline from format */ > + space = strchr(sig->sig_format, '\n'); > + if (space) > + *space = '\0'; > + > + /* Validate hash algorithm */ > + if (strcmp(sig->hash_algo, "sha1") && > + strcmp(sig->hash_algo, "sha256")) > + die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo); > + > + /* Validate signature format */ > + if (!valid_signature_format(sig->sig_format)) > + die("Invalid signature format in gpgsig: '%s'", sig->sig_format); > + if (!strcmp(sig->sig_format, "unknown")) > + warning("'unknown' signature format in gpgsig"); > + > + /* Read signature data */ > + read_next_command(); > + parse_data(&sig->data, 0, NULL); > +} > + > +static void add_gpgsig_to_commit(struct strbuf *commit_data, > + const char *header, > + struct signature_data *sig) > +{ > + struct string_list siglines = STRING_LIST_INIT_NODUP; > + > + if (!sig->hash_algo) > + return; > + > + strbuf_addstr(commit_data, header); > + string_list_split_in_place(&siglines, sig->data.buf, "\n", -1); > + strbuf_add_separated_string_list(commit_data, "\n ", &siglines); > + strbuf_addch(commit_data, '\n'); > + string_list_clear(&siglines, 1); > + strbuf_release(&sig->data); > + free(sig->hash_algo); > +} > + > +static void store_signature(struct signature_data *stored_sig, > + struct signature_data *new_sig, > + const char *hash_type) > +{ > + if (stored_sig->hash_algo) { > + warning("multiple %s signatures found, " > + "ignoring additional signature", > + hash_type); > + strbuf_release(&new_sig->data); > + free(new_sig->hash_algo); > + } else { > + *stored_sig = *new_sig; > + } > +} > + > static void parse_new_commit(const char *arg) > { > - static struct strbuf sig = STRBUF_INIT; > static struct strbuf msg = STRBUF_INIT; > - struct string_list siglines = STRING_LIST_INIT_NODUP; > + struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT }; > + struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT }; > struct branch *b; > char *author = NULL; > char *committer = NULL; > - char *sig_alg = NULL; > char *encoding = NULL; > struct hash_list *merge_list = NULL; > unsigned int merge_count; > @@ -2750,13 +2823,23 @@ static void parse_new_commit(const char *arg) > } > if (!committer) > die("Expected committer but didn't get one"); > - if (skip_prefix(command_buf.buf, "gpgsig ", &v)) { > - sig_alg = xstrdup(v); > - read_next_command(); > - parse_data(&sig, 0, NULL); > + > + /* Process signatures (up to 2: one "sha1" and one "sha256") */ > + while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { > + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; > + > + parse_one_signature(&sig, v); > + > + if (!strcmp(sig.hash_algo, "sha1")) > + store_signature(&sig_sha1, &sig, "SHA-1"); > + else if (!strcmp(sig.hash_algo, "sha256")) > + store_signature(&sig_sha256, &sig, "SHA-256"); > + else > + BUG("parse_one_signature() returned unknown hash algo"); > + > read_next_command(); > - } else > - strbuf_setlen(&sig, 0); > + } > + > if (skip_prefix(command_buf.buf, "encoding ", &v)) { > encoding = xstrdup(v); > read_next_command(); > @@ -2830,23 +2913,14 @@ static void parse_new_commit(const char *arg) > strbuf_addf(&new_data, > "encoding %s\n", > encoding); > - if (sig_alg) { > - if (!strcmp(sig_alg, "sha1")) > - strbuf_addstr(&new_data, "gpgsig "); > - else if (!strcmp(sig_alg, "sha256")) > - strbuf_addstr(&new_data, "gpgsig-sha256 "); > - else > - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); > - string_list_split_in_place(&siglines, sig.buf, "\n", -1); > - strbuf_add_separated_string_list(&new_data, "\n ", &siglines); > - strbuf_addch(&new_data, '\n'); > - } > + > + add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1); > + add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256); > + > strbuf_addch(&new_data, '\n'); > strbuf_addbuf(&new_data, &msg); > - string_list_clear(&siglines, 1); > free(author); > free(committer); > - free(sig_alg); > free(encoding); > > if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark)) > diff --git a/gpg-interface.c b/gpg-interface.c > index 0896458de5..6f2d87475f 100644 > --- a/gpg-interface.c > +++ b/gpg-interface.c > @@ -144,6 +144,18 @@ static struct gpg_format *get_format_by_sig(const char *sig) > return NULL; > } > > +const char *get_signature_format(const char *buf) > +{ > + struct gpg_format *format = get_format_by_sig(buf); > + return format ? format->name : "unknown"; > +} > + > +int valid_signature_format(const char *format) > +{ > + return (!!get_format_by_name(format) || > + !strcmp(format, "unknown")); > +} > + > void signature_check_clear(struct signature_check *sigc) > { > FREE_AND_NULL(sigc->payload); > diff --git a/gpg-interface.h b/gpg-interface.h > index e09f12e8d0..60ddf8bbfa 100644 > --- a/gpg-interface.h > +++ b/gpg-interface.h > @@ -47,6 +47,18 @@ struct signature_check { > > void signature_check_clear(struct signature_check *sigc); > > +/* > + * Return the format of the signature (like "openpgp", "x509", "ssh" > + * or "unknown"). > + */ > +const char *get_signature_format(const char *buf); > + > +/* > + * Is the signature format valid (like "openpgp", "x509", "ssh" or > + * "unknown") > + */ > +int valid_signature_format(const char *format); > + > /* > * Look at a GPG signed tag object. If such a signature exists, store it in > * signature and the signed content in payload. Return 1 if a signature was > diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh > index 76619765fc..46700dbc40 100755 > --- a/t/t9350-fast-export.sh > +++ b/t/t9350-fast-export.sh > @@ -314,7 +314,7 @@ test_expect_success GPG 'signed-commits=abort' ' > test_expect_success GPG 'signed-commits=verbatim' ' > > git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && > - grep "^gpgsig sha" output && > + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && > grep "encoding ISO-8859-1" output && > ( > cd new && > @@ -328,7 +328,7 @@ test_expect_success GPG 'signed-commits=verbatim' ' > test_expect_success GPG 'signed-commits=warn-verbatim' ' > > git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && > - grep "^gpgsig sha" output && > + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && > grep "encoding ISO-8859-1" output && > test -s err && > ( > @@ -369,6 +369,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' > > ' > > +test_expect_success GPGSM 'setup X.509 signed commit' ' > + > + git checkout -b x509-signing main && > + test_config gpg.format x509 && > + test_config user.signingkey $GIT_COMMITTER_EMAIL && > + echo "X.509 content" >file && > + git add file && > + git commit -S -m "X.509 signed commit" && > + X509_COMMIT=$(git rev-parse HEAD) && > + git checkout main > + > +' > + > +test_expect_success GPGSM 'round-trip X.509 signed commit' ' > + > + git fast-export --signed-commits=verbatim x509-signing >output && > + test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output && > + ( > + cd new && > + git fast-import && > + git cat-file commit refs/heads/x509-signing >actual && > + grep "^gpgsig" actual && > + IMPORTED=$(git rev-parse refs/heads/x509-signing) && > + test $X509_COMMIT = $IMPORTED > + ) <output > + > +' > + > +test_expect_success GPGSSH 'setup SSH signed commit' ' > + > + git checkout -b ssh-signing main && > + test_config gpg.format ssh && > + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && > + echo "SSH content" >file && > + git add file && > + git commit -S -m "SSH signed commit" && > + SSH_COMMIT=$(git rev-parse HEAD) && > + git checkout main > + > +' > + > +test_expect_success GPGSSH 'round-trip SSH signed commit' ' > + > + git fast-export --signed-commits=verbatim ssh-signing >output && > + test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output && > + ( > + cd new && > + git fast-import && > + git cat-file commit refs/heads/ssh-signing >actual && > + grep "^gpgsig" actual && > + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && > + test $SSH_COMMIT = $IMPORTED > + ) <output > + > +' > + > test_expect_success 'setup submodule' ' > > test_config_global protocol.file.allow always && > @@ -905,4 +961,46 @@ test_expect_success 'fast-export handles --end-of-options' ' > test_cmp expect actual > ' > > +test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' > + # Create a signed SHA-256 commit > + git init --object-format=sha256 explicit-sha256 && > + git -C explicit-sha256 config extensions.compatObjectFormat sha1 && > + git -C explicit-sha256 checkout -b dual-signed && > + test_commit -C explicit-sha256 A && > + echo B >explicit-sha256/B && > + git -C explicit-sha256 add B && > + test_tick && > + git -C explicit-sha256 commit -S -m "signed" B && > + SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && > + > + # Create the corresponding SHA-1 commit > + SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && > + > + # Check that the resulting SHA-1 commit has both signatures > + echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out && > + test_grep -E "^gpgsig " out && > + test_grep -E "^gpgsig-sha256 " out > +' > + > +test_expect_success GPG 'export and import of doubly signed commit' ' > + git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && > + test_grep -E "^gpgsig sha1 openpgp" output && > + test_grep -E "^gpgsig sha256 openpgp" output && > + > + ( > + cd new && > + git fast-import && > + git cat-file commit refs/heads/dual-signed >actual && > + test_grep -E "^gpgsig " actual && > + test_grep -E "^gpgsig-sha256 " actual && > + IMPORTED=$(git rev-parse refs/heads/dual-signed) && > + if test "$GIT_DEFAULT_HASH" = "sha1" > + then > + test $SHA1_B = $IMPORTED > + else > + test $SHA256_B = $IMPORTED > + fi > + ) <output This last bit seems a bit fragile; could the redirection of output to the stdin of `git fast-import` be made specific to that one line instead of to the whole range of commands? Otherwise, I very much appreciate the work to create a testcase with both signature types on a single commit. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v5] fast-(import|export): improve on commit signature output format 2025-07-08 23:08 ` Elijah Newren @ 2025-07-09 0:03 ` Junio C Hamano 2025-07-09 0:10 ` Elijah Newren 2025-07-09 10:18 ` Christian Couder 2025-07-09 10:15 ` Christian Couder 1 sibling, 2 replies; 65+ messages in thread From: Junio C Hamano @ 2025-07-09 0:03 UTC (permalink / raw) To: Elijah Newren Cc: Christian Couder, git, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Elijah Newren <newren@gmail.com> writes: >> + if (!space) >> + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " >> + "got 'gpgsig %s'", args); >> + *space++ = '\0'; >> + >> + sig->hash_algo = args; >> + sig->sig_format = space; > > Really minor nitpick, but it might be clearer to pre-increment space > here than to increment it above. FWIW, I find what Christian wrote easier to follow. We find a " " in the buffer, point it with a pointer and NUL-terminate the substring. We know we want to further process bytes that follow, so the pointer is post-incremented after the NUL-termination. The next user of that pointer relies on the fact that the previous user concluded its use with that post-increment. If 'space' variable were named more genericly, like '*cp', it would have been perfect. Perhaps only the first half of the code was written first, and it looked for a space, so the name was chosen, but then later ... >> + >> + /* Remove any trailing newline from format */ >> + space = strchr(sig->sig_format, '\n'); ... it is used to point at a LF X-<, at which time the author could have renamed it to keep readers' sanity ;-) >> + if (space) >> + *space = '\0'; I also wonder what should (not "does", as I can see that the code does not do anything) happen if we do not find the LF we were looking for. Is the caller of this function so loosely written that it may or may not guarantee that the data it calls this function with is properly terminated? >> +test_expect_success GPG 'export and import of doubly signed commit' ' >> + git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && >> + test_grep -E "^gpgsig sha1 openpgp" output && >> + test_grep -E "^gpgsig sha256 openpgp" output && >> + >> + ( >> + cd new && >> + git fast-import && >> + git cat-file commit refs/heads/dual-signed >actual && >> + test_grep -E "^gpgsig " actual && >> + test_grep -E "^gpgsig-sha256 " actual && >> + IMPORTED=$(git rev-parse refs/heads/dual-signed) && >> + if test "$GIT_DEFAULT_HASH" = "sha1" >> + then >> + test $SHA1_B = $IMPORTED >> + else >> + test $SHA256_B = $IMPORTED >> + fi >> + ) <output > > This last bit seems a bit fragile; could the redirection of output to > the stdin of `git fast-import` be made specific to that one line > instead of to the whole range of commands? > > Otherwise, I very much appreciate the work to create a testcase with > both signature types on a single commit. Yup, thanks, both of you. It seems that we are getting closer to the finish line? Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v5] fast-(import|export): improve on commit signature output format 2025-07-09 0:03 ` Junio C Hamano @ 2025-07-09 0:10 ` Elijah Newren 2025-07-09 10:18 ` Christian Couder 1 sibling, 0 replies; 65+ messages in thread From: Elijah Newren @ 2025-07-09 0:10 UTC (permalink / raw) To: Junio C Hamano Cc: Christian Couder, git, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Tue, Jul 8, 2025 at 5:03 PM Junio C Hamano <gitster@pobox.com> wrote: > [...] > Yup, thanks, both of you. It seems that we are getting closer to > the finish line? I think so; a few small touch-ups and I think this one should be good to go. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v5] fast-(import|export): improve on commit signature output format 2025-07-09 0:03 ` Junio C Hamano 2025-07-09 0:10 ` Elijah Newren @ 2025-07-09 10:18 ` Christian Couder 1 sibling, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-09 10:18 UTC (permalink / raw) To: Junio C Hamano Cc: Elijah Newren, git, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Wed, Jul 9, 2025 at 2:03 AM Junio C Hamano <gitster@pobox.com> wrote: > > Elijah Newren <newren@gmail.com> writes: > > Really minor nitpick, but it might be clearer to pre-increment space > > here than to increment it above. > > FWIW, I find what Christian wrote easier to follow. We find a " " > in the buffer, point it with a pointer and NUL-terminate the > substring. We know we want to further process bytes that follow, so > the pointer is post-incremented after the NUL-termination. The next > user of that pointer relies on the fact that the previous user > concluded its use with that post-increment. > > If 'space' variable were named more genericly, like '*cp', it would > have been perfect. Perhaps only the first half of the code was > written first, and it looked for a space, so the name was chosen, > but then later ... > > >> + > >> + /* Remove any trailing newline from format */ > >> + space = strchr(sig->sig_format, '\n'); > > ... it is used to point at a LF X-<, at which time the author could > have renamed it to keep readers' sanity ;-) Yeah, right, I couldn't think of a good name to rename it, so I postponed changing the name, and then forgot about it. > >> + if (space) > >> + *space = '\0'; > > I also wonder what should (not "does", as I can see that the code > does not do anything) happen if we do not find the LF we were > looking for. Is the caller of this function so loosely written that > it may or may not guarantee that the data it calls this function > with is properly terminated? This function is passed a pointer to 'command_buf.buf' after "gpgsig " was skipped from it. 'read_next_command()' uses 'strbuf_getline_lf()' to put each line in 'command_buf.buf'. And yeah 'strbuf_getline_lf()' should remove a LF from the end of 'command_buf.buf' if any. So we don't need the code to remove any trailing LF. I have removed it in my current version. This means that we can keep using "space" as the variable name, as it is now only related to the space between the arguments. > > Otherwise, I very much appreciate the work to create a testcase with > > both signature types on a single commit. > > Yup, thanks, both of you. It seems that we are getting closer to > the finish line? Yeah, I plan to send a v6 soon, maybe later today with the changes discussed here and in my reply to Elijah. Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v5] fast-(import|export): improve on commit signature output format 2025-07-08 23:08 ` Elijah Newren 2025-07-09 0:03 ` Junio C Hamano @ 2025-07-09 10:15 ` Christian Couder 1 sibling, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-09 10:15 UTC (permalink / raw) To: Elijah Newren Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Wed, Jul 9, 2025 at 1:08 AM Elijah Newren <newren@gmail.com> wrote: > > On Tue, Jul 8, 2025 at 2:18 AM Christian Couder > <christian.couder@gmail.com> wrote: > > diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc > > index 250d866652..89dec1108f 100644 > > --- a/Documentation/git-fast-import.adoc > > +++ b/Documentation/git-fast-import.adoc > > @@ -445,7 +445,7 @@ one). > > original-oid? > > ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? > > 'committer' (SP <name>)? SP LT <email> GT SP <when> LF > > - ('gpgsig' SP <alg> LF data)? > > + ('gpgsig' SP <algo> SP <format> LF data)? > > ('encoding' SP <encoding> LF)? > > data > > ('from' SP <commit-ish> LF)? > > @@ -518,13 +518,37 @@ their syntax. > > ^^^^^^^^ > > > > The optional `gpgsig` command is used to include a PGP/GPG signature > > -that signs the commit data. > > +or other cryptographic signature that signs the commit data. > > > > -Here <alg> specifies which hashing algorithm is used for this > > -signature, either `sha1` or `sha256`. > > +.... > > + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF > > + data > > Should the `data` be moved to the line above, just to make it clear > it's associated with it? (Similar to the first line you changed in > git-fast-import.adoc?) Yeah, I have changed it in my current version. > > +.... > > + > > +The `gpgsig` command takes two arguments: > > + > > +* `<git-hash-algo>` specifies which Git object format this signature > > + applies to, either `sha1` or `sha256`. This allows to know which > > + representation of the commit was signed (the SHA-1 or the SHA-256 > > + version) which helps with both signature verification and > > + interoperability between repos with different hash functions. > > Should there also be a note added that fast-import is limited on what > signatures it can verify if extensions.compatObjectFormat is not set? It cannot yet verify signatures, but yeah it's probably a good idea to say that setting this config option might help with verifying signatures when it will be able to do it. See below. > > +* `<signature-format>` specifies the type of signature, such as > > + `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for > > + tools that process the stream, so they don't have to parse the ASCII > > + armor to identify the signature type. > > + > > +A commit may have at most one signature for the SHA-1 object format > > +(stored in the "gpgsig" header) and one for the SHA-256 object format > > +(stored in the "gpgsig-sha256" header). > > + > > +See below for a detailed description of the `data` command which > > +contains the raw signature data. > > + > > +Signatures are not yet checked in the current implementation though. > > ...or maybe that extra note could be added as a parenthetical comment > here for now? Yeah, in my current version, there is: "Signatures are not yet checked in the current implementation though. (Already setting the `extensions.compatObjectFormat` configuration option might help with verifying both SHA-1 and SHA-256 object format signatures when it will be implemented.)" [...] > > +test_expect_success GPG 'export and import of doubly signed commit' ' > > + git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && > > + test_grep -E "^gpgsig sha1 openpgp" output && > > + test_grep -E "^gpgsig sha256 openpgp" output && > > + > > + ( > > + cd new && > > + git fast-import && > > + git cat-file commit refs/heads/dual-signed >actual && > > + test_grep -E "^gpgsig " actual && > > + test_grep -E "^gpgsig-sha256 " actual && > > + IMPORTED=$(git rev-parse refs/heads/dual-signed) && > > + if test "$GIT_DEFAULT_HASH" = "sha1" > > + then > > + test $SHA1_B = $IMPORTED > > + else > > + test $SHA256_B = $IMPORTED > > + fi > > + ) <output > > This last bit seems a bit fragile; could the redirection of output to > the stdin of `git fast-import` be made specific to that one line > instead of to the whole range of commands? I used the same style as many other tests in this file. For example there are already: test_expect_success GPG 'signed-commits=verbatim' ' git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && grep "^gpgsig sha" output && grep "encoding ISO-8859-1" output && ( cd new && git fast-import && STRIPPED=$(git rev-parse --verify refs/heads/commit-signing) && test $COMMIT_SIGNING = $STRIPPED ) <output ' test_expect_success GPG 'signed-commits=warn-verbatim' ' git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && grep "^gpgsig sha" output && grep "encoding ISO-8859-1" output && test -s err && ( cd new && git fast-import && STRIPPED=$(git rev-parse --verify refs/heads/commit-signing) && test $COMMIT_SIGNING = $STRIPPED ) <output ' etc. So to keep a consistent style in the tests, I would likely have to add a preparatory commit that changes the style of all these existing tests. Not sure it's worth it at this point. > Otherwise, I very much appreciate the work to create a testcase with > both signature types on a single commit. Thanks for the kind words and for your reviews! ^ permalink raw reply [flat|nested] 65+ messages in thread
* [PATCH v6] fast-(import|export): improve on commit signature output format 2025-07-08 9:17 ` [PATCH v5] " Christian Couder 2025-07-08 21:58 ` Junio C Hamano 2025-07-08 23:08 ` Elijah Newren @ 2025-07-09 14:12 ` Christian Couder 2025-07-09 23:14 ` Junio C Hamano 2025-07-14 21:07 ` Elijah Newren 2 siblings, 2 replies; 65+ messages in thread From: Christian Couder @ 2025-07-09 14:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder, Christian Couder A recent commit, d9cb0e6ff8 (fast-export, fast-import: add support for signed-commits, 2025-03-10), added support for signed commits to fast-export and fast-import. When a signed commit is processed, fast-export can output either "gpgsig sha1" or "gpgsig sha256" depending on whether the signed commit uses the SHA-1 or SHA-256 Git object format. However, this implementation has a number of limitations: - the output format was not properly described in the documentation, - the output format is not very informative as it doesn't even say if the signature is an OpenPGP, an SSH, or an X509 signature, - the implementation doesn't support having both one signature on the SHA-1 object and one on the SHA-256 object. Let's improve on these limitations by improving fast-export and fast-import so that: - all the signatures are exported, - at most one signature on the SHA-1 object and one on the SHA-256 are imported, - if there is more than one signature on the SHA-1 object or on the SHA-256 object, fast-import emits a warning for each additional signature, - the output format is "gpgsig <git-hash-algo> <signature-format>", where <git-hash-algo> is the Git object format as before, and <signature-format> is the signature type ("openpgp", "x509", "ssh" or "unknown"), - the output is properly documented. About the output format: - <git-hash-algo> allows to know which representation of the commit was signed (the SHA-1 or the SHA-256 version) which helps with both signature verification and interoperability between repos with different hash functions, - <signature-format> helps tools that process the fast-export stream, so they don't have to parse the ASCII armor to identify the signature type. It could be even better to be able to import more than one signature on the SHA-1 object and on the SHA-256 object, but other parts of Git don't handle that well for now, so this is left for future improvements. Helped-by: brian m. carlson <sandals@crustytoothpaste.net> Helped-by: Elijah Newren <newren@gmail.com> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> --- This v6 is very similar to v5. There are only a few small changes: - In "git-fast-import.adoc", on a synopsys of the 'gpgsig' command, 'data' has been moved to the same line as the previous parts of the synopsys. - In "git-fast-import.adoc", it is suggested that setting the `extensions.compatObjectFormat` config option might help in the future with verifying signatures. - In "builtin/fast-import.c", "or" has been added in a comment that lists possible values for the signature format. - In "builtin/fast-import.c", some useless code that checked for a possible LF at the end of the 'gpgsig' command arguments has been removed. Thanks to brian, Elijah and Junio who commented on the previous versions. CI tests: https://github.com/chriscool/git/actions/runs/16165147132 All the tests passed. Range-diff with v5: 1: 5791617d7d ! 1: 6e07895ea1 fast-(import|export): improve on commit signature output format @@ Documentation/git-fast-import.adoc: their syntax. -Here <alg> specifies which hashing algorithm is used for this -signature, either `sha1` or `sha256`. +.... -+ 'gpgsig' SP <git-hash-algo> SP <signature-format> LF -+ data ++ 'gpgsig' SP <git-hash-algo> SP <signature-format> LF data +.... + +The `gpgsig` command takes two arguments: @@ Documentation/git-fast-import.adoc: their syntax. +See below for a detailed description of the `data` command which +contains the raw signature data. + -+Signatures are not yet checked in the current implementation though. ++Signatures are not yet checked in the current implementation ++though. (Already setting the `extensions.compatObjectFormat` ++configuration option might help with verifying both SHA-1 and SHA-256 ++object format signatures when it will be implemented.) -NOTE: This is highly experimental and the format of the data stream may -change in the future without compatibility guarantees. @@ builtin/fast-import.c: static struct hash_list *parse_merge(unsigned int *count) +struct signature_data { + char *hash_algo; /* "sha1" or "sha256" */ -+ char *sig_format; /* "openpgp", "x509", "ssh", "unknown" */ ++ char *sig_format; /* "openpgp", "x509", "ssh", or "unknown" */ + struct strbuf data; /* The actual signature data */ +}; + @@ builtin/fast-import.c: static struct hash_list *parse_merge(unsigned int *count) + if (!space) + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " + "got 'gpgsig %s'", args); -+ *space++ = '\0'; ++ *space = '\0'; + + sig->hash_algo = args; -+ sig->sig_format = space; -+ -+ /* Remove any trailing newline from format */ -+ space = strchr(sig->sig_format, '\n'); -+ if (space) -+ *space = '\0'; ++ sig->sig_format = space + 1; + + /* Validate hash algorithm */ + if (strcmp(sig->hash_algo, "sha1") && Documentation/git-fast-export.adoc | 17 +++++ Documentation/git-fast-import.adoc | 38 ++++++++-- builtin/fast-export.c | 62 ++++++++++++---- builtin/fast-import.c | 113 +++++++++++++++++++++++------ gpg-interface.c | 12 +++ gpg-interface.h | 12 +++ t/t9350-fast-export.sh | 102 +++++++++++++++++++++++++- 7 files changed, 312 insertions(+), 44 deletions(-) diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 43bbb4f63c..297b57bb2e 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -50,6 +50,23 @@ resulting tag will have an invalid signature. is the same as how earlier versions of this command without this option behaved. + +When exported, a signature starts with: ++ +gpgsig <git-hash-algo> <signature-format> ++ +where <git-hash-algo> is the Git object hash so either "sha1" or +"sha256", and <signature-format> is the signature type, so "openpgp", +"x509", "ssh" or "unknown". ++ +For example, an OpenPGP signature on a SHA-1 commit starts with +`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit +starts with `gpgsig sha256 ssh`. ++ +While all the signatures of a commit are exported, an importer may +choose to accept only some of them. For example +linkgit:git-fast-import[1] currently stores at most one signature per +Git hash algorithm in each commit. ++ NOTE: This is highly experimental and the format of the data stream may change in the future without compatibility guarantees. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 250d866652..d232784200 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -445,7 +445,7 @@ one). original-oid? ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? 'committer' (SP <name>)? SP LT <email> GT SP <when> LF - ('gpgsig' SP <alg> LF data)? + ('gpgsig' SP <algo> SP <format> LF data)? ('encoding' SP <encoding> LF)? data ('from' SP <commit-ish> LF)? @@ -518,13 +518,39 @@ their syntax. ^^^^^^^^ The optional `gpgsig` command is used to include a PGP/GPG signature -that signs the commit data. +or other cryptographic signature that signs the commit data. -Here <alg> specifies which hashing algorithm is used for this -signature, either `sha1` or `sha256`. +.... + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF data +.... + +The `gpgsig` command takes two arguments: + +* `<git-hash-algo>` specifies which Git object format this signature + applies to, either `sha1` or `sha256`. This allows to know which + representation of the commit was signed (the SHA-1 or the SHA-256 + version) which helps with both signature verification and + interoperability between repos with different hash functions. + +* `<signature-format>` specifies the type of signature, such as + `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for + tools that process the stream, so they don't have to parse the ASCII + armor to identify the signature type. + +A commit may have at most one signature for the SHA-1 object format +(stored in the "gpgsig" header) and one for the SHA-256 object format +(stored in the "gpgsig-sha256" header). + +See below for a detailed description of the `data` command which +contains the raw signature data. + +Signatures are not yet checked in the current implementation +though. (Already setting the `extensions.compatObjectFormat` +configuration option might help with verifying both SHA-1 and SHA-256 +object format signatures when it will be implemented.) -NOTE: This is highly experimental and the format of the data stream may -change in the future without compatibility guarantees. +NOTE: This is highly experimental and the format of the `gpgsig` +command may change in the future without compatibility guarantees. `encoding` ^^^^^^^^^^ diff --git a/builtin/fast-export.c b/builtin/fast-export.c index fcf6b00d5f..7b4e6a6e41 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -29,6 +29,7 @@ #include "quote.h" #include "remote.h" #include "blob.h" +#include "gpg-interface.h" static const char *const fast_export_usage[] = { N_("git fast-export [<rev-list-opts>]"), @@ -652,6 +653,38 @@ static const char *find_commit_multiline_header(const char *msg, return strbuf_detach(&val, NULL); } +static void print_signature(const char *signature, const char *object_hash) +{ + if (!signature) + return; + + printf("gpgsig %s %s\ndata %u\n%s\n", + object_hash, + get_signature_format(signature), + (unsigned)strlen(signature), + signature); +} + +static const char *append_signatures_for_header(struct string_list *signatures, + const char *pos, + const char *header, + const char *object_hash) +{ + const char *signature; + const char *start = pos; + const char *end = pos; + + while ((signature = find_commit_multiline_header(start + 1, + header, + &end))) { + string_list_append(signatures, signature)->util = (void *)object_hash; + free((char *)signature); + start = end; + } + + return end; +} + static void handle_commit(struct commit *commit, struct rev_info *rev, struct string_list *paths_of_changed_objects) { @@ -660,7 +693,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, const char *author, *author_end, *committer, *committer_end; const char *encoding = NULL; size_t encoding_len; - const char *signature_alg = NULL, *signature = NULL; + struct string_list signatures = STRING_LIST_INIT_DUP; const char *message; char *reencoded = NULL; struct commit_list *p; @@ -700,10 +733,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, } if (*commit_buffer_cursor == '\n') { - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) - signature_alg = "sha1"; - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) - signature_alg = "sha256"; + const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor, + "gpgsig", "sha1"); + const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor, + "gpgsig-sha256", "sha256"); + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; } message = strstr(commit_buffer_cursor, "\n\n"); @@ -769,30 +803,30 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, printf("%.*s\n%.*s\n", (int)(author_end - author), author, (int)(committer_end - committer), committer); - if (signature) { + if (signatures.nr) { switch (signed_commit_mode) { case SIGN_ABORT: die("encountered signed commit %s; use " "--signed-commits=<mode> to handle it", oid_to_hex(&commit->object.oid)); case SIGN_WARN_VERBATIM: - warning("exporting signed commit %s", - oid_to_hex(&commit->object.oid)); + warning("exporting %"PRIuMAX" signature(s) for commit %s", + (uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_VERBATIM: - printf("gpgsig %s\ndata %u\n%s", - signature_alg, - (unsigned)strlen(signature), - signature); + for (size_t i = 0; i < signatures.nr; i++) { + struct string_list_item *item = &signatures.items[i]; + print_signature(item->string, item->util); + } break; case SIGN_WARN_STRIP: - warning("stripping signature from commit %s", + warning("stripping signature(s) from commit %s", oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_STRIP: break; } - free((char *)signature); + string_list_clear(&signatures, 0); } if (!reencoded && encoding) printf("encoding %.*s\n", (int)encoding_len, encoding); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index b2839c5f43..b51a17b95a 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -29,6 +29,7 @@ #include "commit-reach.h" #include "khash.h" #include "date.h" +#include "gpg-interface.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -2718,15 +2719,82 @@ static struct hash_list *parse_merge(unsigned int *count) return list; } +struct signature_data { + char *hash_algo; /* "sha1" or "sha256" */ + char *sig_format; /* "openpgp", "x509", "ssh", or "unknown" */ + struct strbuf data; /* The actual signature data */ +}; + +static void parse_one_signature(struct signature_data *sig, const char *v) +{ + char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */ + char *space = strchr(args, ' '); + + if (!space) + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " + "got 'gpgsig %s'", args); + *space = '\0'; + + sig->hash_algo = args; + sig->sig_format = space + 1; + + /* Validate hash algorithm */ + if (strcmp(sig->hash_algo, "sha1") && + strcmp(sig->hash_algo, "sha256")) + die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo); + + /* Validate signature format */ + if (!valid_signature_format(sig->sig_format)) + die("Invalid signature format in gpgsig: '%s'", sig->sig_format); + if (!strcmp(sig->sig_format, "unknown")) + warning("'unknown' signature format in gpgsig"); + + /* Read signature data */ + read_next_command(); + parse_data(&sig->data, 0, NULL); +} + +static void add_gpgsig_to_commit(struct strbuf *commit_data, + const char *header, + struct signature_data *sig) +{ + struct string_list siglines = STRING_LIST_INIT_NODUP; + + if (!sig->hash_algo) + return; + + strbuf_addstr(commit_data, header); + string_list_split_in_place(&siglines, sig->data.buf, "\n", -1); + strbuf_add_separated_string_list(commit_data, "\n ", &siglines); + strbuf_addch(commit_data, '\n'); + string_list_clear(&siglines, 1); + strbuf_release(&sig->data); + free(sig->hash_algo); +} + +static void store_signature(struct signature_data *stored_sig, + struct signature_data *new_sig, + const char *hash_type) +{ + if (stored_sig->hash_algo) { + warning("multiple %s signatures found, " + "ignoring additional signature", + hash_type); + strbuf_release(&new_sig->data); + free(new_sig->hash_algo); + } else { + *stored_sig = *new_sig; + } +} + static void parse_new_commit(const char *arg) { - static struct strbuf sig = STRBUF_INIT; static struct strbuf msg = STRBUF_INIT; - struct string_list siglines = STRING_LIST_INIT_NODUP; + struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT }; + struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT }; struct branch *b; char *author = NULL; char *committer = NULL; - char *sig_alg = NULL; char *encoding = NULL; struct hash_list *merge_list = NULL; unsigned int merge_count; @@ -2750,13 +2818,23 @@ static void parse_new_commit(const char *arg) } if (!committer) die("Expected committer but didn't get one"); - if (skip_prefix(command_buf.buf, "gpgsig ", &v)) { - sig_alg = xstrdup(v); - read_next_command(); - parse_data(&sig, 0, NULL); + + /* Process signatures (up to 2: one "sha1" and one "sha256") */ + while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; + + parse_one_signature(&sig, v); + + if (!strcmp(sig.hash_algo, "sha1")) + store_signature(&sig_sha1, &sig, "SHA-1"); + else if (!strcmp(sig.hash_algo, "sha256")) + store_signature(&sig_sha256, &sig, "SHA-256"); + else + BUG("parse_one_signature() returned unknown hash algo"); + read_next_command(); - } else - strbuf_setlen(&sig, 0); + } + if (skip_prefix(command_buf.buf, "encoding ", &v)) { encoding = xstrdup(v); read_next_command(); @@ -2830,23 +2908,14 @@ static void parse_new_commit(const char *arg) strbuf_addf(&new_data, "encoding %s\n", encoding); - if (sig_alg) { - if (!strcmp(sig_alg, "sha1")) - strbuf_addstr(&new_data, "gpgsig "); - else if (!strcmp(sig_alg, "sha256")) - strbuf_addstr(&new_data, "gpgsig-sha256 "); - else - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); - string_list_split_in_place(&siglines, sig.buf, "\n", -1); - strbuf_add_separated_string_list(&new_data, "\n ", &siglines); - strbuf_addch(&new_data, '\n'); - } + + add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1); + add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256); + strbuf_addch(&new_data, '\n'); strbuf_addbuf(&new_data, &msg); - string_list_clear(&siglines, 1); free(author); free(committer); - free(sig_alg); free(encoding); if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark)) diff --git a/gpg-interface.c b/gpg-interface.c index 0896458de5..6f2d87475f 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -144,6 +144,18 @@ static struct gpg_format *get_format_by_sig(const char *sig) return NULL; } +const char *get_signature_format(const char *buf) +{ + struct gpg_format *format = get_format_by_sig(buf); + return format ? format->name : "unknown"; +} + +int valid_signature_format(const char *format) +{ + return (!!get_format_by_name(format) || + !strcmp(format, "unknown")); +} + void signature_check_clear(struct signature_check *sigc) { FREE_AND_NULL(sigc->payload); diff --git a/gpg-interface.h b/gpg-interface.h index e09f12e8d0..60ddf8bbfa 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -47,6 +47,18 @@ struct signature_check { void signature_check_clear(struct signature_check *sigc); +/* + * Return the format of the signature (like "openpgp", "x509", "ssh" + * or "unknown"). + */ +const char *get_signature_format(const char *buf); + +/* + * Is the signature format valid (like "openpgp", "x509", "ssh" or + * "unknown") + */ +int valid_signature_format(const char *format); + /* * Look at a GPG signed tag object. If such a signature exists, store it in * signature and the signed content in payload. Return 1 if a signature was diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 76619765fc..46700dbc40 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -314,7 +314,7 @@ test_expect_success GPG 'signed-commits=abort' ' test_expect_success GPG 'signed-commits=verbatim' ' git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && grep "encoding ISO-8859-1" output && ( cd new && @@ -328,7 +328,7 @@ test_expect_success GPG 'signed-commits=verbatim' ' test_expect_success GPG 'signed-commits=warn-verbatim' ' git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && - grep "^gpgsig sha" output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && grep "encoding ISO-8859-1" output && test -s err && ( @@ -369,6 +369,62 @@ test_expect_success GPG 'signed-commits=warn-strip' ' ' +test_expect_success GPGSM 'setup X.509 signed commit' ' + + git checkout -b x509-signing main && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + echo "X.509 content" >file && + git add file && + git commit -S -m "X.509 signed commit" && + X509_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSM 'round-trip X.509 signed commit' ' + + git fast-export --signed-commits=verbatim x509-signing >output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/x509-signing >actual && + grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/x509-signing) && + test $X509_COMMIT = $IMPORTED + ) <output + +' + +test_expect_success GPGSSH 'setup SSH signed commit' ' + + git checkout -b ssh-signing main && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + echo "SSH content" >file && + git add file && + git commit -S -m "SSH signed commit" && + SSH_COMMIT=$(git rev-parse HEAD) && + git checkout main + +' + +test_expect_success GPGSSH 'round-trip SSH signed commit' ' + + git fast-export --signed-commits=verbatim ssh-signing >output && + test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output && + ( + cd new && + git fast-import && + git cat-file commit refs/heads/ssh-signing >actual && + grep "^gpgsig" actual && + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && + test $SSH_COMMIT = $IMPORTED + ) <output + +' + test_expect_success 'setup submodule' ' test_config_global protocol.file.allow always && @@ -905,4 +961,46 @@ test_expect_success 'fast-export handles --end-of-options' ' test_cmp expect actual ' +test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' + # Create a signed SHA-256 commit + git init --object-format=sha256 explicit-sha256 && + git -C explicit-sha256 config extensions.compatObjectFormat sha1 && + git -C explicit-sha256 checkout -b dual-signed && + test_commit -C explicit-sha256 A && + echo B >explicit-sha256/B && + git -C explicit-sha256 add B && + test_tick && + git -C explicit-sha256 commit -S -m "signed" B && + SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && + + # Create the corresponding SHA-1 commit + SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && + + # Check that the resulting SHA-1 commit has both signatures + echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out && + test_grep -E "^gpgsig " out && + test_grep -E "^gpgsig-sha256 " out +' + +test_expect_success GPG 'export and import of doubly signed commit' ' + git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && + test_grep -E "^gpgsig sha1 openpgp" output && + test_grep -E "^gpgsig sha256 openpgp" output && + + ( + cd new && + git fast-import && + git cat-file commit refs/heads/dual-signed >actual && + test_grep -E "^gpgsig " actual && + test_grep -E "^gpgsig-sha256 " actual && + IMPORTED=$(git rev-parse refs/heads/dual-signed) && + if test "$GIT_DEFAULT_HASH" = "sha1" + then + test $SHA1_B = $IMPORTED + else + test $SHA256_B = $IMPORTED + fi + ) <output +' + test_done -- 2.50.0.174.g6e07895ea1 ^ permalink raw reply related [flat|nested] 65+ messages in thread
* Re: [PATCH v6] fast-(import|export): improve on commit signature output format 2025-07-09 14:12 ` [PATCH v6] " Christian Couder @ 2025-07-09 23:14 ` Junio C Hamano 2025-07-14 21:07 ` Elijah Newren 1 sibling, 0 replies; 65+ messages in thread From: Junio C Hamano @ 2025-07-09 23:14 UTC (permalink / raw) To: Christian Couder Cc: git, Patrick Steinhardt, Elijah Newren, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Christian Couder <christian.couder@gmail.com> writes: > +static void parse_one_signature(struct signature_data *sig, const char *v) > +{ > + char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */ > + char *space = strchr(args, ' '); > + > + if (!space) > + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " > + "got 'gpgsig %s'", args); > + *space = '\0'; > + > + sig->hash_algo = args; > + sig->sig_format = space + 1; This is minor, but as I already said in the discussion of the previous round, let me remind readers. I think "*space++ = '\0'" followed by "->sig_format = space", as you wrote originally, was easier to follow. If I were doing this 6th iteration, I would have kept that part of the code around here, but would have renamed "space" to a more generic "cp" (very often used in this codebase to stand for a character pointer). Will replace and requeue (unless you have v7 before my tomorrow's integration cycle, in which case this iteration may be skipped). Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v6] fast-(import|export): improve on commit signature output format 2025-07-09 14:12 ` [PATCH v6] " Christian Couder 2025-07-09 23:14 ` Junio C Hamano @ 2025-07-14 21:07 ` Elijah Newren 2025-07-14 21:23 ` Junio C Hamano 1 sibling, 1 reply; 65+ messages in thread From: Elijah Newren @ 2025-07-14 21:07 UTC (permalink / raw) To: Christian Couder Cc: git, Junio C Hamano, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Sorry for the delay; noticed this in what's cooking and wanted to double check whether I missed anything, and decided to add a comment here... On Wed, Jul 9, 2025 at 7:13 AM Christian Couder <christian.couder@gmail.com> wrote: > [...] > Let's improve on these limitations by improving fast-export and > fast-import so that: > > - all the signatures are exported, > - at most one signature on the SHA-1 object and one on the SHA-256 > are imported, > - if there is more than one signature on the SHA-1 object or on > the SHA-256 object, fast-import emits a warning for each > additional signature, > - the output format is "gpgsig <git-hash-algo> <signature-format>", > where <git-hash-algo> is the Git object format as before, and > <signature-format> is the signature type ("openpgp", "x509", > "ssh" or "unknown"), > - the output is properly documented. > [...] > > +test_expect_success GPGSM 'setup X.509 signed commit' ' > + > + git checkout -b x509-signing main && > + test_config gpg.format x509 && > + test_config user.signingkey $GIT_COMMITTER_EMAIL && > + echo "X.509 content" >file && > + git add file && > + git commit -S -m "X.509 signed commit" && > + X509_COMMIT=$(git rev-parse HEAD) && > + git checkout main > + > +' > + > +test_expect_success GPGSM 'round-trip X.509 signed commit' ' > + > + git fast-export --signed-commits=verbatim x509-signing >output && > + test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output && > + ( > + cd new && > + git fast-import && > + git cat-file commit refs/heads/x509-signing >actual && > + grep "^gpgsig" actual && > + IMPORTED=$(git rev-parse refs/heads/x509-signing) && > + test $X509_COMMIT = $IMPORTED > + ) <output > + [...] > + > +test_expect_success GPGSSH 'round-trip SSH signed commit' ' > + > + git fast-export --signed-commits=verbatim ssh-signing >output && > + test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output && > + ( > + cd new && > + git fast-import && > + git cat-file commit refs/heads/ssh-signing >actual && > + grep "^gpgsig" actual && > + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && > + test $SSH_COMMIT = $IMPORTED > + ) <output > + > +' > + Overall, the patch looks great now. There's just one little nit-pick left; I'm still not a fan of tests of the form ( cd dir && git fast-import ...lots of other commands... ) <output because I think the "<output" should really be moved to the "git fast-import" line since it's only meant to be used there. This series adds 2 such tests. You did point out in the discussion on v5 that the testsuite already uses this idiom and you wanted to match existing style. (Though there were only 2 tests previously that used it, and you already modified both as part of this patch.) However...we've been through enough rounds and this is really just a nit-pick; I can submit a follow-on patch later to clean up the four tests and see if others agree with me that this is an eyesore, or if I'm just weird. I think it's good to merge down. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v6] fast-(import|export): improve on commit signature output format 2025-07-14 21:07 ` Elijah Newren @ 2025-07-14 21:23 ` Junio C Hamano 2025-07-25 16:11 ` Christian Couder 0 siblings, 1 reply; 65+ messages in thread From: Junio C Hamano @ 2025-07-14 21:23 UTC (permalink / raw) To: Elijah Newren Cc: Christian Couder, git, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder Elijah Newren <newren@gmail.com> writes: > Overall, the patch looks great now. There's just one little nit-pick > left; I'm still not a fan of tests of the form > > ( > cd dir && > git fast-import > ...lots of other commands... > ) <output > > because I think the "<output" should really be moved to the "git > fast-import" line since it's only meant to be used there. > > This series adds 2 such tests. You did point out in the discussion on > v5 that the testsuite already uses this idiom and you wanted to match > existing style. (Though there were only 2 tests previously that used > it, and you already modified both as part of this patch.) > > However...we've been through enough rounds and this is really just a > nit-pick; I can submit a follow-on patch later to clean up the four > tests and see if others agree with me that this is an eyesore, or if > I'm just weird. FWIW, I think it makes sense to ensure that the "output" is consumed only by the intended command. And "there are already two cases" would not work very well as an excuse to add two more to make the codebase even worse. > I think it's good to merge down. OK. As long as somebody promises that the result will be cleaned up soon later, I am OK with that. Thanks. ^ permalink raw reply [flat|nested] 65+ messages in thread
* Re: [PATCH v6] fast-(import|export): improve on commit signature output format 2025-07-14 21:23 ` Junio C Hamano @ 2025-07-25 16:11 ` Christian Couder 0 siblings, 0 replies; 65+ messages in thread From: Christian Couder @ 2025-07-25 16:11 UTC (permalink / raw) To: Junio C Hamano Cc: Elijah Newren, git, Patrick Steinhardt, Jeff King, brian m . carlson, Johannes Schindelin, Christian Couder On Mon, Jul 14, 2025 at 11:23 PM Junio C Hamano <gitster@pobox.com> wrote: > > Elijah Newren <newren@gmail.com> writes: > > > Overall, the patch looks great now. There's just one little nit-pick > > left; I'm still not a fan of tests of the form > > > > ( > > cd dir && > > git fast-import > > ...lots of other commands... > > ) <output > > > > because I think the "<output" should really be moved to the "git > > fast-import" line since it's only meant to be used there. > > > > This series adds 2 such tests. You did point out in the discussion on > > v5 that the testsuite already uses this idiom and you wanted to match > > existing style. (Though there were only 2 tests previously that used > > it, and you already modified both as part of this patch.) > > > > However...we've been through enough rounds and this is really just a > > nit-pick; I can submit a follow-on patch later to clean up the four > > tests and see if others agree with me that this is an eyesore, or if > > I'm just weird. > > FWIW, I think it makes sense to ensure that the "output" is consumed > only by the intended command. And "there are already two cases" > would not work very well as an excuse to add two more to make the > codebase even worse. I have just sent the following patch to fix all the instances of this in t9350-fast-export.sh: https://lore.kernel.org/git/20250725160536.2909011-1-christian.couder@gmail.com/ > > I think it's good to merge down. > > OK. As long as somebody promises that the result will be cleaned up > soon later, I am OK with that. Thanks both for reviewing the previous patch. ^ permalink raw reply [flat|nested] 65+ messages in thread
end of thread, other threads:[~2025-07-25 16:11 UTC | newest] Thread overview: 65+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-04-24 20:39 [PATCH] fast-(import|export): improve on the signature algorithm name Christian Couder 2025-04-24 21:19 ` Junio C Hamano 2025-04-24 21:59 ` Elijah Newren 2025-04-24 22:58 ` Junio C Hamano 2025-05-26 10:35 ` Christian Couder 2025-05-27 15:18 ` Junio C Hamano 2025-05-28 17:29 ` Junio C Hamano 2025-05-28 20:06 ` Elijah Newren 2025-05-28 21:59 ` Junio C Hamano 2025-05-28 23:15 ` Elijah Newren 2025-05-29 3:14 ` Junio C Hamano 2025-06-02 15:56 ` Christian Couder 2025-06-02 15:56 ` Christian Couder 2025-06-02 16:20 ` Junio C Hamano 2025-05-26 10:34 ` Christian Couder 2025-04-24 21:41 ` Elijah Newren 2025-05-26 10:34 ` Christian Couder 2025-04-24 22:05 ` brian m. carlson 2025-05-26 10:35 ` Christian Couder 2025-04-24 23:25 ` Junio C Hamano 2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder 2025-05-26 10:33 ` [PATCH v2 1/6] gpg-interface: simplify ssh fingerprint parsing Christian Couder 2025-05-26 10:33 ` [PATCH v2 2/6] gpg-interface: use left shift to define GPG_VERIFY_* Christian Couder 2025-05-26 10:33 ` [PATCH v2 3/6] doc/verify-commit: update and improve the whole doc Christian Couder 2025-05-26 10:33 ` [PATCH v2 4/6] gpg-interface: extract hash algorithm from signature status output Christian Couder 2025-05-26 10:33 ` [PATCH v2 5/6] gpg-interface: extract SSH key type " Christian Couder 2025-05-26 10:33 ` [PATCH v2 6/6] verify-commit: add a --summary flag Christian Couder 2025-05-26 16:03 ` [PATCH v2 0/6] extract algo information from signatures Elijah Newren 2025-06-19 13:38 ` Christian Couder 2025-06-02 22:17 ` brian m. carlson 2025-06-19 13:37 ` Christian Couder 2025-06-18 15:18 ` [PATCH v3] fast-(import|export): improve on commit signature output format Christian Couder 2025-06-19 13:36 ` [PATCH v4] " Christian Couder 2025-06-19 14:55 ` Junio C Hamano 2025-07-08 9:16 ` Christian Couder 2025-06-19 21:44 ` Elijah Newren 2025-06-20 16:12 ` Christian Couder 2025-06-20 19:20 ` Junio C Hamano 2025-07-08 9:16 ` Christian Couder 2025-06-26 19:11 ` Elijah Newren 2025-07-08 9:16 ` Christian Couder 2025-07-07 22:58 ` Junio C Hamano 2025-07-08 3:35 ` Christian Couder 2025-07-08 5:03 ` Junio C Hamano 2025-07-08 6:38 ` Patrick Steinhardt 2025-07-08 11:08 ` Christian Couder 2025-07-08 16:38 ` Junio C Hamano 2025-07-09 0:19 ` Christian Couder 2025-07-09 15:35 ` Junio C Hamano 2025-07-10 8:25 ` Patrick Steinhardt 2025-07-10 15:29 ` Christian Couder 2025-07-10 15:33 ` Junio C Hamano 2025-07-08 10:17 ` Christian Couder 2025-07-08 9:17 ` [PATCH v5] " Christian Couder 2025-07-08 21:58 ` Junio C Hamano 2025-07-08 23:08 ` Elijah Newren 2025-07-09 0:03 ` Junio C Hamano 2025-07-09 0:10 ` Elijah Newren 2025-07-09 10:18 ` Christian Couder 2025-07-09 10:15 ` Christian Couder 2025-07-09 14:12 ` [PATCH v6] " Christian Couder 2025-07-09 23:14 ` Junio C Hamano 2025-07-14 21:07 ` Elijah Newren 2025-07-14 21:23 ` Junio C Hamano 2025-07-25 16:11 ` Christian Couder
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).