Merge branch 'kn/for-each-tag-branch'
authorJunio C Hamano <gitster@pobox.com>
Mon, 5 Oct 2015 19:30:02 +0000 (12:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 5 Oct 2015 19:30:03 +0000 (12:30 -0700)
Some features from "git tag -l" and "git branch -l" have been made
available to "git for-each-ref" so that eventually the unified
implementation can be shared across all three, in a follow-up
series or two.

* kn/for-each-tag-branch:
for-each-ref: add '--contains' option
ref-filter: implement '--contains' option
parse-options.h: add macros for '--contains' option
parse-option: rename parse_opt_with_commit()
for-each-ref: add '--merged' and '--no-merged' options
ref-filter: implement '--merged' and '--no-merged' options
ref-filter: add parse_opt_merge_filter()
for-each-ref: add '--points-at' option
ref-filter: implement '--points-at' option
tag: libify parse_opt_points_at()
t6302: for-each-ref tests for ref-filter APIs

1  2 
builtin/branch.c
builtin/tag.c
parse-options-cb.c
parse-options.h
ref-filter.c
diff --combined builtin/branch.c
index ff05869949b7c5c4cd0034e7a0ba54e2adeae6b4,c443cd82516932e754d8bcea5f3d1e71528338d2..3ba4d1bd3b2507303d95c449a6ab926db8f350d9
@@@ -160,7 -160,7 +160,7 @@@ static int branch_merged(int kind, cons
  }
  
  static int check_branch_commit(const char *branchname, const char *refname,
 -                             unsigned char *sha1, struct commit *head_rev,
 +                             const unsigned char *sha1, struct commit *head_rev,
                               int kinds, int force)
  {
        struct commit *rev = lookup_commit_reference(sha1);
@@@ -253,8 -253,7 +253,8 @@@ static int delete_branches(int argc, co
                        continue;
                }
  
 -              if (delete_ref(name, sha1, REF_NODEREF)) {
 +              if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
 +                             REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
                              : _("Error deleting branch '%s'"),
@@@ -636,6 -635,10 +636,10 @@@ static int print_ref_list(int kinds, in
        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 -749,10 +750,10 @@@ static void rename_branch(const char *o
        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')
@@@ -776,7 -783,7 +784,7 @@@ static int edit_branch_description(cons
                    "  %s\n"
                    "Lines starting with '%c' will be stripped.\n",
                    branch_name, comment_line_char);
 -      if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
 +      if (write_file_gently(git_path(edit_description), "%s", buf.buf)) {
                strbuf_release(&buf);
                return error(_("could not write branch description template: %s"),
                             strerror(errno));
@@@ -821,18 -828,8 +829,8 @@@ int cmd_branch(int argc, const char **a
                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:")),
diff --combined builtin/tag.c
index cba0e2266623d57fe96fcec1910c562bf424d143,071d001655c754b502ddcdf6f1625a053e32fa46..aaae358c2e212968446659200bd468dd7989c54a
@@@ -56,6 -56,10 +56,10 @@@ static int match_pattern(const char **p
        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 +86,11 @@@ static int in_commit_list(const struct 
        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 +555,6 @@@ static int strbuf_check_tag_ref(struct 
        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;
@@@ -579,7 -571,6 +571,7 @@@ int cmd_tag(int argc, const char **argv
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
        int annotate = 0, force = 0, lines = -1;
 +      int create_reflog = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
                OPT_STRING('u', "local-user", &keyid, N_("key-id"),
                                        N_("use another key to sign the tag")),
                OPT__FORCE(&force, N_("replace the tag if exists")),
 +              OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")),
  
                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()
        };
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_update(transaction, ref.buf, object, prev,
 -                                 0, NULL, &err) ||
 +                                 create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
 +                                 NULL, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
        ref_transaction_free(transaction);
diff --combined parse-options-cb.c
index 5ab6ed6b0875ff703e3d55abbd0e9d8b0aa1a3c7,632f10f2026661522a022b13a2ebb0c920983f51..239898d946f06d102030569fd45780f523fdcd5a
@@@ -4,7 -4,7 +4,8 @@@
  #include "commit.h"
  #include "color.h"
  #include "string-list.h"
 +#include "argv-array.h"
+ #include "sha1-array.h"
  
  /*----- some often used options -----*/
  
@@@ -77,7 -77,7 +78,7 @@@ int parse_opt_verbosity_cb(const struc
        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;
        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;
@@@ -135,71 -151,3 +152,71 @@@ int parse_opt_noop_cb(const struct opti
  {
        return 0;
  }
 +
 +/**
 + * Recreates the command-line option in the strbuf.
 + */
 +static int recreate_opt(struct strbuf *sb, const struct option *opt,
 +              const char *arg, int unset)
 +{
 +      strbuf_reset(sb);
 +
 +      if (opt->long_name) {
 +              strbuf_addstr(sb, unset ? "--no-" : "--");
 +              strbuf_addstr(sb, opt->long_name);
 +              if (arg) {
 +                      strbuf_addch(sb, '=');
 +                      strbuf_addstr(sb, arg);
 +              }
 +      } else if (opt->short_name && !unset) {
 +              strbuf_addch(sb, '-');
 +              strbuf_addch(sb, opt->short_name);
 +              if (arg)
 +                      strbuf_addstr(sb, arg);
 +      } else
 +              return -1;
 +
 +      return 0;
 +}
 +
 +/**
 + * For an option opt, recreates the command-line option in opt->value which
 + * must be an char* initialized to NULL. This is useful when we need to pass
 + * the command-line option to another command. Since any previous value will be
 + * overwritten, this callback should only be used for options where the last
 + * one wins.
 + */
 +int parse_opt_passthru(const struct option *opt, const char *arg, int unset)
 +{
 +      static struct strbuf sb = STRBUF_INIT;
 +      char **opt_value = opt->value;
 +
 +      if (recreate_opt(&sb, opt, arg, unset) < 0)
 +              return -1;
 +
 +      if (*opt_value)
 +              free(*opt_value);
 +
 +      *opt_value = strbuf_detach(&sb, NULL);
 +
 +      return 0;
 +}
 +
 +/**
 + * For an option opt, recreate the command-line option, appending it to
 + * opt->value which must be a argv_array. This is useful when we need to pass
 + * the command-line option, which can be specified multiple times, to another
 + * command.
 + */
 +int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset)
 +{
 +      static struct strbuf sb = STRBUF_INIT;
 +      struct argv_array *opt_value = opt->value;
 +
 +      if (recreate_opt(&sb, opt, arg, unset) < 0)
 +              return -1;
 +
 +      argv_array_push(opt_value, sb.buf);
 +
 +      return 0;
 +}
diff --combined parse-options.h
index 3f1cc3aee0faaed923f736393db45877de14edc3,6db7cb31cc90fa6ba6717fd7b72881558507a408..e8b55ea87aaea6a0e16239fd9b2025c4927d2145
@@@ -16,7 -16,6 +16,7 @@@ enum parse_opt_type 
        /* options with arguments (usually) */
        OPTION_STRING,
        OPTION_INTEGER,
 +      OPTION_MAGNITUDE,
        OPTION_CALLBACK,
        OPTION_LOWLEVEL_CALLBACK,
        OPTION_FILENAME
@@@ -127,11 -126,9 +127,11 @@@ struct option 
  #define OPT_BOOL(s, l, v, h)        OPT_SET_INT(s, l, v, h, 1)
  #define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1}
 -#define OPT_CMDMODE(s, l, v, h, i) { OPTION_CMDMODE, (s), (l), (v), NULL, \
 +#define OPT_CMDMODE(s, l, v, h, i)  { OPTION_CMDMODE, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) }
  #define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), N_("n"), (h) }
 +#define OPT_MAGNITUDE(s, l, v, h)   { OPTION_MAGNITUDE, (s), (l), (v), \
 +                                    N_("n"), (h), PARSE_OPT_NONEG }
  #define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }
  #define OPT_STRING_LIST(s, l, v, a, h) \
                                    { OPTION_CALLBACK, (s), (l), (v), (a), \
@@@ -223,12 -220,11 +223,13 @@@ extern int parse_opt_approxidate_cb(con
  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);
 +extern int parse_opt_passthru(const struct option *, const char *, int);
 +extern int parse_opt_passthru_argv(const struct option *, const char *, int);
  
  #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
  #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
        OPT_COLOR_FLAG(0, "color", (var), (h))
  #define OPT_COLUMN(s, l, v, h) \
        { OPTION_CALLBACK, (s), (l), (v), N_("style"), (h), PARSE_OPT_OPTARG, parseopt_column_callback }
 +#define OPT_PASSTHRU(s, l, v, a, h, f) \
 +      { 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
diff --combined ref-filter.c
index f38dee4f605df344315e0202487aeebc623f6582,409b94fcfba60b61a9e52a2d97f7df349b809427..46963a5a421255cf2d37bce9338e3c6de3e20d6a
@@@ -9,6 -9,7 +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;
  
@@@ -362,7 -363,7 +363,7 @@@ static void grab_date(const char *buf, 
        char *zone;
        unsigned long timestamp;
        long tz;
 -      enum date_mode date_mode = DATE_NORMAL;
 +      struct date_mode date_mode = { DATE_NORMAL };
        const char *formatp;
  
        /*
        formatp = strchr(atomname, ':');
        if (formatp != NULL) {
                formatp++;
 -              date_mode = parse_date_format(formatp);
 +              parse_date_format(formatp, &date_mode);
        }
  
        if (!eoemail)
        tz = strtol(zone, NULL, 10);
        if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
                goto bad;
 -      v->s = xstrdup(show_date(timestamp, tz, date_mode));
 +      v->s = xstrdup(show_date(timestamp, tz, &date_mode));
        v->ul = timestamp;
        return;
   bad:
@@@ -817,6 -818,114 +818,114 @@@ static void get_ref_atom_value(struct r
        *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 +951,38 @@@ static int match_name_as_path(const cha
        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,26 -1007,41 +1007,46 @@@ static int ref_filter_handler(const cha
        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);
                return 0;
        }
  
 +      if (flag & REF_ISBROKEN) {
 +              warning("ignoring broken ref %s", refname);
 +              return 0;
 +      }
 +
        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 -1067,50 +1072,50 @@@ void ref_array_clear(struct ref_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
  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 -1311,22 +1316,22 @@@ int parse_opt_ref_sorting(const struct 
        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;
+ }