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 oidmaint
commit
4d59753227
|
@ -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
|
||||||
--------
|
--------
|
||||||
|
|
|
@ -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
|
||||||
....
|
....
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
166
fast-import.c
166
fast-import.c
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue