@@ -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 /*
0 commit comments