ref-filter: implement '--merged' and '--no-merged' options

In 'branch -l' we have '--merged' option which only lists refs (branches) merged into the named commit and '--no-merged' option which only lists refs (branches) not merged into the named commit. Implement these two options in ref-filter.{c,h} so that other commands can benefit from this. Based-on-patch-by: Jeff King <peff@peff.net> 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 35257aa01203bae74f9fb856fb02c10c4b3836e6
3 files changed +81 -4
builtin/branch.c
+4
@@ -635,6 +635,10 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
635 cb.pattern = pattern;
636 cb.ret = 0;
637 for_each_rawref(append_ref, &cb);
638 + /*
639 + * The following implementation is currently duplicated in ref-filter. It
640 + * will eventually be removed when we port branch.c to use ref-filter APIs.
641 + */
642 if (merge_filter != NO_FILTER) {
643 struct commit *filter;
644 filter = lookup_commit_reference_gently(merge_filter_ref, 0);
ref-filter.c
+69 -4
@@ -9,6 +9,7 @@
9 #include "tag.h"
10 #include "quote.h"
11 #include "ref-filter.h"
12 +#include "revision.h"
13
14 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
15
@@ -898,6 +899,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
899 struct ref_filter_cbdata *ref_cbdata = cb_data;
900 struct ref_filter *filter = ref_cbdata->filter;
901 struct ref_array_item *ref;
902 + struct commit *commit = NULL;
903
904 if (flag & REF_BAD_NAME) {
905 warning("ignoring ref with broken name %s", refname);
@@ -910,12 +912,24 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
912 if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
913 return 0;
914
915 + /*
916 + * A merge filter is applied on refs pointing to commits. Hence
917 + * obtain the commit using the 'oid' available and discard all
918 + * non-commits early. The actual filtering is done later.
919 + */
920 + if (filter->merge_commit) {
921 + commit = lookup_commit_reference_gently(oid->hash, 1);
922 + if (!commit)
923 + return 0;
924 + }
925 +
926 /*
927 * We do not open the object yet; sort may only need refname
928 * to do its job and the resulting list may yet to be pruned
929 * by maxcount logic.
930 */
931 ref = new_ref_array_item(refname, oid->hash, flag);
932 + ref->commit = commit;
933
934 REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
935 ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
@@ -941,6 +955,50 @@ void ref_array_clear(struct ref_array *array)
955 array->nr = array->alloc = 0;
956 }
957
958 +static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
959 +{
960 + struct rev_info revs;
961 + int i, old_nr;
962 + struct ref_filter *filter = ref_cbdata->filter;
963 + struct ref_array *array = ref_cbdata->array;
964 + struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
965 +
966 + init_revisions(&revs, NULL);
967 +
968 + for (i = 0; i < array->nr; i++) {
969 + struct ref_array_item *item = array->items[i];
970 + add_pending_object(&revs, &item->commit->object, item->refname);
971 + to_clear[i] = item->commit;
972 + }
973 +
974 + filter->merge_commit->object.flags |= UNINTERESTING;
975 + add_pending_object(&revs, &filter->merge_commit->object, "");
976 +
977 + revs.limited = 1;
978 + if (prepare_revision_walk(&revs))
979 + die(_("revision walk setup failed"));
980 +
981 + old_nr = array->nr;
982 + array->nr = 0;
983 +
984 + for (i = 0; i < old_nr; i++) {
985 + struct ref_array_item *item = array->items[i];
986 + struct commit *commit = item->commit;
987 +
988 + int is_merged = !!(commit->object.flags & UNINTERESTING);
989 +
990 + if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
991 + array->items[array->nr++] = array->items[i];
992 + else
993 + free_array_item(item);
994 + }
995 +
996 + for (i = 0; i < old_nr; i++)
997 + clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
998 + clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
999 + free(to_clear);
1000 +}
1001 +
1002 /*
1003 * API for filtering a set of refs. Based on the type of refs the user
1004 * has requested, we iterate through those refs and apply filters
@@ -950,17 +1008,24 @@ void ref_array_clear(struct ref_array *array)
1008 int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
1009 {
1010 struct ref_filter_cbdata ref_cbdata;
1011 + int ret = 0;
1012
1013 ref_cbdata.array = array;
1014 ref_cbdata.filter = filter;
1015
1016 + /* Simple per-ref filtering */
1017 if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
958 - return for_each_rawref(ref_filter_handler, &ref_cbdata);
1018 + ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
1019 else if (type & FILTER_REFS_ALL)
960 - return for_each_ref(ref_filter_handler, &ref_cbdata);
961 - else
1020 + ret = for_each_ref(ref_filter_handler, &ref_cbdata);
1021 + else if (type)
1022 die("filter_refs: invalid type");
963 - return 0;
1023 +
1024 + /* Filters that need revision walking */
1025 + if (filter->merge_commit)
1026 + do_merge_filter(&ref_cbdata);
1027 +
1028 + return ret;
1029 }
1030
1031 static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
ref-filter.h
+8
@@ -31,6 +31,7 @@ struct ref_array_item {
31 unsigned char objectname[20];
32 int flag;
33 const char *symref;
34 + struct commit *commit;
35 struct atom_value *value;
36 char refname[FLEX_ARRAY];
37 };
@@ -43,6 +44,13 @@ struct ref_array {
44 struct ref_filter {
45 const char **name_patterns;
46 struct sha1_array points_at;
47 +
48 + enum {
49 + REF_FILTER_MERGED_NONE = 0,
50 + REF_FILTER_MERGED_INCLUDE,
51 + REF_FILTER_MERGED_OMIT
52 + } merge;
53 + struct commit *merge_commit;
54 };
55
56 struct ref_filter_cbdata {