parse-option: rename parse_opt_with_commit()
[gitweb.git] / ref-filter.c
index cfc0e8a89249fbe2b16aa5f29c26355b206a81b8..0d7ec446dcebbdd4d1e5d471491391bd092d171d 100644 (file)
@@ -9,6 +9,7 @@
 #include "tag.h"
 #include "quote.h"
 #include "ref-filter.h"
+#include "revision.h"
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -842,13 +843,47 @@ static int match_name_as_path(const char **pattern, const char *refname)
        return 0;
 }
 
+/*
+ * Given a ref (sha1, refname), check if the ref belongs to the array
+ * of sha1s. If the given ref is a tag, check if the given tag points
+ * at one of the sha1s in the given sha1 array.
+ * the given sha1_array.
+ * NEEDSWORK:
+ * 1. Only a single level of inderection is obtained, we might want to
+ * change this to account for multiple levels (e.g. annotated tags
+ * pointing to annotated tags pointing to a commit.)
+ * 2. As the refs are cached we might know what refname peels to without
+ * the need to parse the object via parse_object(). peel_ref() might be a
+ * more efficient alternative to obtain the pointee.
+ */
+static const unsigned char *match_points_at(struct sha1_array *points_at,
+                                           const unsigned char *sha1,
+                                           const char *refname)
+{
+       const unsigned char *tagged_sha1 = NULL;
+       struct object *obj;
+
+       if (sha1_array_lookup(points_at, sha1) >= 0)
+               return sha1;
+       obj = parse_object(sha1);
+       if (!obj)
+               die(_("malformed object at '%s'"), refname);
+       if (obj->type == OBJ_TAG)
+               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+       if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
+               return tagged_sha1;
+       return NULL;
+}
+
 /* Allocate space for a new ref_array_item and copy the objectname and flag to it */
 static struct ref_array_item *new_ref_array_item(const char *refname,
                                                 const unsigned char *objectname,
                                                 int flag)
 {
-       struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item));
-       ref->refname = xstrdup(refname);
+       size_t len = strlen(refname);
+       struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);
+       memcpy(ref->refname, refname, len);
+       ref->refname[len] = '\0';
        hashcpy(ref->objectname, objectname);
        ref->flag = flag;
 
@@ -859,11 +894,12 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
  * A call-back given to for_each_ref().  Filter refs and keep them for
  * later object processing.
  */
-int ref_filter_handler(const char *refname, const struct object_id *oid, int flag, void *cb_data)
+static int ref_filter_handler(const char *refname, const struct object_id *oid, int flag, void *cb_data)
 {
        struct ref_filter_cbdata *ref_cbdata = cb_data;
-       struct ref_filter *filter = &ref_cbdata->filter;
+       struct ref_filter *filter = ref_cbdata->filter;
        struct ref_array_item *ref;
+       struct commit *commit = NULL;
 
        if (flag & REF_BAD_NAME) {
                warning("ignoring ref with broken name %s", refname);
@@ -873,15 +909,30 @@ int ref_filter_handler(const char *refname, const struct object_id *oid, int fla
        if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
                return 0;
 
+       if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
+               return 0;
+
+       /*
+        * A merge filter is applied on refs pointing to commits. Hence
+        * obtain the commit using the 'oid' available and discard all
+        * non-commits early. The actual filtering is done later.
+        */
+       if (filter->merge_commit) {
+               commit = lookup_commit_reference_gently(oid->hash, 1);
+               if (!commit)
+                       return 0;
+       }
+
        /*
         * We do not open the object yet; sort may only need refname
         * to do its job and the resulting list may yet to be pruned
         * by maxcount logic.
         */
        ref = new_ref_array_item(refname, oid->hash, flag);
+       ref->commit = commit;
 
-       REALLOC_ARRAY(ref_cbdata->array.items, ref_cbdata->array.nr + 1);
-       ref_cbdata->array.items[ref_cbdata->array.nr++] = ref;
+       REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
+       ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
        return 0;
 }
 
@@ -889,7 +940,6 @@ int ref_filter_handler(const char *refname, const struct object_id *oid, int fla
 static void free_array_item(struct ref_array_item *item)
 {
        free((char *)item->symref);
-       free(item->refname);
        free(item);
 }
 
@@ -905,6 +955,79 @@ void ref_array_clear(struct ref_array *array)
        array->nr = array->alloc = 0;
 }
 
+static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
+{
+       struct rev_info revs;
+       int i, old_nr;
+       struct ref_filter *filter = ref_cbdata->filter;
+       struct ref_array *array = ref_cbdata->array;
+       struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
+
+       init_revisions(&revs, NULL);
+
+       for (i = 0; i < array->nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               add_pending_object(&revs, &item->commit->object, item->refname);
+               to_clear[i] = item->commit;
+       }
+
+       filter->merge_commit->object.flags |= UNINTERESTING;
+       add_pending_object(&revs, &filter->merge_commit->object, "");
+
+       revs.limited = 1;
+       if (prepare_revision_walk(&revs))
+               die(_("revision walk setup failed"));
+
+       old_nr = array->nr;
+       array->nr = 0;
+
+       for (i = 0; i < old_nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               struct commit *commit = item->commit;
+
+               int is_merged = !!(commit->object.flags & UNINTERESTING);
+
+               if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
+                       array->items[array->nr++] = array->items[i];
+               else
+                       free_array_item(item);
+       }
+
+       for (i = 0; i < old_nr; i++)
+               clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
+       clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
+       free(to_clear);
+}
+
+/*
+ * API for filtering a set of refs. Based on the type of refs the user
+ * has requested, we iterate through those refs and apply filters
+ * as per the given ref_filter structure and finally store the
+ * filtered refs in the ref_array structure.
+ */
+int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
+{
+       struct ref_filter_cbdata ref_cbdata;
+       int ret = 0;
+
+       ref_cbdata.array = array;
+       ref_cbdata.filter = filter;
+
+       /*  Simple per-ref filtering */
+       if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
+               ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
+       else if (type & FILTER_REFS_ALL)
+               ret = for_each_ref(ref_filter_handler, &ref_cbdata);
+       else if (type)
+               die("filter_refs: invalid type");
+
+       /*  Filters that need revision walking */
+       if (filter->merge_commit)
+               do_merge_filter(&ref_cbdata);
+
+       return ret;
+}
+
 static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
 {
        struct atom_value *va, *vb;
@@ -1076,3 +1199,22 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
        s->atom = parse_ref_filter_atom(arg, arg+len);
        return 0;
 }
+
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
+{
+       struct ref_filter *rf = opt->value;
+       unsigned char sha1[20];
+
+       rf->merge = starts_with(opt->long_name, "no")
+               ? REF_FILTER_MERGED_OMIT
+               : REF_FILTER_MERGED_INCLUDE;
+
+       if (get_sha1(arg, sha1))
+               die(_("malformed object name %s"), arg);
+
+       rf->merge_commit = lookup_commit_reference_gently(sha1, 0);
+       if (!rf->merge_commit)
+               return opterror(opt, "must point to a commit", 0);
+
+       return 0;
+}