Merge 'jk/git-path' into kn/for-each-tag
authorJunio C Hamano <gitster@pobox.com>
Mon, 24 Aug 2015 22:30:43 +0000 (15:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 24 Aug 2015 22:30:43 +0000 (15:30 -0700)
* jk/git-path:
memoize common git-path "constant" files
get_repo_path: refactor path-allocation
find_hook: keep our own static buffer
refs.c: remove_empty_directories can take a strbuf
refs.c: avoid git_path assignment in lock_ref_sha1_basic
refs.c: avoid repeated git_path calls in rename_tmp_log
refs.c: simplify strbufs in reflog setup and writing
path.c: drop git_path_submodule
refs.c: remove extra git_path calls from read_loose_refs
remote.c: drop extraneous local variable from migrate_file
prefer mkpathdup to mkpath in assignments
prefer git_pathdup to git_path in some possibly-dangerous cases
add_to_alternates_file: don't add duplicate entries
t5700: modernize style
cache.h: complete set of git_path_submodule helpers
cache.h: clarify documentation for git_path, et al

Documentation/git-for-each-ref.txt
builtin/branch.c
builtin/for-each-ref.c
builtin/tag.c
parse-options-cb.c
parse-options.h
ref-filter.c
ref-filter.h
t/t6302-for-each-ref-filter.sh [new file with mode: 0755]
index 7f8d9a5b5f358baa50bdfe205f555ea904f9f629..e49d5782fc6f82289d0b71c720110b11e38cb8b0 100644 (file)
@@ -10,6 +10,8 @@ SYNOPSIS
 [verse]
 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
                   [(--sort=<key>)...] [--format=<format>] [<pattern>...]
+                  [--points-at <object>] [(--merged | --no-merged) [<object>]]
+                  [--contains [<object>]]
 
 DESCRIPTION
 -----------
@@ -62,6 +64,20 @@ OPTIONS
        the specified host language.  This is meant to produce
        a scriptlet that can directly be `eval`ed.
 
+--points-at <object>::
+       Only list refs which points at the given object.
+
+--merged [<object>]::
+       Only list refs whose tips are reachable from the
+       specified commit (HEAD if not specified).
+
+--no-merged [<object>]::
+       Only list refs whose tips are not reachable from the
+       specified commit (HEAD if not specified).
+
+--contains [<object>]::
+       Only list tags which contain the specified commit (HEAD if not
+       specified).
 
 FIELD NAMES
 -----------
index 58aa84f1e85212772d15175679e95263eae75123..4fc8beb23c369380632f7d7b908c8c788f2969cb 100644 (file)
@@ -636,6 +636,10 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
        cb.pattern = pattern;
        cb.ret = 0;
        for_each_rawref(append_ref, &cb);
+       /*
+        * The following implementation is currently duplicated in ref-filter. It
+        * will eventually be removed when we port branch.c to use ref-filter APIs.
+        */
        if (merge_filter != NO_FILTER) {
                struct commit *filter;
                filter = lookup_commit_reference_gently(merge_filter_ref, 0);
@@ -746,6 +750,10 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        strbuf_release(&newsection);
 }
 
+/*
+ * This function is duplicated in ref-filter. It will eventually be removed
+ * when we port branch.c to use ref-filter APIs.
+ */
 static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
 {
        merge_filter = ((opt->long_name[0] == 'n')
@@ -821,18 +829,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT__COLOR(&branch_use_color, N_("use colored output")),
                OPT_SET_INT('r', "remotes",     &kinds, N_("act on remote-tracking branches"),
                        REF_REMOTE_BRANCH),
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t) "HEAD",
-               },
+               OPT_CONTAINS(&with_commit, N_("print only branches that contain the commit")),
+               OPT_WITH(&with_commit, N_("print only branches that contain the commit")),
                OPT__ABBREV(&abbrev),
 
                OPT_GROUP(N_("Specific git-branch actions:")),
index 7919206187c9968cc974c10bb9b5f75c7574e630..40f343b6a36fc76271e53d842b92f8ab4c161d38 100644 (file)
@@ -7,6 +7,9 @@
 
 static char const * const for_each_ref_usage[] = {
        N_("git for-each-ref [<options>] [<pattern>]"),
+       N_("git for-each-ref [--points-at <object>]"),
+       N_("git for-each-ref [(--merged | --no-merged) [<object>]]"),
+       N_("git for-each-ref [--contains [<object>]]"),
        NULL
 };
 
@@ -34,9 +37,18 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
                            N_("field name to sort on"), &parse_opt_ref_sorting),
