Skip to content

Commit ee2bd06

Browse files
KarthikNayakgitster
authored andcommitted
ref-filter: implement '--contains' option
'tag -l' and 'branch -l' have two different ways of finding out if a certain ref contains a commit. Implement both these methods in ref-filter and give the caller of ref-filter API the option to pick which implementation to be used. 'branch -l' uses 'is_descendant_of()' from commit.c which is left as the default implementation to be used. 'tag -l' uses a more specific algorithm since ffc4b80. This implementation is used whenever the 'with_commit_tag_algo' bit is set in 'struct ref_filter'. Mentored-by: Christian Couder <[email protected]> Mentored-by: Matthieu Moy <[email protected]> Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f266c91 commit ee2bd06

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

builtin/tag.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
8686
return 0;
8787
}
8888

89+
/*
90+
* The entire code segment for supporting the --contains option has been
91+
* copied over to ref-filter.{c,h}. This will be deleted evetually when
92+
* we port tag.c to use ref-filter APIs.
93+
*/
8994
enum contains_result {
9095
CONTAINS_UNKNOWN = -1,
9196
CONTAINS_NO = 0,

ref-filter.c

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,114 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
818818
*v = &ref->value[atom];
819819
}
820820

821+
enum contains_result {
822+
CONTAINS_UNKNOWN = -1,
823+
CONTAINS_NO = 0,
824+
CONTAINS_YES = 1
825+
};
826+
827+
/*
828+
* Mimicking the real stack, this stack lives on the heap, avoiding stack
829+
* overflows.
830+
*
831+
* At each recursion step, the stack items points to the commits whose
832+
* ancestors are to be inspected.
833+
*/
834+
struct contains_stack {
835+
int nr, alloc;
836+
struct contains_stack_entry {
837+
struct commit *commit;
838+
struct commit_list *parents;
839+
} *contains_stack;
840+
};
841+
842+
static int in_commit_list(const struct commit_list *want, struct commit *c)
843+
{
844+
for (; want; want = want->next)
845+
if (!hashcmp(want->item->object.sha1, c->object.sha1))
846+
return 1;
847+
return 0;
848+
}
849+
850+
/*
851+
* Test whether the candidate or one of its parents is contained in the list.
852+
* Do not recurse to find out, though, but return -1 if inconclusive.
853+
*/
854+
static enum contains_result contains_test(struct commit *candidate,
855+
const struct commit_list *want)
856+
{
857+
/* was it previously marked as containing a want commit? */
858+
if (candidate->object.flags & TMP_MARK)
859+
return 1;
860+
/* or marked as not possibly containing a want commit? */
861+
if (candidate->object.flags & UNINTERESTING)
862+
return 0;
863+
/* or are we it? */
864+
if (in_commit_list(want, candidate)) {
865+
candidate->object.flags |= TMP_MARK;
866+
return 1;
867+
}
868+
869+
if (parse_commit(candidate) < 0)
870+
return 0;
871+
872+
return -1;
873+
}
874+
875+
static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
876+
{
877+
ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
878+
contains_stack->contains_stack[contains_stack->nr].commit = candidate;
879+
contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
880+
}
881+
882+
static enum contains_result contains_tag_algo(struct commit *candidate,
883+
const struct commit_list *want)
884+
{
885+
struct contains_stack contains_stack = { 0, 0, NULL };
886+
int result = contains_test(candidate, want);
887+
888+
if (result != CONTAINS_UNKNOWN)
889+
return result;
890+
891+
push_to_contains_stack(candidate, &contains_stack);
892+
while (contains_stack.nr) {
893+
struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
894+
struct commit *commit = entry->commit;
895+
struct commit_list *parents = entry->parents;
896+
897+
if (!parents) {
898+
commit->object.flags |= UNINTERESTING;
899+
contains_stack.nr--;
900+
}
901+
/*
902+
* If we just popped the stack, parents->item has been marked,
903+
* therefore contains_test will return a meaningful 0 or 1.
904+
*/
905+
else switch (contains_test(parents->item, want)) {
906+
case CONTAINS_YES:
907+
commit->object.flags |= TMP_MARK;
908+
contains_stack.nr--;
909+
break;
910+
case CONTAINS_NO:
911+
entry->parents = parents->next;
912+
break;
913+
case CONTAINS_UNKNOWN:
914+
push_to_contains_stack(parents->item, &contains_stack);
915+
break;
916+
}
917+
}
918+
free(contains_stack.contains_stack);
919+
return contains_test(candidate, want);
920+
}
921+
922+
static int commit_contains(struct ref_filter *filter, struct commit *commit)
923+
{
924+
if (filter->with_commit_tag_algo)
925+
return contains_tag_algo(commit, filter->with_commit);
926+
return is_descendant_of(commit, filter->with_commit);
927+
}
928+
821929
/*
822930
* Return 1 if the refname matches one of the patterns, otherwise 0.
823931
* A pattern can be path prefix (e.g. a refname "refs/heads/master"
@@ -917,10 +1025,14 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
9171025
* obtain the commit using the 'oid' available and discard all
9181026
* non-commits early. The actual filtering is done later.
9191027
*/
920-
if (filter->merge_commit) {
1028+
if (filter->merge_commit || filter->with_commit) {
9211029
commit = lookup_commit_reference_gently(oid->hash, 1);
9221030
if (!commit)
9231031
return 0;
1032+
/* We perform the filtering for the '--contains' option */
1033+
if (filter->with_commit &&
1034+
!commit_contains(filter, commit))
1035+
return 0;
9241036
}
9251037

9261038
/*

ref-filter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,16 @@ struct ref_array {
4444
struct ref_filter {
4545
const char **name_patterns;
4646
struct sha1_array points_at;
47+
struct commit_list *with_commit;
4748

4849
enum {
4950
REF_FILTER_MERGED_NONE = 0,
5051
REF_FILTER_MERGED_INCLUDE,
5152
REF_FILTER_MERGED_OMIT
5253
} merge;
5354
struct commit *merge_commit;
55+
56+
unsigned int with_commit_tag_algo : 1;
5457
};
5558

5659
struct ref_filter_cbdata {

0 commit comments

Comments
 (0)