fast-export: add a --show-original-ids option to show original names

Knowing the original names (hashes) of commits can sometimes enable post-filtering that would otherwise be difficult or impossible. In particular, the desire to rewrite commit messages which refer to other prior commits (on top of whatever other filtering is being done) is very difficult without knowing the original names of each commit. In addition, knowing the original names (hashes) of blobs can allow filtering by blob-id without requiring re-hashing the content of the blob, and is thus useful as a small optimization. Once we add original ids for both commits and blobs, we may as well add them for tags too for completeness. Perhaps someone will have a use for them. This commit teaches a new --show-original-ids option to fast-export which will make it add a 'original-oid <hash>' line to blob, commits, and tags. It also teaches fast-import to parse (and ignore) such lines. Signed-off-by: Elijah Newren <newren@gmail.com> Acked-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Nov 15, 2018 at 23:59 UTC a965bb31166d04f3e5c8f7a93569fb73f9a9d749
5 files changed +67 -5
Documentation/git-fast-export.txt
+7
@@ -122,6 +122,13 @@ marks the same across runs.
122 repository which already contains the necessary parent
123 commits.
124
125 +--show-original-ids::
126 + Add an extra directive to the output for commits and blobs,
127 + `original-oid <SHA1SUM>`. While such directives will likely be
128 + ignored by importers such as git-fast-import, it may be useful
129 + for intermediary filters (e.g. for rewriting commit messages
130 + which refer to older commits, or for stripping blobs by id).
131 +
132 --refspec::
133 Apply the specified refspec to each ref exported. Multiple of them can
134 be specified.
Documentation/git-fast-import.txt
+16
@@ -385,6 +385,7 @@ change to the project.
385 ....
386 'commit' SP <ref> LF
387 mark?
388 + original-oid?
389 ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
390 'committer' (SP <name>)? SP LT <email> GT SP <when> LF
391 data
@@ -741,6 +742,19 @@ New marks are created automatically. Existing marks can be moved
742 to another object simply by reusing the same `<idnum>` in another
743 `mark` command.
744
745 +`original-oid`
746 +~~~~~~~~~~~~~~
747 +Provides the name of the object in the original source control system.
748 +fast-import will simply ignore this directive, but filter processes
749 +which operate on and modify the stream before feeding to fast-import
750 +may have uses for this information
751 +
752 +....
753 + 'original-oid' SP <object-identifier> LF
754 +....
755 +
756 +where `<object-identifer>` is any string not containing LF.
757 +
758 `tag`
759 ~~~~~
760 Creates an annotated tag referring to a specific commit. To create
@@ -749,6 +763,7 @@ lightweight (non-annotated) tags see the `reset` command below.
763 ....
764 'tag' SP <name> LF
765 'from' SP <commit-ish> LF
766 + original-oid?
767 'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
768 data
769 ....
@@ -823,6 +838,7 @@ assigned mark.
838 ....
839 'blob' LF
840 mark?
841 + original-oid?
842 data
843 ....
844
builtin/fast-export.c
+15 -5
@@ -38,6 +38,7 @@ static int use_done_feature;
38 static int no_data;
39 static int full_tree;
40 static int reference_excluded_commits;
41 +static int show_original_ids;
42 static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
43 static struct string_list tag_refs = STRING_LIST_INIT_NODUP;
44 static struct refspec refspecs = REFSPEC_INIT_FETCH;
@@ -271,7 +272,10 @@ static void export_blob(const struct object_id *oid)
272
273 mark_next_object(object);
274
274 - printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size);
275 + printf("blob\nmark :%"PRIu32"\n", last_idnum);
276 + if (show_original_ids)
277 + printf("original-oid %s\n", oid_to_hex(oid));
278 + printf("data %lu\n", size);
279 if (size && fwrite(buf, size, 1, stdout) != 1)
280 die_errno("could not write blob '%s'", oid_to_hex(oid));
281 printf("\n");
@@ -635,8 +639,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
639 reencoded = reencode_string(message, "UTF-8", encoding);
640 if (!commit->parents)
641 printf("reset %s\n", refname);
638 - printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
639 - refname, last_idnum,
642 + printf("commit %s\nmark :%"PRIu32"\n", refname, last_idnum);
643 + if (show_original_ids)
644 + printf("original-oid %s\n", oid_to_hex(&commit->object.oid));
645 + printf("%.*s\n%.*s\ndata %u\n%s",
646 (int)(author_end - author), author,
647 (int)(committer_end - committer), committer,
648 (unsigned)(reencoded
@@ -814,8 +820,10 @@ static void handle_tag(const char *name, struct tag *tag)
820
821 if (starts_with(name, "refs/tags/"))
822 name += 10;
817 - printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
818 - name, tagged_mark,
823 + printf("tag %s\nfrom :%d\n", name, tagged_mark);
824 + if (show_original_ids)
825 + printf("original-oid %s\n", oid_to_hex(&tag->object.oid));
826 + printf("%.*s%sdata %d\n%.*s\n",
827 (int)(tagger_end - tagger), tagger,
828 tagger == tagger_end ? "" : "\n",
829 (int)message_size, (int)message_size, message ? message : "");
@@ -1096,6 +1104,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
1104 OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")),
1105 OPT_BOOL(0, "reference-excluded-parents",
1106 &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")),
1107 + OPT_BOOL(0, "show-original-ids", &show_original_ids,
1108 + N_("Show original object ids of blobs/commits")),
1109
1110 OPT_END()
1111 };
fast-import.c
+12
@@ -1814,6 +1814,13 @@ static void parse_mark(void)
1814 next_mark = 0;
1815 }
1816
1817 +static void parse_original_identifier(void)
1818 +{
1819 + const char *v;
1820 + if (skip_prefix(command_buf.buf, "original-oid ", &v))
1821 + read_next_command();
1822 +}
1823 +
1824 static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
1825 {
1826 const char *data;
@@ -1956,6 +1963,7 @@ static void parse_new_blob(void)
1963 {
1964 read_next_command();
1965 parse_mark();
1966 + parse_original_identifier();
1967 parse_and_store_blob(&last_blob, NULL, next_mark);
1968 }
1969
@@ -2579,6 +2587,7 @@ static void parse_new_commit(const char *arg)
2587
2588 read_next_command();
2589 parse_mark();
2590 + parse_original_identifier();
2591 if (skip_prefix(command_buf.buf, "author ", &v)) {
2592 author = parse_ident(v);
2593 read_next_command();
@@ -2711,6 +2720,9 @@ static void parse_new_tag(const char *arg)
2720 die("Invalid ref name or SHA1 expression: %s", from);
2721 read_next_command();
2722
2723 + /* original-oid ... */
2724 + parse_original_identifier();
2725 +
2726 /* tagger ... */
2727 if (skip_prefix(command_buf.buf, "tagger ", &v)) {
2728 tagger = parse_ident(v);
t/t9350-fast-export.sh
+17
@@ -77,6 +77,23 @@ test_expect_success 'fast-export --reference-excluded-parents master~2..master'
77 test $MASTER = $(git rev-parse --verify refs/heads/rewrite))
78 '
79
80 +test_expect_success 'fast-export --show-original-ids' '
81 +
82 + git fast-export --show-original-ids master >output &&
83 + grep ^original-oid output| sed -e s/^original-oid.// | sort >actual &&
84 + git rev-list --objects master muss >objects-and-names &&
85 + awk "{print \$1}" objects-and-names | sort >commits-trees-blobs &&
86 + comm -23 actual commits-trees-blobs >unfound &&
87 + test_must_be_empty unfound
88 +'
89 +
90 +test_expect_success 'fast-export --show-original-ids | git fast-import' '
91 +
92 + git fast-export --show-original-ids master muss | git fast-import --quiet &&
93 + test $MASTER = $(git rev-parse --verify refs/heads/master) &&
94 + test $MUSS = $(git rev-parse --verify refs/tags/muss)
95 +'
96 +
97 test_expect_success 'iso-8859-1' '
98
99 git config i18n.commitencoding ISO8859-1 &&