+               OPT_CALLBACK(0, "points-at", &filter.points_at,
+                            N_("object"), N_("print only refs which points at the given object"),
+                            parse_opt_object_name),
+               OPT_MERGED(&filter, N_("print only refs that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
+               OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
                OPT_END(),
        };
 
+       memset(&array, 0, sizeof(array));
+       memset(&filter, 0, sizeof(filter));
+
        parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
        if (maxcount < 0) {
                error("invalid --count argument: `%d'", maxcount);
@@ -55,8 +67,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
        /* for warn_ambiguous_refs */
        git_config(git_default_config, NULL);
 
-       memset(&array, 0, sizeof(array));
-       memset(&filter, 0, sizeof(filter));
        filter.name_patterns = argv;
        filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
        ref_array_sort(sorting, &array);
index cccca991046a6f4b6cf967a99a7f7a4396d4d588..471d6b1ab8847d7344483509633d3067c088bf6d 100644 (file)
@@ -56,6 +56,10 @@ static int match_pattern(const char **patterns, const char *ref)
        return 0;
 }
 
+/*
+ * This is currently duplicated in ref-filter.c, and will eventually be
+ * removed as we port tag.c to use the ref-filter APIs.
+ */
 static const unsigned char *match_points_at(const char *refname,
                                            const unsigned char *sha1)
 {
@@ -82,6 +86,11 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
        return 0;
 }
 
+/*
+ * The entire code segment for supporting the --contains option has been
+ * copied over to ref-filter.{c,h}. This will be deleted evetually when
+ * we port tag.c to use ref-filter APIs.
+ */
 enum contains_result {
        CONTAINS_UNKNOWN = -1,
        CONTAINS_NO = 0,
@@ -546,23 +555,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
        return check_refname_format(sb->buf, 0);
 }
 
-static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
-                       const char *arg, int unset)
-{
-       unsigned char sha1[20];
-
-       if (unset) {
-               sha1_array_clear(&points_at);
-               return 0;
-       }
-       if (!arg)
-               return error(_("switch 'points-at' requires an object"));
-       if (get_sha1(arg, sha1))
-               return error(_("malformed object name '%s'"), arg);
-       sha1_array_append(&points_at, sha1);
-       return 0;
-}
-
 static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
 {
        int *sort = opt->value;
@@ -610,25 +602,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
+               OPT_CONTAINS(&with_commit, N_("print only tags that contain the commit")),
+               OPT_WITH(&with_commit, N_("print only tags that contain the commit")),
                {
                        OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
                        PARSE_OPT_NONEG, parse_opt_sort
                },
                {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
-                       N_("print only tags of the object"), 0, parse_opt_points_at
+                       OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"),
+                       N_("print only tags of the object"), 0, parse_opt_object_name
                },
                OPT_END()
        };
index 5ab6ed6b0875ff703e3d55abbd0e9d8b0aa1a3c7..239898d946f06d102030569fd45780f523fdcd5a 100644 (file)
@@ -5,6 +5,7 @@
 #include "color.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "sha1-array.h"
 
 /*----- some often used options -----*/
 
@@ -77,7 +78,7 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
        return 0;
 }
 
