Merge branch 'en/fast-export-import'

Small fixes and features for fast-export and fast-import, mostly on
the fast-export side.

* en/fast-export-import:
  fast-export: add a --show-original-ids option to show original names
  fast-import: remove unmaintained duplicate documentation
  fast-export: add --reference-excluded-parents option
  fast-export: ensure we export requested refs
  fast-export: when using paths, avoid corrupt stream with non-existent mark
  fast-export: move commit rewriting logic into a function for reuse
  fast-export: avoid dying when filtering by paths and old tags exist
  fast-export: use value from correct enum
  git-fast-export.txt: clarify misleading documentation about rev-list args
  git-fast-import.txt: fix documentation for --quiet option
  fast-export: convert sha1 to oid
maint
Junio C Hamano 2019-01-04 13:33:33 -08:00
commit 4d59753227
5 changed files with 268 additions and 214 deletions

View File

@ -110,6 +110,25 @@ marks the same across runs.
the shape of the history and stored tree. See the section on the shape of the history and stored tree. See the section on
`ANONYMIZING` below. `ANONYMIZING` below.


--reference-excluded-parents::
By default, running a command such as `git fast-export
master~5..master` will not include the commit master{tilde}5
and will make master{tilde}4 no longer have master{tilde}5 as
a parent (though both the old master{tilde}4 and new
master{tilde}4 will have all the same files). Use
--reference-excluded-parents to instead have the the stream
refer to commits in the excluded range of history by their
sha1sum. Note that the resulting stream can only be used by a
repository which already contains the necessary parent
commits.

--show-original-ids::
Add an extra directive to the output for commits and blobs,
`original-oid <SHA1SUM>`. While such directives will likely be
ignored by importers such as git-fast-import, it may be useful
for intermediary filters (e.g. for rewriting commit messages
which refer to older commits, or for stripping blobs by id).

--refspec:: --refspec::
Apply the specified refspec to each ref exported. Multiple of them can Apply the specified refspec to each ref exported. Multiple of them can
be specified. be specified.
@ -119,7 +138,9 @@ marks the same across runs.
'git rev-list', that specifies the specific objects and references 'git rev-list', that specifies the specific objects and references
to export. For example, `master~10..master` causes the to export. For example, `master~10..master` causes the
current master reference to be exported along with all objects current master reference to be exported along with all objects
added since its 10th ancestor commit. added since its 10th ancestor commit and (unless the
--reference-excluded-parents option is specified) all files
common to master{tilde}9 and master{tilde}10.


EXAMPLES EXAMPLES
-------- --------

View File

@ -40,9 +40,10 @@ OPTIONS
not contain the old commit). not contain the old commit).


--quiet:: --quiet::
Disable all non-fatal output, making fast-import silent when it Disable the output shown by --stats, making fast-import usually
is successful. This option disables the output shown by be silent when it is successful. However, if the import stream
--stats. has directives intended to show user output (e.g. `progress`
directives), the corresponding messages will still be shown.


--stats:: --stats::
Display some basic statistics about the objects fast-import has Display some basic statistics about the objects fast-import has
@ -384,6 +385,7 @@ change to the project.
.... ....
'commit' SP <ref> LF 'commit' SP <ref> LF
mark? mark?
original-oid?
('author' (SP <name>)? SP LT <email> GT SP <when> LF)? ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
'committer' (SP <name>)? SP LT <email> GT SP <when> LF 'committer' (SP <name>)? SP LT <email> GT SP <when> LF
data data
@ -740,6 +742,19 @@ New marks are created automatically. Existing marks can be moved
to another object simply by reusing the same `<idnum>` in another to another object simply by reusing the same `<idnum>` in another
`mark` command. `mark` command.


`original-oid`
~~~~~~~~~~~~~~
Provides the name of the object in the original source control system.
fast-import will simply ignore this directive, but filter processes
which operate on and modify the stream before feeding to fast-import
may have uses for this information

