@@ -35,8 +35,11 @@ static const char *fast_export_usage[] = {
3535 NULL
3636};
3737
38+ enum sign_mode { SIGN_ABORT , SIGN_VERBATIM , SIGN_STRIP , SIGN_WARN_VERBATIM , SIGN_WARN_STRIP };
39+
3840static int progress ;
39- static enum signed_tag_mode { SIGNED_TAG_ABORT , VERBATIM , WARN , WARN_STRIP , STRIP } signed_tag_mode = SIGNED_TAG_ABORT ;
41+ static enum sign_mode signed_tag_mode = SIGN_ABORT ;
42+ static enum sign_mode signed_commit_mode = SIGN_ABORT ;
4043static enum tag_of_filtered_mode { TAG_FILTERING_ABORT , DROP , REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT ;
4144static enum reencode_mode { REENCODE_ABORT , REENCODE_YES , REENCODE_NO } reencode_mode = REENCODE_ABORT ;
4245static int fake_missing_tagger ;
@@ -53,23 +56,24 @@ static int anonymize;
5356static struct hashmap anonymized_seeds ;
5457static struct revision_sources revision_sources ;
5558
56- static int parse_opt_signed_tag_mode (const struct option * opt ,
59+ static int parse_opt_sign_mode (const struct option * opt ,
5760 const char * arg , int unset )
5861{
59- enum signed_tag_mode * val = opt -> value ;
60-
61- if (unset || !strcmp (arg , "abort" ))
62- * val = SIGNED_TAG_ABORT ;
62+ enum sign_mode * val = opt -> value ;
63+ if (unset )
64+ return 0 ;
65+ else if (!strcmp (arg , "abort" ))
66+ * val = SIGN_ABORT ;
6367 else if (!strcmp (arg , "verbatim" ) || !strcmp (arg , "ignore" ))
64- * val = VERBATIM ;
65- else if (!strcmp (arg , "warn" ))
66- * val = WARN ;
68+ * val = SIGN_VERBATIM ;
69+ else if (!strcmp (arg , "warn-verbatim" ) || ! strcmp ( arg , "warn " ))
70+ * val = SIGN_WARN_VERBATIM ;
6771 else if (!strcmp (arg , "warn-strip" ))
68- * val = WARN_STRIP ;
72+ * val = SIGN_WARN_STRIP ;
6973 else if (!strcmp (arg , "strip" ))
70- * val = STRIP ;
74+ * val = SIGN_STRIP ;
7175 else
72- return error ("Unknown signed-tags mode: %s" , arg );
76+ return error ("Unknown %s mode: %s" , opt -> long_name , arg );
7377 return 0 ;
7478}
7579
@@ -510,21 +514,6 @@ static void show_filemodify(struct diff_queue_struct *q,
510514 }
511515}
512516
513- static const char * find_encoding (const char * begin , const char * end )
514- {
515- const char * needle = "\nencoding " ;
516- char * bol , * eol ;
517-
518- bol = memmem (begin , end ? end - begin : strlen (begin ),
519- needle , strlen (needle ));
520- if (!bol )
521- return NULL ;
522- bol += strlen (needle );
523- eol = strchrnul (bol , '\n' );
524- * eol = '\0' ;
525- return bol ;
526- }
527-
528517static char * anonymize_ref_component (void )
529518{
530519 static int counter ;
@@ -626,13 +615,54 @@ static void anonymize_ident_line(const char **beg, const char **end)
626615 * end = out -> buf + out -> len ;
627616}
628617
618+ /*
619+ * find_commit_multiline_header is similar to find_commit_header,
620+ * except that it handles multi-line headers, rathar than simply
621+ * returning the first line of the header.
622+ *
623+ * The returned string has had the ' ' line continuation markers
624+ * removed, and points to statically allocated memory (not to memory
625+ * within 'msg'), so it is only valid until the next call to
626+ * find_commit_multiline_header.
627+ *
628+ * If the header is found, then *end is set to point at the '\n' in
629+ * msg that immediately follows the header value.
630+ */
631+ static const char * find_commit_multiline_header (const char * msg ,
632+ const char * key ,
633+ const char * * end )
634+ {
635+ struct strbuf val = STRBUF_INIT ;
636+ const char * bol , * eol ;
637+ size_t len ;
638+
639+ bol = find_commit_header (msg , key , & len );
640+ if (!bol )
641+ return NULL ;
642+ eol = bol + len ;
643+ strbuf_add (& val , bol , len );
644+
645+ while (eol [0 ] == '\n' && eol [1 ] == ' ' ) {
646+ bol = eol + 2 ;
647+ eol = strchrnul (bol , '\n' );
648+ strbuf_addch (& val , '\n' );
649+ strbuf_add (& val , bol , eol - bol );
650+ }
651+
652+ * end = eol ;
653+ return strbuf_detach (& val , NULL );
654+ }
655+
629656static void handle_commit (struct commit * commit , struct rev_info * rev ,
630657 struct string_list * paths_of_changed_objects )
631658{
632659 int saved_output_format = rev -> diffopt .output_format ;
633- const char * commit_buffer ;
660+ const char * commit_buffer , * commit_buffer_cursor ;
634661 const char * author , * author_end , * committer , * committer_end ;
635- const char * encoding , * message ;
662+ const char * encoding = NULL ;
663+ size_t encoding_len ;
664+ const char * signature_alg = NULL , * signature = NULL ;
665+ const char * message ;
636666 char * reencoded = NULL ;
637667 struct commit_list * p ;
638668 const char * refname ;
@@ -641,21 +671,43 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
641671 rev -> diffopt .output_format = DIFF_FORMAT_CALLBACK ;
642672
643673 parse_commit_or_die (commit );
644- commit_buffer = repo_get_commit_buffer (the_repository , commit , NULL );
645- author = strstr (commit_buffer , "\nauthor " );
674+ commit_buffer_cursor = commit_buffer = repo_get_commit_buffer (the_repository , commit , NULL );
675+
676+ author = strstr (commit_buffer_cursor , "\nauthor " );
646677 if (!author )
647678 die ("could not find author in commit %s" ,
648679 oid_to_hex (& commit -> object .oid ));
649680 author ++ ;
650- author_end = strchrnul (author , '\n' );
651- committer = strstr (author_end , "\ncommitter " );
681+ commit_buffer_cursor = author_end = strchrnul (author , '\n' );
682+
683+ committer = strstr (commit_buffer_cursor , "\ncommitter " );
652684 if (!committer )
653685 die ("could not find committer in commit %s" ,
654686 oid_to_hex (& commit -> object .oid ));
655687 committer ++ ;
656- committer_end = strchrnul (committer , '\n' );
657- message = strstr (committer_end , "\n\n" );
658- encoding = find_encoding (committer_end , message );
688+ commit_buffer_cursor = committer_end = strchrnul (committer , '\n' );
689+
690+ /*
691+ * find_commit_header() and find_commit_multiline_header() get
692+ * a `+ 1` because commit_buffer_cursor points at the trailing
693+ * "\n" at the end of the previous line, but they want a
694+ * pointer to the beginning of the next line.
695+ */
696+
697+ if (* commit_buffer_cursor == '\n' ) {
698+ encoding = find_commit_header (commit_buffer_cursor + 1 , "encoding" , & encoding_len );
699+ if (encoding )
700+ commit_buffer_cursor = encoding + encoding_len ;
701+ }
702+
703+ if (* commit_buffer_cursor == '\n' ) {
704+ if ((signature = find_commit_multiline_header (commit_buffer_cursor + 1 , "gpgsig" , & commit_buffer_cursor )))
705+ signature_alg = "sha1" ;
706+ else if ((signature = find_commit_multiline_header (commit_buffer_cursor + 1 , "gpgsig-sha256" , & commit_buffer_cursor )))
707+ signature_alg = "sha256" ;
708+ }
709+
710+ message = strstr (commit_buffer_cursor , "\n\n" );
659711 if (message )
660712 message += 2 ;
661713
@@ -694,16 +746,20 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
694746 if (anonymize ) {
695747 reencoded = anonymize_commit_message ();
696748 } else if (encoding ) {
697- switch (reencode_mode ) {
749+ char * buf ;
750+ switch (reencode_mode ) {
698751 case REENCODE_YES :
699- reencoded = reencode_string (message , "UTF-8" , encoding );
752+ buf = xstrfmt ("%.*s" , (int )encoding_len , encoding );
753+ reencoded = reencode_string (message , "UTF-8" , buf );
754+ free (buf );
700755 break ;
701756 case REENCODE_NO :
702757 break ;
703758 case REENCODE_ABORT :
704- die ("Encountered commit-specific encoding %s in commit "
759+ die ("Encountered commit-specific encoding %.* s in commit "
705760 "%s; use --reencode=[yes|no] to handle it" ,
706- encoding , oid_to_hex (& commit -> object .oid ));
761+ (int )encoding_len , encoding ,
762+ oid_to_hex (& commit -> object .oid ));
707763 }
708764 }
709765 if (!commit -> parents )
@@ -714,8 +770,33 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
714770 printf ("%.*s\n%.*s\n" ,
715771 (int )(author_end - author ), author ,
716772 (int )(committer_end - committer ), committer );
773+ if (signature ) {
774+ switch (signed_commit_mode ) {
775+ case SIGN_ABORT :
776+ die ("encountered signed commit %s; use "
777+ "--signed-commits=<mode> to handle it" ,
778+ oid_to_hex (& commit -> object .oid ));
779+ case SIGN_WARN_VERBATIM :
780+ warning ("exporting signed commit %s" ,
781+ oid_to_hex (& commit -> object .oid ));
782+ /* fallthru */
783+ case SIGN_VERBATIM :
784+ printf ("gpgsig %s\ndata %u\n%s" ,
785+ signature_alg ,
786+ (unsigned )strlen (signature ),
787+ signature );
788+ break ;
789+ case SIGN_WARN_STRIP :
790+ warning ("stripping signature from commit %s" ,
791+ oid_to_hex (& commit -> object .oid ));
792+ /* fallthru */
793+ case SIGN_STRIP :
794+ break ;
795+ }
796+ free ((char * )signature );
797+ }
717798 if (!reencoded && encoding )
718- printf ("encoding %s\n" , encoding );
799+ printf ("encoding %.* s\n" , ( int ) encoding_len , encoding );
719800 printf ("data %u\n%s" ,
720801 (unsigned )(reencoded
721802 ? strlen (reencoded ) : message
@@ -828,22 +909,22 @@ static void handle_tag(const char *name, struct tag *tag)
828909 const char * signature = strstr (message ,
829910 "\n-----BEGIN PGP SIGNATURE-----\n" );
830911 if (signature )
831- switch (signed_tag_mode ) {
832- case SIGNED_TAG_ABORT :
912+ switch (signed_tag_mode ) {
913+ case SIGN_ABORT :
833914 die ("encountered signed tag %s; use "
834915 "--signed-tags=<mode> to handle it" ,
835916 oid_to_hex (& tag -> object .oid ));
836- case WARN :
917+ case SIGN_WARN_VERBATIM :
837918 warning ("exporting signed tag %s" ,
838919 oid_to_hex (& tag -> object .oid ));
839920 /* fallthru */
840- case VERBATIM :
921+ case SIGN_VERBATIM :
841922 break ;
842- case WARN_STRIP :
923+ case SIGN_WARN_STRIP :
843924 warning ("stripping signature from tag %s" ,
844925 oid_to_hex (& tag -> object .oid ));
845926 /* fallthru */
846- case STRIP :
927+ case SIGN_STRIP :
847928 message_size = signature + 1 - message ;
848929 break ;
849930 }
@@ -853,7 +934,7 @@ static void handle_tag(const char *name, struct tag *tag)
853934 tagged = tag -> tagged ;
854935 tagged_mark = get_object_mark (tagged );
855936 if (!tagged_mark ) {
856- switch (tag_of_filtered_mode ) {
937+ switch (tag_of_filtered_mode ) {
857938 case TAG_FILTERING_ABORT :
858939 die ("tag %s tags unexported object; use "
859940 "--tag-of-filtered-object=<mode> to handle it" ,
@@ -965,7 +1046,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
9651046 continue ;
9661047 }
9671048
968- switch (commit -> object .type ) {
1049+ switch (commit -> object .type ) {
9691050 case OBJ_COMMIT :
9701051 break ;
9711052 case OBJ_BLOB :
@@ -1189,6 +1270,7 @@ int cmd_fast_export(int argc,
11891270 const char * prefix ,
11901271 struct repository * repo UNUSED )
11911272{
1273+ const char * env_signed_commits_noabort ;
11921274 struct rev_info revs ;
11931275 struct commit * commit ;
11941276 char * export_filename = NULL ,
@@ -1202,7 +1284,10 @@ int cmd_fast_export(int argc,
12021284 N_ ("show progress after <n> objects" )),
12031285 OPT_CALLBACK (0 , "signed-tags" , & signed_tag_mode , N_ ("mode" ),
12041286 N_ ("select handling of signed tags" ),
1205- parse_opt_signed_tag_mode ),
1287+ parse_opt_sign_mode ),
1288+ OPT_CALLBACK (0 , "signed-commits" , & signed_commit_mode , N_ ("mode" ),
1289+ N_ ("select handling of signed commits" ),
1290+ parse_opt_sign_mode ),
12061291 OPT_CALLBACK (0 , "tag-of-filtered-object" , & tag_of_filtered_mode , N_ ("mode" ),
12071292 N_ ("select handling of tags that tag filtered objects" ),
12081293 parse_opt_tag_of_filtered_mode ),
@@ -1243,6 +1328,10 @@ int cmd_fast_export(int argc,
12431328 if (argc == 1 )
12441329 usage_with_options (fast_export_usage , options );
12451330
1331+ env_signed_commits_noabort = getenv ("FAST_EXPORT_SIGNED_COMMITS_NOABORT" );
1332+ if (env_signed_commits_noabort && * env_signed_commits_noabort )
1333+ signed_commit_mode = SIGN_WARN_STRIP ;
1334+
12461335 /* we handle encodings */
12471336 git_config (git_default_config , NULL );
12481337
0 commit comments