-int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+int parse_opt_commits(const struct option *opt, const char *arg, int unset)
 {
        unsigned char sha1[20];
        struct commit *commit;
@@ -93,6 +94,22 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+int parse_opt_object_name(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+
+       if (unset) {
+               sha1_array_clear(opt->value);
+               return 0;
+       }
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               return error(_("malformed object name '%s'"), arg);
+       sha1_array_append(opt->value, sha1);
+       return 0;
+}
+
 int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 {
        int *target = opt->value;
index 6ca8388a532e44256fac23fc4f999bf40832e06f..a2bc34e9d7acc8c4e880686bc5f4761b23333c21 100644 (file)
@@ -223,7 +223,8 @@ extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
 extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
 extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
-extern int parse_opt_with_commit(const struct option *, const char *, int);
+extern int parse_opt_object_name(const struct option *, const char *, int);
+extern int parse_opt_commits(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
 extern int parse_opt_string_list(const struct option *, const char *, int);
 extern int parse_opt_noop_cb(const struct option *, const char *, int);
@@ -251,5 +252,12 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int);
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru }
 #define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) \
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru_argv }
+#define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \
+       { OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \
+         PARSE_OPT_LASTARG_DEFAULT | flag, \
+         parse_opt_commits, (intptr_t) "HEAD" \
+       }
+#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, 0)
+#define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN)
 
 #endif
index f38dee4f605df344315e0202487aeebc623f6582..46963a5a421255cf2d37bce9338e3c6de3e20d6a 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;
 
@@ -817,6 +818,114 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
        *v = &ref->value[atom];
 }
 
+enum contains_result {
+       CONTAINS_UNKNOWN = -1,
+       CONTAINS_NO = 0,
+       CONTAINS_YES = 1
+};
+
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct contains_stack {
+       int nr, alloc;
+       struct contains_stack_entry {
+               struct commit *commit;
+               struct commit_list *parents;
+       } *contains_stack;
+};
+
+static int in_commit_list(const struct commit_list *want, struct commit *c)
+{
+       for (; want; want = want->next)
+               if (!hashcmp(want->item->object.sha1, c->object.sha1))
+                       return 1;
+       return 0;
+}
+
+/*
+ * Test whether the candidate or one of its parents is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
+                           const struct commit_list *want)
+{
+       /* was it previously marked as containing a want commit? */
+       if (candidate->object.flags & TMP_MARK)
+               return 1;
+       /* or marked as not possibly containing a want commit? */
+       if (candidate->object.flags & UNINTERESTING)
+               return 0;
+       /* or are we it? */
+       if (in_commit_list(want, candidate)) {
+               candidate->object.flags |= TMP_MARK;
+               return 1;
+       }
+
+       if (parse_commit(candidate) < 0)
+               return 0;
+
+       return -1;
+}
+
+static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
+{
+       ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
+       contains_stack->contains_stack[contains_stack->nr].commit = candidate;
+       contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
+}
+
+static enum contains_result contains_tag_algo(struct commit *candidate,
+               const struct commit_list *want)
+{
+       struct contains_stack contains_stack = { 0, 0, NULL };
+       int result = contains_test(candidate, want);
+
+       if (result != CONTAINS_UNKNOWN)
+               return result;
+
+       push_to_contains_stack(candidate, &contains_stack);
+       while (contains_stack.nr) {
+               struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
+               struct commit *commit = entry->commit;
+               struct commit_list *parents = entry->parents;
+
+               if (!parents) {
+                       commit->object.flags |= UNINTERESTING;
+                       contains_stack.nr--;
+               }
+               /*
+                * If we just popped the stack, parents->item has been marked,
+                * therefore contains_test will return a meaningful 0 or 1.
+                */
+               else switch (contains_test(parents->item, want)) {
+               case CONTAINS_YES:
+                       commit->object.flags |= TMP_MARK;
+                       contains_stack.nr--;
+                       break;
+               case CONTAINS_NO:
+                       entry->parents = parents->next;
+                       break;
+               case CONTAINS_UNKNOWN:
+                       push_to_contains_stack(parents->item, &contains_stack);
+                       break;
+               }
+       }
+       free(contains_stack.contains_stack);
+       return contains_test(candidate, want);
+}
+
+static int commit_contains(struct ref_filter *filter, struct commit *commit)
+{
+       if (filter->with_commit_tag_algo)
+               return contains_tag_algo(commit, filter->with_commit);
+       return is_descendant_of(commit, filter->with_commit);
+}
+
 /*
  * Return 1 if the refname matches one of the patterns, otherwise 0.
  * A pattern can be path prefix (e.g. a refname "refs/heads/master"
@@ -842,6 +951,38 @@ 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,
@@ -866,6 +1007,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
        struct ref_filter_cbdata *ref_cbdata = cb_data;
        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);
@@ -880,12 +1022,31 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
        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 || filter->with_commit) {
+               commit = lookup_commit_reference_gently(oid->hash, 1);
+               if (!commit)
+                       return 0;
+               /* We perform the filtering for the '--contains' option */
+               if (filter->with_commit &&
+                   !commit_contains(filter, 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;
@@ -911,6 +1072,50 @@ 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
@@ -920,17 +1125,24 @@ void ref_array_clear(struct ref_array *array)
 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))
