fast-export: do automatic reencoding of commit messages only if requested

Automatic re-encoding of commit messages (and dropping of the encoding header) hurts attempts to do reversible history rewrites (e.g. sha1sum <-> sha256sum transitions, some subtree rewrites), and seems inconsistent with the general principle followed elsewhere in fast-export of requiring explicit user requests to modify the output (e.g. --signed-tags=strip, --tag-of-filtered-object=rewrite). Add a --reencode flag that the user can use to specify, and like other fast-export flags, default it to 'abort'. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed May 13, 2019 at 21:31 UTC e80001f8fd7f608c6d05ce0ab2ebfc90d724a307
3 files changed +85 -6
Documentation/git-fast-export.txt
+7
@@ -129,6 +129,13 @@ marks the same across runs.
129 for intermediary filters (e.g. for rewriting commit messages
130 which refer to older commits, or for stripping blobs by id).
131
132 +--reencode=(yes|no|abort)::
133 + Specify how to handle `encoding` header in commit objects. When
134 + asking to 'abort' (which is the default), this program will die
135 + when encountering such a commit object. With 'yes', the commit
136 + message will be reencoded into UTF-8. With 'no', the original
137 + encoding will be preserved.
138 +
139 --refspec::
140 Apply the specified refspec to each ref exported. Multiple of them can
141 be specified.
builtin/fast-export.c
+43 -3
@@ -33,6 +33,7 @@ static const char *fast_export_usage[] = {
33 static int progress;
34 static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;
35 static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;
36 +static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;
37 static int fake_missing_tagger;
38 static int use_done_feature;
39 static int no_data;
@@ -77,6 +78,31 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt,
78 return 0;
79 }
80
81 +static int parse_opt_reencode_mode(const struct option *opt,
82 + const char *arg, int unset)
83 +{
84 + if (unset) {
85 + reencode_mode = REENCODE_ABORT;
86 + return 0;
87 + }
88 +
89 + switch (git_parse_maybe_bool(arg)) {
90 + case 0:
91 + reencode_mode = REENCODE_NO;
92 + break;
93 + case 1:
94 + reencode_mode = REENCODE_YES;
95 + break;
96 + default:
97 + if (!strcasecmp(arg, "abort"))
98 + reencode_mode = REENCODE_ABORT;
99 + else
100 + return error("Unknown reencoding mode: %s", arg);
101 + }
102 +
103 + return 0;
104 +}
105 +
106 static struct decoration idnums;
107 static uint32_t last_idnum;
108
@@ -633,10 +659,21 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
659 }
660
661 mark_next_object(&commit->object);
636 - if (anonymize)
662 + if (anonymize) {
663 reencoded = anonymize_commit_message(message);
638 - else if (!is_encoding_utf8(encoding))
639 - reencoded = reencode_string(message, "UTF-8", encoding);
664 + } else if (encoding) {
665 + switch(reencode_mode) {
666 + case REENCODE_YES:
667 + reencoded = reencode_string(message, "UTF-8", encoding);
668 + break;
669 + case REENCODE_NO:
670 + break;
671 + case REENCODE_ABORT:
672 + die("Encountered commit-specific encoding %s in commit "
673 + "%s; use --reencode=[yes|no] to handle it",
674 + encoding, oid_to_hex(&commit->object.oid));
675 + }
676 + }
677 if (!commit->parents)
678 printf("reset %s\n", refname);
679 printf("commit %s\nmark :%"PRIu32"\n", refname, last_idnum);
@@ -1091,6 +1128,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
1128 OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, N_("mode"),
1129 N_("select handling of tags that tag filtered objects"),
1130 parse_opt_tag_of_filtered_mode),
1131 + OPT_CALLBACK(0, "reencode", &reencode_mode, N_("mode"),
1132 + N_("select handling of commit messages in an alternate encoding"),
1133 + parse_opt_reencode_mode),
1134 OPT_STRING(0, "export-marks", &export_filename, N_("file"),
1135 N_("Dump marks to this file")),
1136 OPT_STRING(0, "import-marks", &import_filename, N_("file"),
t/t9350-fast-export.sh
+35 -3
@@ -94,14 +94,14 @@ test_expect_success 'fast-export --show-original-ids | git fast-import' '
94 test $MUSS = $(git rev-parse --verify refs/tags/muss)
95 '
96
97 -test_expect_success 'iso-8859-7' '
97 +test_expect_success 'reencoding iso-8859-7' '
98
99 test_when_finished "git reset --hard HEAD~1" &&
100 test_config i18n.commitencoding iso-8859-7 &&
101 test_tick &&
102 echo rosten >file &&
103 git commit -s -F "$TEST_DIRECTORY/t9350/simple-iso-8859-7-commit-message.txt" file &&
104 - git fast-export wer^..wer >iso-8859-7.fi &&
104 + git fast-export --reencode=yes wer^..wer >iso-8859-7.fi &&
105 sed "s/wer/i18n/" iso-8859-7.fi |
106 (cd new &&
107 git fast-import &&
@@ -118,13 +118,45 @@ test_expect_success 'iso-8859-7' '
118 ! grep ^encoding actual)
119 '
120
121 +test_expect_success 'aborting on iso-8859-7' '
122 +
123 + test_when_finished "git reset --hard HEAD~1" &&
124 + test_config i18n.commitencoding iso-8859-7 &&
125 + echo rosten >file &&
126 + git commit -s -F "$TEST_DIRECTORY/t9350/simple-iso-8859-7-commit-message.txt" file &&
127 + test_must_fail git fast-export --reencode=abort wer^..wer >iso-8859-7.fi
128 +'
129 +
130 +test_expect_success 'preserving iso-8859-7' '
131 +
132 + test_when_finished "git reset --hard HEAD~1" &&
133 + test_config i18n.commitencoding iso-8859-7 &&
134 + echo rosten >file &&
135 + git commit -s -F "$TEST_DIRECTORY/t9350/simple-iso-8859-7-commit-message.txt" file &&
136 + git fast-export --reencode=no wer^..wer >iso-8859-7.fi &&
137 + sed "s/wer/i18n-no-recoding/" iso-8859-7.fi |
138 + (cd new &&
139 + git fast-import &&
140 + # The commit object, if not re-encoded, is 240 bytes.
141 + # Removing the "encoding iso-8859-7\n" header would drops 20
142 + # bytes. Re-encoding the Pi character from \xF0 (\360) in
143 + # iso-8859-7 to \xCF\x80 (\317\200) in UTF-8 adds a byte.
144 + # Check for the expected size...
145 + test 240 -eq "$(git cat-file -s i18n-no-recoding)" &&
146 + # ...as well as the expected byte.
147 + git cat-file commit i18n-no-recoding >actual &&
148 + grep $(printf "\360") actual &&
149 + # Also make sure the commit has the "encoding" header
150 + grep ^encoding actual)
151 +'
152 +
153 test_expect_success 'encoding preserved if reencoding fails' '
154
155 test_when_finished "git reset --hard HEAD~1" &&
156 test_config i18n.commitencoding iso-8859-7 &&
157 echo rosten >file &&
158 git commit -s -F "$TEST_DIRECTORY/t9350/broken-iso-8859-7-commit-message.txt" file &&
127 - git fast-export wer^..wer >iso-8859-7.fi &&
159 + git fast-export --reencode=yes wer^..wer >iso-8859-7.fi &&
160 sed "s/wer/i18n-invalid/" iso-8859-7.fi |
161 (cd new &&
162 git fast-import &&