....
'original-oid' SP <object-identifier> LF
....

where `<object-identifer>` is any string not containing LF.

`tag` `tag`
~~~~~ ~~~~~
Creates an annotated tag referring to a specific commit. To create Creates an annotated tag referring to a specific commit. To create
@ -748,6 +763,7 @@ lightweight (non-annotated) tags see the `reset` command below.
.... ....
'tag' SP <name> LF 'tag' SP <name> LF
'from' SP <commit-ish> LF 'from' SP <commit-ish> LF
original-oid?
'tagger' (SP <name>)? SP LT <email> GT SP <when> LF 'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
data data
.... ....
@ -822,6 +838,7 @@ assigned mark.
.... ....
'blob' LF 'blob' LF
mark? mark?
original-oid?
data data
.... ....



View File

@ -31,13 +31,16 @@ static const char *fast_export_usage[] = {
}; };


static int progress; static int progress;
static enum { ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = ABORT; static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;
static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR; static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;
static int fake_missing_tagger; static int fake_missing_tagger;
static int use_done_feature; static int use_done_feature;
static int no_data; static int no_data;
static int full_tree; static int full_tree;
static int reference_excluded_commits;
static int show_original_ids;
static struct string_list extra_refs = STRING_LIST_INIT_NODUP; static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
static struct string_list tag_refs = STRING_LIST_INIT_NODUP;
static struct refspec refspecs = REFSPEC_INIT_FETCH; static struct refspec refspecs = REFSPEC_INIT_FETCH;
static int anonymize; static int anonymize;
static struct revision_sources revision_sources; static struct revision_sources revision_sources;
@ -46,7 +49,7 @@ static int parse_opt_signed_tag_mode(const struct option *opt,
const char *arg, int unset) const char *arg, int unset)
{ {
if (unset || !strcmp(arg, "abort")) if (unset || !strcmp(arg, "abort"))
signed_tag_mode = ABORT; signed_tag_mode = SIGNED_TAG_ABORT;
else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
signed_tag_mode = VERBATIM; signed_tag_mode = VERBATIM;
else if (!strcmp(arg, "warn")) else if (!strcmp(arg, "warn"))
@ -64,7 +67,7 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt,
const char *arg, int unset) const char *arg, int unset)
{ {
if (unset || !strcmp(arg, "abort")) if (unset || !strcmp(arg, "abort"))
tag_of_filtered_mode = ERROR; tag_of_filtered_mode = TAG_FILTERING_ABORT;
else if (!strcmp(arg, "drop")) else if (!strcmp(arg, "drop"))
tag_of_filtered_mode = DROP; tag_of_filtered_mode = DROP;
else if (!strcmp(arg, "rewrite")) else if (!strcmp(arg, "rewrite"))
@ -187,6 +190,22 @@ static int get_object_mark(struct object *object)
return ptr_to_mark(decoration); return ptr_to_mark(decoration);
} }


static struct commit *rewrite_commit(struct commit *p)
{
for (;;) {
if (p->parents && p->parents->next)
break;
if (p->object.flags & UNINTERESTING)
break;
if (!(p->object.flags & TREESAME))
break;
if (!p->parents)
return NULL;
p = p->parents->item;
}
return p;
}

static void show_progress(void) static void show_progress(void)
{ {
static int counter = 0; static int counter = 0;
@ -243,7 +262,7 @@ static void export_blob(const struct object_id *oid)
if (!buf) if (!buf)
die("could not read blob %s", oid_to_hex(oid)); die("could not read blob %s", oid_to_hex(oid));
if (check_object_signature(oid, buf, size, type_name(type)) < 0) if (check_object_signature(oid, buf, size, type_name(type)) < 0)
die("sha1 mismatch in blob %s", oid_to_hex(oid)); die("oid mismatch in blob %s", oid_to_hex(oid));
object = parse_object_buffer(the_repository, oid, type, object = parse_object_buffer(the_repository, oid, type,
size, buf, &eaten); size, buf, &eaten);
} }
@ -253,7 +272,10 @@ static void export_blob(const struct object_id *oid)


