Git fork

fast-import: add '--signed-commits=<mode>' option

A '--signed-commits=<mode>' option is already available when using
`git fast-export` to decide what should be done at export time about
commit signatures. At import time though, there is no option, or
other way, in `git fast-import` to decide about commit signatures.

To remediate that, let's add a '--signed-commits=<mode>' option to
`git fast-import` too.

For now the supported <mode>s are the same as those supported by
`git fast-export`.

The code responsible for consuming a signature is refactored into
the import_one_signature() and discard_one_signature() functions,
which makes it easier to follow the logic and add new modes in the
future.

In the 'strip' and 'warn-strip' modes, we deliberately use
discard_one_signature() to discard the signature without parsing it.
This ensures that even malformed signatures, which would cause the
parser to fail, can be successfully stripped from a commit.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Christian Couder and committed by
Junio C Hamano
eaaddf57 2f8fd208

+164 -9
+5
Documentation/git-fast-import.adoc
··· 66 66 remote-helpers that use the `import` capability, as they are 67 67 already trusted to run their own code. 68 68 69 + --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort):: 70 + Specify how to handle signed commits. Behaves in the same way 71 + as the same option in linkgit:git-fast-export[1], except that 72 + default is 'verbatim' (instead of 'abort'). 73 + 69 74 Options for Frontends 70 75 ~~~~~~~~~~~~~~~~~~~~~ 71 76
+52 -9
builtin/fast-import.c
··· 188 188 static const char **global_argv; 189 189 static const char *global_prefix; 190 190 191 + static enum sign_mode signed_commit_mode = SIGN_VERBATIM; 192 + 191 193 /* Memory pools */ 192 194 static struct mem_pool fi_mem_pool = { 193 195 .block_alloc = 2*1024*1024 - sizeof(struct mp_block), ··· 2752 2754 parse_data(&sig->data, 0, NULL); 2753 2755 } 2754 2756 2757 + static void discard_one_signature(void) 2758 + { 2759 + struct strbuf data = STRBUF_INIT; 2760 + 2761 + read_next_command(); 2762 + parse_data(&data, 0, NULL); 2763 + strbuf_release(&data); 2764 + } 2765 + 2755 2766 static void add_gpgsig_to_commit(struct strbuf *commit_data, 2756 2767 const char *header, 2757 2768 struct signature_data *sig) ··· 2785 2796 } 2786 2797 } 2787 2798 2799 + static void import_one_signature(struct signature_data *sig_sha1, 2800 + struct signature_data *sig_sha256, 2801 + const char *v) 2802 + { 2803 + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; 2804 + 2805 + parse_one_signature(&sig, v); 2806 + 2807 + if (!strcmp(sig.hash_algo, "sha1")) 2808 + store_signature(sig_sha1, &sig, "SHA-1"); 2809 + else if (!strcmp(sig.hash_algo, "sha256")) 2810 + store_signature(sig_sha256, &sig, "SHA-256"); 2811 + else 2812 + die(_("parse_one_signature() returned unknown hash algo")); 2813 + } 2814 + 2788 2815 static void parse_new_commit(const char *arg) 2789 2816 { 2790 2817 static struct strbuf msg = STRBUF_INIT; ··· 2817 2844 if (!committer) 2818 2845 die("Expected committer but didn't get one"); 2819 2846 2820 - /* Process signatures (up to 2: one "sha1" and one "sha256") */ 2821 2847 while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { 2822 - struct signature_data sig = { NULL, NULL, STRBUF_INIT }; 2848 + switch (signed_commit_mode) { 2823 2849 2824 - parse_one_signature(&sig, v); 2850 + /* First, modes that don't need the signature to be parsed */ 2851 + case SIGN_ABORT: 2852 + die("encountered signed commit; use " 2853 + "--signed-commits=<mode> to handle it"); 2854 + case SIGN_WARN_STRIP: 2855 + warning(_("stripping a commit signature")); 2856 + /* fallthru */ 2857 + case SIGN_STRIP: 2858 + discard_one_signature(); 2859 + break; 2825 2860 2826 - if (!strcmp(sig.hash_algo, "sha1")) 2827 - store_signature(&sig_sha1, &sig, "SHA-1"); 2828 - else if (!strcmp(sig.hash_algo, "sha256")) 2829 - store_signature(&sig_sha256, &sig, "SHA-256"); 2830 - else 2831 - BUG("parse_one_signature() returned unknown hash algo"); 2861 + /* Second, modes that parse the signature */ 2862 + case SIGN_WARN_VERBATIM: 2863 + warning(_("importing a commit signature verbatim")); 2864 + /* fallthru */ 2865 + case SIGN_VERBATIM: 2866 + import_one_signature(&sig_sha1, &sig_sha256, v); 2867 + break; 2832 2868 2869 + /* Third, BUG */ 2870 + default: 2871 + BUG("invalid signed_commit_mode value %d", signed_commit_mode); 2872 + } 2833 2873 read_next_command(); 2834 2874 } 2835 2875 ··· 3501 3541 option_active_branches(option); 3502 3542 } else if (skip_prefix(option, "export-pack-edges=", &option)) { 3503 3543 option_export_pack_edges(option); 3544 + } else if (skip_prefix(option, "signed-commits=", &option)) { 3545 + if (parse_sign_mode(option, &signed_commit_mode)) 3546 + usagef(_("unknown --signed-commits mode '%s'"), option); 3504 3547 } else if (!strcmp(option, "quiet")) { 3505 3548 show_stats = 0; 3506 3549 quiet = 1;
+1
t/meson.build
··· 1032 1032 't9302-fast-import-unpack-limit.sh', 1033 1033 't9303-fast-import-compression.sh', 1034 1034 't9304-fast-import-marks.sh', 1035 + 't9305-fast-import-signatures.sh', 1035 1036 't9350-fast-export.sh', 1036 1037 't9351-fast-export-anonymize.sh', 1037 1038 't9400-git-cvsserver-server.sh',
+106
t/t9305-fast-import-signatures.sh
··· 1 + #!/bin/sh 2 + 3 + test_description='git fast-import --signed-commits=<mode>' 4 + 5 + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main 6 + 7 + . ./test-lib.sh 8 + . "$TEST_DIRECTORY/lib-gpg.sh" 9 + 10 + test_expect_success 'set up unsigned initial commit and import repo' ' 11 + test_commit first && 12 + git init new 13 + ' 14 + 15 + test_expect_success GPG 'set up OpenPGP signed commit' ' 16 + git checkout -b openpgp-signing main && 17 + echo "Content for OpenPGP signing." >file-sign && 18 + git add file-sign && 19 + git commit -S -m "OpenPGP signed commit" && 20 + OPENPGP_SIGNING=$(git rev-parse --verify openpgp-signing) 21 + ' 22 + 23 + test_expect_success GPG 'import OpenPGP signature with --signed-commits=verbatim' ' 24 + git fast-export --signed-commits=verbatim openpgp-signing >output && 25 + git -C new fast-import --quiet --signed-commits=verbatim <output >log 2>&1 && 26 + IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) && 27 + test $OPENPGP_SIGNING = $IMPORTED && 28 + test_must_be_empty log 29 + ' 30 + 31 + test_expect_success GPGSM 'set up X.509 signed commit' ' 32 + git checkout -b x509-signing main && 33 + test_config gpg.format x509 && 34 + test_config user.signingkey $GIT_COMMITTER_EMAIL && 35 + echo "Content for X.509 signing." >file-sign && 36 + git add file-sign && 37 + git commit -S -m "X.509 signed commit" && 38 + X509_SIGNING=$(git rev-parse HEAD) 39 + ' 40 + 41 + test_expect_success GPGSM 'import X.509 signature fails with --signed-commits=abort' ' 42 + git fast-export --signed-commits=verbatim x509-signing >output && 43 + test_must_fail git -C new fast-import --quiet --signed-commits=abort <output 44 + ' 45 + 46 + test_expect_success GPGSM 'import X.509 signature with --signed-commits=warn-verbatim' ' 47 + git -C new fast-import --quiet --signed-commits=warn-verbatim <output >log 2>&1 && 48 + IMPORTED=$(git -C new rev-parse --verify refs/heads/x509-signing) && 49 + test $X509_SIGNING = $IMPORTED && 50 + test_grep "importing a commit signature" log 51 + ' 52 + 53 + test_expect_success GPGSSH 'set up SSH signed commit' ' 54 + git checkout -b ssh-signing main && 55 + test_config gpg.format ssh && 56 + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && 57 + echo "Content for SSH signing." >file-sign && 58 + git add file-sign && 59 + git commit -S -m "SSH signed commit" && 60 + SSH_SIGNING=$(git rev-parse HEAD) 61 + ' 62 + 63 + test_expect_success GPGSSH 'strip SSH signature with --signed-commits=strip' ' 64 + git fast-export --signed-commits=verbatim ssh-signing >output && 65 + git -C new fast-import --quiet --signed-commits=strip <output >log 2>&1 && 66 + IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) && 67 + test $SSH_SIGNING != $IMPORTED && 68 + git -C new cat-file commit "$IMPORTED" >actual && 69 + test_grep ! -E "^gpgsig" actual && 70 + test_must_be_empty log 71 + ' 72 + 73 + test_expect_success GPG 'setup a commit with dual OpenPGP signatures on its SHA-1 and SHA-256 formats' ' 74 + # Create a signed SHA-256 commit 75 + git init --object-format=sha256 explicit-sha256 && 76 + git -C explicit-sha256 config extensions.compatObjectFormat sha1 && 77 + git -C explicit-sha256 checkout -b dual-signed && 78 + test_commit -C explicit-sha256 A && 79 + echo B >explicit-sha256/B && 80 + git -C explicit-sha256 add B && 81 + test_tick && 82 + git -C explicit-sha256 commit -S -m "signed" B && 83 + SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && 84 + 85 + # Create the corresponding SHA-1 commit 86 + SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && 87 + 88 + # Check that the resulting SHA-1 commit has both signatures 89 + git -C explicit-sha256 cat-file -p $SHA1_B >out && 90 + test_grep -E "^gpgsig " out && 91 + test_grep -E "^gpgsig-sha256 " out 92 + ' 93 + 94 + test_expect_success GPG 'strip both OpenPGP signatures with --signed-commits=warn-strip' ' 95 + git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && 96 + test_grep -E "^gpgsig sha1 openpgp" output && 97 + test_grep -E "^gpgsig sha256 openpgp" output && 98 + git -C new fast-import --quiet --signed-commits=warn-strip <output >log 2>&1 && 99 + git -C new cat-file commit refs/heads/dual-signed >actual && 100 + test_grep ! -E "^gpgsig " actual && 101 + test_grep ! -E "^gpgsig-sha256 " actual && 102 + test_grep "stripping a commit signature" log >out && 103 + test_line_count = 2 out 104 + ' 105 + 106 + test_done