@@ -818,6 +818,114 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
818
818
* v = & ref -> value [atom ];
819
819
}
820
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
+
821
929
/*
822
930
* Return 1 if the refname matches one of the patterns, otherwise 0.
823
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,
917
1025
* obtain the commit using the 'oid' available and discard all
918
1026
* non-commits early. The actual filtering is done later.
919
1027
*/
920
- if (filter -> merge_commit ) {
1028
+ if (filter -> merge_commit || filter -> with_commit ) {
921
1029
commit = lookup_commit_reference_gently (oid -> hash , 1 );
922
1030
if (!commit )
923
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 ;
924
1036
}
925
1037
926
1038
/*
0 commit comments