mark_next_object(object); mark_next_object(object);


printf("blob\nmark :%"PRIu32"\ndata %"PRIuMAX"\n", last_idnum, (uintmax_t)size); printf("blob\nmark :%"PRIu32"\n", last_idnum);
if (show_original_ids)
printf("original-oid %s\n", oid_to_hex(oid));
printf("data %"PRIuMAX"\n", (uintmax_t)size);
if (size && fwrite(buf, size, 1, stdout) != 1) if (size && fwrite(buf, size, 1, stdout) != 1)
die_errno("could not write blob '%s'", oid_to_hex(oid)); die_errno("could not write blob '%s'", oid_to_hex(oid));
printf("\n"); printf("\n");
@ -330,17 +352,18 @@ static void print_path(const char *path)


static void *generate_fake_oid(const void *old, size_t *len) static void *generate_fake_oid(const void *old, size_t *len)
{ {
static uint32_t counter = 1; /* avoid null sha1 */ static uint32_t counter = 1; /* avoid null oid */
unsigned char *out = xcalloc(GIT_SHA1_RAWSZ, 1); const unsigned hashsz = the_hash_algo->rawsz;
put_be32(out + GIT_SHA1_RAWSZ - 4, counter++); unsigned char *out = xcalloc(hashsz, 1);
put_be32(out + hashsz - 4, counter++);
return out; return out;
} }


static const unsigned char *anonymize_sha1(const struct object_id *oid) static const struct object_id *anonymize_oid(const struct object_id *oid)
{ {
static struct hashmap sha1s; static struct hashmap objs;
size_t len = GIT_SHA1_RAWSZ; size_t len = the_hash_algo->rawsz;
return anonymize_mem(&sha1s, generate_fake_oid, oid, &len); return anonymize_mem(&objs, generate_fake_oid, oid, &len);
} }


