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 <christian.couder@gmail.com> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr> Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Karthik Nayak committed
Jul 7, 2015 at 21:36 UTC
ee2bd06b0f735a00ce0216ca1d3391b13722d987
3 files changed
+121
-1
builtin/tag.c
+5
@@ -86,6 +86,11 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
86
return 0;
87
}
88
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
+ */
94
enum contains_result {
95
CONTAINS_UNKNOWN = -1,
96
CONTAINS_NO = 0,
ref-filter.c
+113
-1
@@ -818,6 +818,114 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
818
*v = &ref->value[atom];
819
}
820
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
+
929
/*
930
* Return 1 if the refname matches one of the patterns, otherwise 0.
931
* 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,
1025
* obtain the commit using the 'oid' available and discard all
1026
* non-commits early. The actual filtering is done later.
1027
*/
920
- if (filter->merge_commit) {
1028
+ if (filter->merge_commit || filter->with_commit) {
1029
commit = lookup_commit_reference_gently(oid->hash, 1);
1030
if (!commit)
1031
return 0;
1032
+ /* We perform the filtering for the '--contains' option */
1033
+ if (filter->with_commit &&
1034
+ !commit_contains(filter, commit))
1035
+ return 0;
1036
}
1037
1038
/*
ref-filter.h
+3
@@ -44,6 +44,7 @@ struct ref_array {
44
struct ref_filter {
45
const char **name_patterns;
46
struct sha1_array points_at;
47
+ struct commit_list *with_commit;
48
49
enum {
50
REF_FILTER_MERGED_NONE = 0,
@@ -51,6 +52,8 @@ struct ref_filter {
52
REF_FILTER_MERGED_OMIT
53
} merge;
54
struct commit *merge_commit;
55
+
56
+ unsigned int with_commit_tag_algo : 1;
57
};
58
59
struct ref_filter_cbdata {