-               return for_each_rawref(ref_filter_handler, &ref_cbdata);
+               ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
        else if (type & FILTER_REFS_ALL)
-               return for_each_ref(ref_filter_handler, &ref_cbdata);
-       else
+               ret = for_each_ref(ref_filter_handler, &ref_cbdata);
+       else if (type)
                die("filter_refs: invalid type");
-       return 0;
+
+       /*  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)
@@ -1104,3 +1316,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;
+}
index 699798400b329a12bc5dfa09590853a18616f876..6bf27d8bc4a0b32c0c457b34b3a36e457f89b029 100644 (file)
@@ -31,6 +31,7 @@ struct ref_array_item {
        unsigned char objectname[20];
        int flag;
        const char *symref;
+       struct commit *commit;
        struct atom_value *value;
        char refname[FLEX_ARRAY];
 };
@@ -42,6 +43,17 @@ struct ref_array {
 
 struct ref_filter {
        const char **name_patterns;
+       struct sha1_array points_at;
+       struct commit_list *with_commit;
+
+       enum {
+               REF_FILTER_MERGED_NONE = 0,
+               REF_FILTER_MERGED_INCLUDE,
+               REF_FILTER_MERGED_OMIT
+       } merge;
+       struct commit *merge_commit;
+
+       unsigned int with_commit_tag_algo : 1;
 };
 
 struct ref_filter_cbdata {
@@ -49,6 +61,15 @@ struct ref_filter_cbdata {
        struct ref_filter *filter;
 };
 
+/*  Macros for checking --merged and --no-merged options */
+#define _OPT_MERGED_NO_MERGED(option, filter, h) \
+       { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \
+         PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \
+         parse_opt_merge_filter, (intptr_t) "HEAD" \
+       }
+#define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h)
+#define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h)
+
 /*
  * 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
@@ -70,5 +91,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
 /*  Default sort option based on refname */
 struct ref_sorting *ref_default_sorting(void);
+/*  Function to parse --merged and --no-merged options */
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
 
 #endif /*  REF_FILTER_H  */
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
new file mode 100755 (executable)
index 0000000..505a360
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+test_description='test for-each-refs usage of ref-filter APIs'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
+
+if ! test_have_prereq GPG
+then
+       skip_all="skipping for-each-ref tests, GPG not available"
+       test_done
+fi
+
+test_expect_success 'setup some history and refs' '
+       test_commit one &&
+       test_commit two &&
+       test_commit three &&
+       git checkout -b side &&
+       test_commit four &&
+       git tag -s -m "A signed tag message" signed-tag &&
+       git tag -s -m "Annonated doubly" double-tag signed-tag &&
+       git checkout master &&
+       git update-ref refs/odd/spot master
+'
+
+test_expect_success 'filtering with --points-at' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/three
+       EOF
+       git for-each-ref --format="%(refname)" --points-at=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check signed tags with --points-at' '
+       sed -e "s/Z$//" >expect <<-\EOF &&
+       refs/heads/side Z
+       refs/tags/four Z
+       refs/tags/signed-tag four
+       EOF
+       git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/one
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --no-merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/side
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       EOF
+       git for-each-ref --format="%(refname)" --no-merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --contains' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/heads/side
+       refs/odd/spot
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --contains=two >actual &&
+       test_cmp expect actual
+'
+
+test_done