static void show_filemodify(struct diff_queue_struct *q, static void show_filemodify(struct diff_queue_struct *q,
@ -399,9 +422,9 @@ static void show_filemodify(struct diff_queue_struct *q,
*/ */
if (no_data || S_ISGITLINK(spec->mode)) if (no_data || S_ISGITLINK(spec->mode))
printf("M %06o %s ", spec->mode, printf("M %06o %s ", spec->mode,
sha1_to_hex(anonymize ? oid_to_hex(anonymize ?
anonymize_sha1(&spec->oid) : anonymize_oid(&spec->oid) :
spec->oid.hash)); &spec->oid));
else { else {
struct object *object = lookup_object(the_repository, struct object *object = lookup_object(the_repository,
spec->oid.hash); spec->oid.hash);
@ -579,7 +602,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
message += 2; message += 2;


if (commit->parents && if (commit->parents &&
get_object_mark(&commit->parents->item->object) != 0 && (get_object_mark(&commit->parents->item->object) != 0 ||
reference_excluded_commits) &&
!full_tree) { !full_tree) {
parse_commit_or_die(commit->parents->item); parse_commit_or_die(commit->parents->item);
diff_tree_oid(get_commit_tree_oid(commit->parents->item), diff_tree_oid(get_commit_tree_oid(commit->parents->item),
@ -595,6 +619,13 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
export_blob(&diff_queued_diff.queue[i]->two->oid); export_blob(&diff_queued_diff.queue[i]->two->oid);


refname = *revision_sources_at(&revision_sources, commit); refname = *revision_sources_at(&revision_sources, commit);
/*
* FIXME: string_list_remove() below for each ref is overall
* O(N^2). Compared to a history walk and diffing trees, this is
* just lost in the noise in practice. However, theoretically a
* repo may have enough refs for this to become slow.
*/
string_list_remove(&extra_refs, refname, 0);
if (anonymize) { if (anonymize) {
refname = anonymize_refname(refname); refname = anonymize_refname(refname);
anonymize_ident_line(&committer, &committer_end); anonymize_ident_line(&committer, &committer_end);
@ -608,8 +639,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
reencoded = reencode_string(message, "UTF-8", encoding); reencoded = reencode_string(message, "UTF-8", encoding);
if (!commit->parents) if (!commit->parents)
printf("reset %s\n", refname); printf("reset %s\n", refname);
printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s", printf("commit %s\nmark :%"PRIu32"\n", refname, last_idnum);
refname, last_idnum, if (show_original_ids)
printf("original-oid %s\n", oid_to_hex(&commit->object.oid));
printf("%.*s\n%.*s\ndata %u\n%s",
(int)(author_end - author), author, (int)(author_end - author), author,
(int)(committer_end - committer), committer, (int)(committer_end - committer), committer,
(unsigned)(reencoded (unsigned)(reencoded
@ -620,13 +653,21 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
unuse_commit_buffer(commit, commit_buffer); unuse_commit_buffer(commit, commit_buffer);


for (i = 0, p = commit->parents; p; p = p->next) { for (i = 0, p = commit->parents; p; p = p->next) {
int mark = get_object_mark(&p->item->object); struct object *obj = &p->item->object;
if (!mark) int mark = get_object_mark(obj);

if (!mark && !reference_excluded_commits)
continue; continue;
if (i == 0) if (i == 0)
printf("from :%d\n", mark); printf("from ");
else else
printf("merge :%d\n", mark); printf("merge ");
if (mark)
printf(":%d\n", mark);
else
printf("%s\n", oid_to_hex(anonymize ?
anonymize_oid(&obj->oid) :
&obj->oid));
i++; i++;
} }


@ -727,7 +768,7 @@ static void handle_tag(const char *name, struct tag *tag)
"\n-----BEGIN PGP SIGNATURE-----\n"); "\n-----BEGIN PGP SIGNATURE-----\n");
if (signature) if (signature)
switch(signed_tag_mode) { switch(signed_tag_mode) {
case ABORT: case SIGNED_TAG_ABORT:
die("encountered signed tag %s; use " die("encountered signed tag %s; use "
"--signed-tags=<mode> to handle it", "--signed-tags=<mode> to handle it",
oid_to_hex(&tag->object.oid)); oid_to_hex(&tag->object.oid));
@ -752,7 +793,7 @@ static void handle_tag(const char *name, struct tag *tag)
tagged_mark = get_object_mark(tagged); tagged_mark = get_object_mark(tagged);
if (!tagged_mark) { if (!tagged_mark) {
switch(tag_of_filtered_mode) { switch(tag_of_filtered_mode) {
case ABORT: case TAG_FILTERING_ABORT:
die("tag %s tags unexported object; use " die("tag %s tags unexported object; use "
"--tag-of-filtered-object=<mode> to handle it", "--tag-of-filtered-object=<mode> to handle it",
oid_to_hex(&tag->object.oid)); oid_to_hex(&tag->object.oid));
@ -766,18 +807,12 @@ static void handle_tag(const char *name, struct tag *tag)
oid_to_hex(&tag->object.oid), oid_to_hex(&tag->object.oid),
type_name(tagged->type)); type_name(tagged->type));
} }
p = (struct commit *)tagged; p = rewrite_commit((struct commit *)tagged);
for (;;) { if (!p) {
if (p->parents && p->parents->next) printf("reset %s\nfrom %s\n\n",
break; name, oid_to_hex(&null_oid));
if (p->object.flags & UNINTERESTING) free(buf);
break; return;
if (!(p->object.flags & TREESAME))
break;
if (!p->parents)
die("can't find replacement commit for tag %s",
oid_to_hex(&tag->object.oid));
p = p->parents->item;
} }
tagged_mark = get_object_mark(&p->object); tagged_mark = get_object_mark(&p->object);
} }
@ -785,8 +820,10 @@ static void handle_tag(const char *name, struct tag *tag)


if (starts_with(name, "refs/tags/")) if (starts_with(name, "refs/tags/"))
name += 10; name += 10;
printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n", printf("tag %s\nfrom :%d\n", name, tagged_mark);
name, tagged_mark, if (show_original_ids)
printf("original-oid %s\n", oid_to_hex(&tag->object.oid));
printf("%.*s%sdata %d\n%.*s\n",
(int)(tagger_end - tagger), tagger, (int)(tagger_end - tagger), tagger,
tagger == tagger_end ? "" : "\n", tagger == tagger_end ? "" : "\n",
(int)message_size, (int)message_size, message ? message : ""); (int)message_size, (int)message_size, message ? message : "");
@ -804,7 +841,7 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)
/* handle nested tags */ /* handle nested tags */
while (tag && tag->object.type == OBJ_TAG) { while (tag && tag->object.type == OBJ_TAG) {
parse_object(the_repository, &tag->object.oid); parse_object(the_repository, &tag->object.oid);
string_list_append(&extra_refs, full_name)->util = tag; string_list_append(&tag_refs, full_name)->util = tag;
tag = (struct tag *)tag->tagged; tag = (struct tag *)tag->tagged;
} }
if (!tag) if (!tag)
@ -863,25 +900,30 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
} }


/* /*
* This ref will not be updated through a commit, lets make * Make sure this ref gets properly updated eventually, whether
* sure it gets properly updated eventually. * through a commit or manually at the end.
*/ */
if (*revision_sources_at(&revision_sources, commit) || if (e->item->type != OBJ_TAG)
commit->object.flags & SHOWN)
string_list_append(&extra_refs, full_name)->util = commit; string_list_append(&extra_refs, full_name)->util = commit;

if (!*revision_sources_at(&revision_sources, commit)) if (!*revision_sources_at(&revision_sources, commit))
*revision_sources_at(&revision_sources, commit) = full_name; *revision_sources_at(&revision_sources, commit) = full_name;
} }

string_list_sort(&extra_refs);
string_list_remove_duplicates(&extra_refs, 0);
} }


static void handle_tags_and_duplicates(void) static void handle_tags_and_duplicates(struct string_list *extras)
{ {
struct commit *commit; struct commit *commit;
int i; int i;


for (i = extra_refs.nr - 1; i >= 0; i--) { for (i = extras->nr - 1; i >= 0; i--) {
const char *name = extra_refs.items[i].string; const char *name = extras->items[i].string;
struct object *object = extra_refs.items[i].util; struct object *object = extras->items[i].util;
int mark;

switch (object->type) { switch (object->type) {
case OBJ_TAG: case OBJ_TAG:
handle_tag(name, (struct tag *)object); handle_tag(name, (struct tag *)object);
@ -890,9 +932,45 @@ static void handle_tags_and_duplicates(void)
if (anonymize) if (anonymize)
name = anonymize_refname(name); name = anonymize_refname(name);
/* create refs pointing to already seen commits */ /* create refs pointing to already seen commits */
commit = (struct commit *)object; commit = rewrite_commit((struct commit *)object);
printf("reset %s\nfrom :%d\n\n", name, if (!commit) {
get_object_mark(&commit->object)); /*
* Neither this object nor any of its
* ancestors touch any relevant paths, so
* it has been filtered to nothing. Delete
* it.
*/
printf("reset %s\nfrom %s\n\n",
name, oid_to_hex(&null_oid));
continue;
}

mark = get_object_mark(&commit->object);
if (!mark) {
/*
* Getting here means we have a commit which
* was excluded by a negative refspec (e.g.
* fast-export ^master master). If we are
* referencing excluded commits, set the ref
* to the exact commit. Otherwise, the user
* wants the branch exported but every commit
* in its history to be deleted, which basically
* just means deletion of the ref.
*/
if (!reference_excluded_commits) {
/* delete the ref */
printf("reset %s\nfrom %s\n\n",
name, oid_to_hex(&null_oid));
continue;
}
/* set ref to commit using oid, not mark */
printf("reset %s\nfrom %s\n\n", name,
oid_to_hex(&commit->object.oid));
continue;
}

printf("reset %s\nfrom :%d\n\n", name, mark
);
show_progress(); show_progress();
break; break;
} }
@ -988,7 +1066,7 @@ static void handle_deletes(void)
continue; continue;


printf("reset %s\nfrom %s\n\n", printf("reset %s\nfrom %s\n\n",
refspec->dst, sha1_to_hex(null_sha1)); refspec->dst, oid_to_hex(&null_oid));
} }
} }


@ -1024,6 +1102,11 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"),
N_("Apply refspec to exported refs")), N_("Apply refspec to exported refs")),
OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")),
OPT_BOOL(0, "reference-excluded-parents",
&reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")),
OPT_BOOL(0, "show-original-ids", &show_original_ids,
N_("Show original object ids of blobs/commits")),

OPT_END() OPT_END()
}; };


@ -1080,7 +1163,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
} }
} }


