Skip to content

Commit fdf31b6

Browse files
newrengitster
authored andcommitted
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 <[email protected]> Acked-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent cd13762 commit fdf31b6

File tree

2 files changed

+55
-15
lines changed

2 files changed

+55
-15
lines changed

builtin/fast-export.c

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ static int use_done_feature;
3838
static int no_data;
3939
static int full_tree;
4040
static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
41+
static struct string_list tag_refs = STRING_LIST_INIT_NODUP;
4142
static struct refspec refspecs = REFSPEC_INIT_FETCH;
4243
static int anonymize;
4344
static struct revision_sources revision_sources;
@@ -612,6 +613,13 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
612613
export_blob(&diff_queued_diff.queue[i]->two->oid);
613614

614615
refname = *revision_sources_at(&revision_sources, commit);
616+
/*
617+
* FIXME: string_list_remove() below for each ref is overall
618+
* O(N^2). Compared to a history walk and diffing trees, this is
619+
* just lost in the noise in practice. However, theoretically a
620+
* repo may have enough refs for this to become slow.
621+
*/
622+
string_list_remove(&extra_refs, refname, 0);
615623
if (anonymize) {
616624
refname = anonymize_refname(refname);
617625
anonymize_ident_line(&committer, &committer_end);
@@ -815,7 +823,7 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)
815823
/* handle nested tags */
816824
while (tag && tag->object.type == OBJ_TAG) {
817825
parse_object(the_repository, &tag->object.oid);
818-
string_list_append(&extra_refs, full_name)->util = tag;
826+
string_list_append(&tag_refs, full_name)->util = tag;
819827
tag = (struct tag *)tag->tagged;
820828
}
821829
if (!tag)
@@ -874,25 +882,30 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
874882
}
875883

876884
/*
877-
* This ref will not be updated through a commit, lets make
878-
* sure it gets properly updated eventually.
885+
* Make sure this ref gets properly updated eventually, whether
886+
* through a commit or manually at the end.
879887
*/
880-
if (*revision_sources_at(&revision_sources, commit) ||
881-
commit->object.flags & SHOWN)
888+
if (e->item->type != OBJ_TAG)
882889
string_list_append(&extra_refs, full_name)->util = commit;
890+
883891
if (!*revision_sources_at(&revision_sources, commit))
884892
*revision_sources_at(&revision_sources, commit) = full_name;
885893
}
894+
895+
string_list_sort(&extra_refs);
896+
string_list_remove_duplicates(&extra_refs, 0);
886897
}
887898

888-
static void handle_tags_and_duplicates(void)
899+
static void handle_tags_and_duplicates(struct string_list *extras)
889900
{
890901
struct commit *commit;
891902
int i;
892903

893-
for (i = extra_refs.nr - 1; i >= 0; i--) {
894-
const char *name = extra_refs.items[i].string;
895-
struct object *object = extra_refs.items[i].util;
904+
for (i = extras->nr - 1; i >= 0; i--) {
905+
const char *name = extras->items[i].string;
906+
struct object *object = extras->items[i].util;
907+
int mark;
908+
896909
switch (object->type) {
897910
case OBJ_TAG:
898911
handle_tag(name, (struct tag *)object);
@@ -913,8 +926,24 @@ static void handle_tags_and_duplicates(void)
913926
name, oid_to_hex(&null_oid));
914927
continue;
915928
}
916-
printf("reset %s\nfrom :%d\n\n", name,
917-
get_object_mark(&commit->object));
929+
930+
mark = get_object_mark(&commit->object);
931+
if (!mark) {
932+
/*
933+
* Getting here means we have a commit which
934+
* was excluded by a negative refspec (e.g.
935+
* fast-export ^master master). If the user
936+
* wants the branch exported but every commit
937+
* in its history to be deleted, that sounds
938+
* like a ref deletion to me.
939+
*/
940+
printf("reset %s\nfrom %s\n\n",
941+
name, oid_to_hex(&null_oid));
942+
continue;
943+
}
944+
945+
printf("reset %s\nfrom :%d\n\n", name, mark
946+
);
918947
show_progress();
919948
break;
920949
}
@@ -1102,7 +1131,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
11021131
}
11031132
}
11041133

1105-
handle_tags_and_duplicates();
1134+
handle_tags_and_duplicates(&extra_refs);
1135+
handle_tags_and_duplicates(&tag_refs);
11061136
handle_deletes();
11071137

11081138
if (export_filename && lastimportid != last_idnum)

t/t9350-fast-export.sh

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,10 +544,20 @@ test_expect_success 'use refspec' '
544544
test_cmp expected actual
545545
'
546546

547-
test_expect_success 'delete refspec' '
547+
test_expect_success 'delete ref because entire history excluded' '
548548
git branch to-delete &&
549-
git fast-export --refspec :refs/heads/to-delete to-delete ^to-delete > actual &&
550-
cat > expected <<-EOF &&
549+
git fast-export to-delete ^to-delete >actual &&
550+
cat >expected <<-EOF &&
551+
reset refs/heads/to-delete
552+
from 0000000000000000000000000000000000000000
553+
554+
EOF
555+
test_cmp expected actual
556+
'
557+
558+
test_expect_success 'delete refspec' '
559+
git fast-export --refspec :refs/heads/to-delete >actual &&
560+
cat >expected <<-EOF &&
551561
reset refs/heads/to-delete
552562
from 0000000000000000000000000000000000000000
553563

0 commit comments

Comments
 (0)