fast-export: ensure we export requested refs
If file paths are specified to fast-export and a ref points to a commit that does not touch any of the relevant paths, then that ref would sometimes fail to be exported. (This depends on whether any ancestors of the commit which do touch the relevant paths would be exported with that same ref name or a different ref name.) To avoid this problem, put *all* specified refs into extra_refs to start, and then as we export each commit, remove the refname used in the 'commit $REFNAME' directive from extra_refs. Then, in handle_tags_and_duplicates() we know which refs actually do need a manual reset directive in order to be included. This means that we do need some special handling for excluded refs; e.g. if someone runs git fast-export ^master master then they've asked for master to be exported, but they have also asked for the commit which master points to and all of its history to be excluded. That logically means ref deletion. Previously, such refs were just silently omitted from being exported despite having been explicitly requested for export. Signed-off-by: Elijah Newren <newren@gmail.com> Acked-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
parent
cd13762d8f
commit
fdf31b6369
|
@ -38,6 +38,7 @@ static int use_done_feature;
|
||||||
static int no_data;
|
static int no_data;
|
||||||
static int full_tree;
|
static int full_tree;
|
||||||
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;
|
||||||
|
@ -612,6 +613,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);
|
||||||
|
@ -815,7 +823,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)
|
||||||
|
@ -874,25 +882,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);
|
||||||
|
@ -913,8 +926,24 @@ static void handle_tags_and_duplicates(void)
|
||||||
name, oid_to_hex(&null_oid));
|
name, oid_to_hex(&null_oid));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
printf("reset %s\nfrom :%d\n\n", name,
|
|
||||||
get_object_mark(&commit->object));
|
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 the user
|
||||||
|
* wants the branch exported but every commit
|
||||||
|
* in its history to be deleted, that sounds
|
||||||
|
* like a ref deletion to me.
|
||||||
|
*/
|
||||||
|
printf("reset %s\nfrom %s\n\n",
|
||||||
|
name, oid_to_hex(&null_oid));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("reset %s\nfrom :%d\n\n", name, mark
|
||||||
|
);
|
||||||
show_progress();
|
show_progress();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1102,7 +1131,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)
|
||||||
|
|
|
@ -544,10 +544,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