handle_tags_and_duplicates(); handle_tags_and_duplicates(&extra_refs);
handle_tags_and_duplicates(&tag_refs);
handle_deletes(); handle_deletes();


if (export_filename && lastimportid != last_idnum) if (export_filename && lastimportid != last_idnum)

View File

@ -1,157 +1,3 @@
/*
(See Documentation/git-fast-import.txt for maintained documentation.)
Format of STDIN stream:

stream ::= cmd*;

cmd ::= new_blob
| new_commit
| new_tag
| reset_branch
| checkpoint
| progress
;

new_blob ::= 'blob' lf
mark?
file_content;
file_content ::= data;

new_commit ::= 'commit' sp ref_str lf
mark?
('author' (sp name)? sp '<' email '>' sp when lf)?
'committer' (sp name)? sp '<' email '>' sp when lf
commit_msg
('from' sp commit-ish lf)?
('merge' sp commit-ish lf)*
(file_change | ls)*
lf?;
commit_msg ::= data;

ls ::= 'ls' sp '"' quoted(path) '"' lf;

file_change ::= file_clr
| file_del
| file_rnm
| file_cpy
| file_obm
| file_inm;
file_clr ::= 'deleteall' lf;
file_del ::= 'D' sp path_str lf;
file_rnm ::= 'R' sp path_str sp path_str lf;
file_cpy ::= 'C' sp path_str sp path_str lf;
file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
data;
note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf;
note_inm ::= 'N' sp 'inline' sp commit-ish lf
data;

new_tag ::= 'tag' sp tag_str lf
'from' sp commit-ish lf
('tagger' (sp name)? sp '<' email '>' sp when lf)?
tag_msg;
tag_msg ::= data;

reset_branch ::= 'reset' sp ref_str lf
('from' sp commit-ish lf)?
lf?;

checkpoint ::= 'checkpoint' lf
lf?;

progress ::= 'progress' sp not_lf* lf
lf?;

# note: the first idnum in a stream should be 1 and subsequent
# idnums should not have gaps between values as this will cause
# the stream parser to reserve space for the gapped values. An
# idnum can be updated in the future to a new object by issuing
# a new mark directive with the old idnum.
#
mark ::= 'mark' sp idnum lf;
data ::= (delimited_data | exact_data)
lf?;

# note: delim may be any string but must not contain lf.
# data_line may contain any data but must not be exactly
# delim.
delimited_data ::= 'data' sp '<<' delim lf
(data_line lf)*
delim lf;

# note: declen indicates the length of binary_data in bytes.
# declen does not include the lf preceding the binary data.
#
exact_data ::= 'data' sp declen lf
binary_data;

# note: quoted strings are C-style quoting supporting \c for
# common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn
# is the signed byte value in octal. Note that the only
# characters which must actually be escaped to protect the
# stream formatting is: \, " and LF. Otherwise these values
# are UTF8.
#
commit-ish ::= (ref_str | hexsha1 | sha1exp_str | idnum);
ref_str ::= ref;
sha1exp_str ::= sha1exp;
tag_str ::= tag;
path_str ::= path | '"' quoted(path) '"' ;
mode ::= '100644' | '644'
| '100755' | '755'
| '120000'
;

declen ::= # unsigned 32 bit value, ascii base10 notation;
bigint ::= # unsigned integer value, ascii base10 notation;
binary_data ::= # file content, not interpreted;

when ::= raw_when | rfc2822_when;
raw_when ::= ts sp tz;
rfc2822_when ::= # Valid RFC 2822 date and time;

sp ::= # ASCII space character;
lf ::= # ASCII newline (LF) character;

# note: a colon (':') must precede the numerical value assigned to
# an idnum. This is to distinguish it from a ref or tag name as
# GIT does not permit ':' in ref or tag strings.
#
idnum ::= ':' bigint;
path ::= # GIT style file path, e.g. "a/b/c";
ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT";
tag ::= # GIT tag name, e.g. "FIREFOX_1_5";
sha1exp ::= # Any valid GIT SHA1 expression;
hexsha1 ::= # SHA1 in hexadecimal format;

# note: name and email are UTF8 strings, however name must not
# contain '<' or lf and email must not contain any of the
# following: '<', '>', lf.
#
name ::= # valid GIT author/committer name;
email ::= # valid GIT author/committer email;
ts ::= # time since the epoch in seconds, ascii base10 notation;
tz ::= # GIT style timezone;

# note: comments, get-mark, ls-tree, and cat-blob requests may
# appear anywhere in the input, except within a data command. Any
# form of the data command always escapes the related input from
# comment processing.
#
# In case it is not clear, the '#' that starts the comment
# must be the first character on that line (an lf
# preceded it).
#

get_mark ::= 'get-mark' sp idnum lf;
cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
ls_tree ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;

comment ::= '#' not_lf* lf;
not_lf ::= # Any byte that is not ASCII newline (LF);
*/

#include "builtin.h" #include "builtin.h"
#include "cache.h" #include "cache.h"
#include "repository.h" #include "repository.h"
@ -1968,6 +1814,13 @@ static void parse_mark(void)
next_mark = 0; next_mark = 0;
} }


static void parse_original_identifier(void)
{
const char *v;
if (skip_prefix(command_buf.buf, "original-oid ", &v))
read_next_command();
}

static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res) static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
{ {
const char *data; const char *data;
@ -2110,6 +1963,7 @@ static void parse_new_blob(void)
{ {
read_next_command(); read_next_command();
parse_mark(); parse_mark();
parse_original_identifier();
parse_and_store_blob(&last_blob, NULL, next_mark); parse_and_store_blob(&last_blob, NULL, next_mark);
} }


@ -2733,6 +2587,7 @@ static void parse_new_commit(const char *arg)


read_next_command(); read_next_command();
parse_mark(); parse_mark();
parse_original_identifier();
if (skip_prefix(command_buf.buf, "author ", &v)) { if (skip_prefix(command_buf.buf, "author ", &v)) {
author = parse_ident(v); author = parse_ident(v);
read_next_command(); read_next_command();
@ -2865,6 +2720,9 @@ static void parse_new_tag(const char *arg)
die("Invalid ref name or SHA1 expression: %s", from); die("Invalid ref name or SHA1 expression: %s", from);
read_next_command(); read_next_command();


/* original-oid ... */
parse_original_identifier();

/* tagger ... */ /* tagger ... */
if (skip_prefix(command_buf.buf, "tagger ", &v)) { if (skip_prefix(command_buf.buf, "tagger ", &v)) {
tagger = parse_ident(v); tagger = parse_ident(v);

View File

@ -66,6 +66,34 @@ test_expect_success 'fast-export master~2..master' '


' '


test_expect_success 'fast-export --reference-excluded-parents master~2..master' '

git fast-export --reference-excluded-parents master~2..master >actual &&
grep commit.refs/heads/master actual >commit-count &&
test_line_count = 2 commit-count &&
sed "s/master/rewrite/" actual |
(cd new &&
git fast-import &&
test $MASTER = $(git rev-parse --verify refs/heads/rewrite))
'

test_expect_success 'fast-export --show-original-ids' '

git fast-export --show-original-ids master >output &&
grep ^original-oid output| sed -e s/^original-oid.// | sort >actual &&
git rev-list --objects master muss >objects-and-names &&
awk "{print \$1}" objects-and-names | sort >commits-trees-blobs &&
comm -23 actual commits-trees-blobs >unfound &&
test_must_be_empty unfound
'

test_expect_success 'fast-export --show-original-ids | git fast-import' '

git fast-export --show-original-ids master muss | git fast-import --quiet &&
test $MASTER = $(git rev-parse --verify refs/heads/master) &&
test $MUSS = $(git rev-parse --verify refs/tags/muss)
'

test_expect_success 'iso-8859-1' ' test_expect_success 'iso-8859-1' '


git config i18n.commitencoding ISO8859-1 && git config i18n.commitencoding ISO8859-1 &&
@ -325,6 +353,22 @@ test_expect_success 'rewriting tag of filtered out object' '
) )
' '


test_expect_success 'rewrite tag predating pathspecs to nothing' '
test_create_repo rewrite_tag_predating_pathspecs &&
(
cd rewrite_tag_predating_pathspecs &&

test_commit initial &&

git tag -a -m "Some old tag" v0.0.0.0.0.0.1 &&

test_commit bar &&

git fast-export --tag-of-filtered-object=rewrite --all -- bar.t >output &&
grep from.$ZERO_OID output
)
'

cat > limit-by-paths/expected << EOF cat > limit-by-paths/expected << EOF
blob blob
mark :1 mark :1
@ -366,6 +410,26 @@ test_expect_success 'path limiting with import-marks does not lose unmodified fi
grep file0 actual grep file0 actual
' '


test_expect_success 'avoid corrupt stream with non-existent mark' '
test_create_repo avoid_non_existent_mark &&
(
cd avoid_non_existent_mark &&

test_commit important-path &&

test_commit ignored &&

git branch A &&
git branch B &&

echo foo >>important-path.t &&
git add important-path.t &&
test_commit more changes &&

git fast-export --all -- important-path.t | git fast-import --force
)
'

test_expect_success 'full-tree re-shows unmodified files' ' test_expect_success 'full-tree re-shows unmodified files' '
git checkout -f simple && git checkout -f simple &&
git fast-export --full-tree simple >actual && git fast-export --full-tree simple >actual &&
@ -508,10 +572,20 @@ test_expect_success 'use refspec' '
test_cmp expected actual test_cmp expected actual
' '


test_expect_success 'delete refspec' ' test_expect_success 'delete ref because entire history excluded' '
git branch to-delete && git branch to-delete &&
git fast-export --refspec :refs/heads/to-delete to-delete ^to-delete > actual && git fast-export to-delete ^to-delete >actual &&
cat > expected <<-EOF && cat >expected <<-EOF &&
reset refs/heads/to-delete
from 0000000000000000000000000000000000000000

EOF
test_cmp expected actual
'

test_expect_success 'delete refspec' '
git fast-export --refspec :refs/heads/to-delete >actual &&
cat >expected <<-EOF &&
reset refs/heads/to-delete reset refs/heads/to-delete
from 0000000000000000000000000000000000000000 from 0000000000000000000000000000000000000000