Merge branch 'kn/ref-filter-branch-list'
authorJunio C Hamano <gitster@pobox.com>
Wed, 12 Jul 2017 22:18:23 +0000 (15:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 12 Jul 2017 22:18:23 +0000 (15:18 -0700)
The rewrite of "git branch --list" using for-each-ref's internals
that happened in v2.13 regressed its handling of color.branch.local;
this has been fixed.

* kn/ref-filter-branch-list:
ref-filter.c: drop return from void function
branch: set remote color in ref-filter branch immediately
branch: use BRANCH_COLOR_LOCAL in ref-filter format
branch: only perform HEAD check for local branches

1  2 
builtin/branch.c
ref-filter.c
diff --combined builtin/branch.c
index c958e93257910816bd8d40673805e8553f921f3f,fd186fe3b86284dc479e9e8445478cf37f3a2349..8a0595e11587aeac0f784400c43609404da3e045
@@@ -6,7 -6,6 +6,7 @@@
   */
  
  #include "cache.h"
 +#include "config.h"
  #include "color.h"
  #include "refs.h"
  #include "commit.h"
@@@ -34,7 -33,7 +34,7 @@@ static const char * const builtin_branc
  };
  
  static const char *head;
 -static unsigned char head_sha1[20];
 +static struct object_id head_oid;
  
  static int branch_use_color = -1;
  static char branch_colors[][COLOR_MAXLEN] = {
@@@ -119,13 -118,13 +119,13 @@@ static int branch_merged(int kind, cons
        if (kind == FILTER_REFS_BRANCHES) {
                struct branch *branch = branch_get(name);
                const char *upstream = branch_get_upstream(branch, NULL);
 -              unsigned char sha1[20];
 +              struct object_id oid;
  
                if (upstream &&
                    (reference_name = reference_name_to_free =
                     resolve_refdup(upstream, RESOLVE_REF_READING,
 -                                  sha1, NULL)) != NULL)
 -                      reference_rev = lookup_commit_reference(sha1);
 +                                  oid.hash, NULL)) != NULL)
 +                      reference_rev = lookup_commit_reference(&oid);
        }
        if (!reference_rev)
                reference_rev = head_rev;
  }
  
  static int check_branch_commit(const char *branchname, const char *refname,
 -                             const unsigned char *sha1, struct commit *head_rev,
 +                             const struct object_id *oid, struct commit *head_rev,
                               int kinds, int force)
  {
 -      struct commit *rev = lookup_commit_reference(sha1);
 +      struct commit *rev = lookup_commit_reference(oid);
        if (!rev) {
                error(_("Couldn't look up commit object for '%s'"), refname);
                return -1;
@@@ -185,34 -184,31 +185,34 @@@ static int delete_branches(int argc, co
                           int quiet)
  {
        struct commit *head_rev = NULL;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        char *name = NULL;
        const char *fmt;
        int i;
        int ret = 0;
        int remote_branch = 0;
        struct strbuf bname = STRBUF_INIT;
 +      unsigned allowed_interpret;
  
        switch (kinds) {
        case FILTER_REFS_REMOTES:
                fmt = "refs/remotes/%s";
                /* For subsequent UI messages */
                remote_branch = 1;
 +              allowed_interpret = INTERPRET_BRANCH_REMOTE;
  
                force = 1;
                break;
        case FILTER_REFS_BRANCHES:
                fmt = "refs/heads/%s";
 +              allowed_interpret = INTERPRET_BRANCH_LOCAL;
                break;
        default:
                die(_("cannot use -a with -d"));
        }
  
        if (!force) {
 -              head_rev = lookup_commit_reference(head_sha1);
 +              head_rev = lookup_commit_reference(&head_oid);
                if (!head_rev)
                        die(_("Couldn't look up commit object for HEAD"));
        }
                char *target = NULL;
                int flags = 0;
  
 -              strbuf_branchname(&bname, argv[i]);
 +              strbuf_branchname(&bname, argv[i], allowed_interpret);
                free(name);
                name = mkpathdup(fmt, bname.buf);
  
                                        RESOLVE_REF_READING
                                        | RESOLVE_REF_NO_RECURSE
                                        | RESOLVE_REF_ALLOW_BAD_NAME,
 -                                      sha1, &flags);
 +                                      oid.hash, &flags);
                if (!target) {
                        error(remote_branch
                              ? _("remote-tracking branch '%s' not found.")
                }
  
                if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
 -                  check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
 +                  check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
                                        force)) {
                        ret = 1;
                        goto next;
                }
  
 -              if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
 +              if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash,
                               REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
                               bname.buf,
                               (flags & REF_ISBROKEN) ? "broken"
                               : (flags & REF_ISSYMREF) ? target
 -                             : find_unique_abbrev(sha1, DEFAULT_ABBREV));
 +                             : find_unique_abbrev(oid.hash, DEFAULT_ABBREV));
                }
                delete_branch_config(bname.buf);
  
@@@ -335,8 -331,11 +335,11 @@@ static char *build_format(struct ref_fi
        struct strbuf local = STRBUF_INIT;
        struct strbuf remote = STRBUF_INIT;
  
-       strbuf_addf(&fmt, "%%(if)%%(HEAD)%%(then)* %s%%(else)  %%(end)",
-                   branch_get_color(BRANCH_COLOR_CURRENT));
+       strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else)  %s%%(end)",
+                   branch_get_color(BRANCH_COLOR_CURRENT),
+                   branch_get_color(BRANCH_COLOR_LOCAL));
+       strbuf_addf(&remote, "  %s",
+                   branch_get_color(BRANCH_COLOR_REMOTE));
  
        if (filter->verbose) {
                struct strbuf obname = STRBUF_INIT;
                else
                        strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
  
-               strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
+               strbuf_addf(&remote, "%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
                            "%%(if)%%(symref)%%(then) -> %%(symref:short)"
                            "%%(else) %s %%(contents:subject)%%(end)",
-                           branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix),
+                           maxwidth, quote_literal_for_format(remote_prefix),
                            branch_get_color(BRANCH_COLOR_RESET), obname.buf);
                strbuf_release(&obname);
        } else {
                strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
                            branch_get_color(BRANCH_COLOR_RESET));
-               strbuf_addf(&remote, "%s%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
-                           branch_get_color(BRANCH_COLOR_REMOTE), quote_literal_for_format(remote_prefix),
+               strbuf_addf(&remote, "%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
+                           quote_literal_for_format(remote_prefix),
                            branch_get_color(BRANCH_COLOR_RESET));
        }
  
@@@ -486,15 -485,14 +489,15 @@@ static void rename_branch(const char *o
  
        if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch rename failed"));
 -      strbuf_release(&logmsg);
  
        if (recovery)
                warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
  
 -      if (replace_each_worktree_head_symref(oldref.buf, newref.buf))
 +      if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
  
 +      strbuf_release(&logmsg);
 +
        strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
        strbuf_release(&oldref);
        strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
        strbuf_release(&newsection);
  }
  
 -static const char edit_description[] = "BRANCH_DESCRIPTION";
 +static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
  
  static int edit_branch_description(const char *branch_name)
  {
                      "  %s\n"
                      "Lines starting with '%c' will be stripped.\n"),
                    branch_name, comment_line_char);
 -      write_file_buf(git_path(edit_description), buf.buf, buf.len);
 +      write_file_buf(edit_description(), buf.buf, buf.len);
        strbuf_reset(&buf);
 -      if (launch_editor(git_path(edit_description), &buf, NULL)) {
 +      if (launch_editor(edit_description(), &buf, NULL)) {
                strbuf_release(&buf);
                return -1;
        }
@@@ -563,9 -561,7 +566,9 @@@ int cmd_branch(int argc, const char **a
                OPT_SET_INT('r', "remotes",     &filter.kind, N_("act on remote-tracking branches"),
                        FILTER_REFS_REMOTES),
                OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
 +              OPT_NO_CONTAINS(&filter.no_commit, N_("print only branches that don't contain the commit")),
                OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
 +              OPT_WITHOUT(&filter.no_commit, N_("print only branches that don't contain the commit")),
                OPT__ABBREV(&filter.abbrev),
  
                OPT_GROUP(N_("Specific git-branch actions:")),
  
        track = git_branch_track;
  
 -      head = resolve_refdup("HEAD", 0, head_sha1, NULL);
 +      head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
        if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
  
 -      if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
 +      if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
 +          filter.no_commit)
                list = 1;
  
        if (!!delete + !!rename + !!new_upstream +
diff --combined ref-filter.c
index e0578d8b5ae1a467082ee94ff798d054db1450bf,238888e4de342c601b26650251a235ffeb3190f2..ae6ecbd1cfe42936b22488d7761e4e487e2922ca
@@@ -15,7 -15,6 +15,7 @@@
  #include "version.h"
  #include "trailer.h"
  #include "wt-status.h"
 +#include "commit-slab.h"
  
  static struct ref_msg {
        const char *gone;
@@@ -221,7 -220,7 +221,7 @@@ static void objectname_atom_parser(stru
  
  static void refname_atom_parser(struct used_atom *atom, const char *arg)
  {
-       return refname_atom_parser_internal(&atom->u.refname, arg, atom->name);
+       refname_atom_parser_internal(&atom->u.refname, arg, atom->name);
  }
  
  static align_type parse_align_position(const char *s)
@@@ -290,9 -289,9 +290,9 @@@ static void if_atom_parser(struct used_
  
  static void head_atom_parser(struct used_atom *atom, const char *arg)
  {
 -      unsigned char unused[GIT_SHA1_RAWSZ];
 +      struct object_id unused;
  
 -      atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused, NULL);
 +      atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused.hash, NULL);
  }
  
  static struct {
@@@ -358,7 -357,7 +358,7 @@@ struct ref_formatting_state 
  struct atom_value {
        const char *s;
        void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
 -      unsigned long ul; /* used for sorting when not FIELD_STR */
 +      uintmax_t value; /* used for sorting when not FIELD_STR */
        struct used_atom *atom;
  };
  
@@@ -684,13 -683,13 +684,13 @@@ int verify_ref_format(const char *forma
   * by the "struct object" representation, set *eaten as well---it is a
   * signal from parse_object_buffer to us not to free the buffer.
   */
 -static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
 +static void *get_obj(const struct object_id *oid, struct object **obj, unsigned long *sz, int *eaten)
  {
        enum object_type type;
 -      void *buf = read_sha1_file(sha1, &type, sz);
 +      void *buf = read_sha1_file(oid->hash, &type, sz);
  
        if (buf)
 -              *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
 +              *obj = parse_object_buffer(oid, type, *sz, buf, eaten);
        else
                *obj = NULL;
        return buf;
@@@ -730,7 -729,7 +730,7 @@@ static void grab_common_values(struct a
                if (!strcmp(name, "objecttype"))
                        v->s = typename(obj->type);
                else if (!strcmp(name, "objectsize")) {
 -                      v->ul = sz;
 +                      v->value = sz;
                        v->s = xstrfmt("%lu", sz);
                }
                else if (deref)
@@@ -777,8 -776,8 +777,8 @@@ static void grab_commit_values(struct a
                        v->s = xstrdup(oid_to_hex(&commit->tree->object.oid));
                }
                else if (!strcmp(name, "numparent")) {
 -                      v->ul = commit_list_count(commit->parents);
 -                      v->s = xstrfmt("%lu", v->ul);
 +                      v->value = commit_list_count(commit->parents);
 +                      v->s = xstrfmt("%lu", (unsigned long)v->value);
                }
                else if (!strcmp(name, "parent")) {
                        struct commit_list *parents;
@@@ -856,7 -855,7 +856,7 @@@ static void grab_date(const char *buf, 
  {
        const char *eoemail = strstr(buf, "> ");
        char *zone;
 -      unsigned long timestamp;
 +      timestamp_t timestamp;
        long tz;
        struct date_mode date_mode = { DATE_NORMAL };
        const char *formatp;
  
        if (!eoemail)
                goto bad;
 -      timestamp = strtoul(eoemail + 2, &zone, 10);
 -      if (timestamp == ULONG_MAX)
 +      timestamp = parse_timestamp(eoemail + 2, &zone, 10);
 +      if (timestamp == TIME_MAX)
                goto bad;
        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->ul = timestamp;
 +      v->value = timestamp;
        return;
   bad:
        v->s = "";
 -      v->ul = 0;
 +      v->value = 0;
  }
  
  /* See grab_values */
@@@ -1257,18 -1256,12 +1257,18 @@@ char *get_head_description(void
                strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
                            state.branch);
        else if (state.detached_from) {
 -              /* TRANSLATORS: make sure these match _("HEAD detached at ")
 -                 and _("HEAD detached from ") in wt-status.c */
                if (state.detached_at)
 +                      /*
 +                       * TRANSLATORS: make sure this matches "HEAD
 +                       * detached at " in wt-status.c
 +                       */
                        strbuf_addf(&desc, _("(HEAD detached at %s)"),
                                state.detached_from);
                else
 +                      /*
 +                       * TRANSLATORS: make sure this matches "HEAD
 +                       * detached from " in wt-status.c
 +                       */
                        strbuf_addf(&desc, _("(HEAD detached from %s)"),
                                state.detached_from);
        }
@@@ -1304,14 -1297,14 +1304,14 @@@ static void populate_value(struct ref_a
        struct object *obj;
        int eaten, i;
        unsigned long size;
 -      const unsigned char *tagged;
 +      const struct object_id *tagged;
  
        ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
  
        if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
 -              unsigned char unused1[20];
 +              struct object_id unused1;
                ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
 -                                           unused1, NULL);
 +                                           unused1.hash, NULL);
                if (!ref->symref)
                        ref->symref = "";
        }
                                v->s = xstrdup(buf + 1);
                        }
                        continue;
 -              } else if (!deref && grab_objectname(name, ref->objectname, v, atom)) {
 +              } else if (!deref && grab_objectname(name, ref->objectname.hash, v, atom)) {
                        continue;
                } else if (!strcmp(name, "HEAD")) {
                        if (atom->u.head && !strcmp(ref->refname, atom->u.head))
        return;
  
   need_obj:
 -      buf = get_obj(ref->objectname, &obj, &size, &eaten);
 +      buf = get_obj(&ref->objectname, &obj, &size, &eaten);
        if (!buf)
                die(_("missing object %s for %s"),
 -                  sha1_to_hex(ref->objectname), ref->refname);
 +                  oid_to_hex(&ref->objectname), ref->refname);
        if (!obj)
                die(_("parse_object_buffer failed on %s for %s"),
 -                  sha1_to_hex(ref->objectname), ref->refname);
 +                  oid_to_hex(&ref->objectname), ref->refname);
  
        grab_values(ref->value, 0, obj, buf, size);
        if (!eaten)
         * If it is a tag object, see if we use a value that derefs
         * the object, and if we do grab the object it refers to.
         */
 -      tagged = ((struct tag *)obj)->tagged->oid.hash;
 +      tagged = &((struct tag *)obj)->tagged->oid;
  
        /*
         * NEEDSWORK: This derefs tag only once, which
        buf = get_obj(tagged, &obj, &size, &eaten);
        if (!buf)
                die(_("missing object %s for %s"),
 -                  sha1_to_hex(tagged), ref->refname);
 +                  oid_to_hex(tagged), ref->refname);
        if (!obj)
                die(_("parse_object_buffer failed on %s for %s"),
 -                  sha1_to_hex(tagged), ref->refname);
 +                  oid_to_hex(tagged), ref->refname);
        grab_values(ref->value, 1, obj, buf, size);
        if (!eaten)
                free(buf);
@@@ -1477,23 -1470,10 +1477,23 @@@ static void get_ref_atom_value(struct r
        *v = &ref->value[atom];
  }
  
 +/*
 + * Unknown has to be "0" here, because that's the default value for
 + * contains_cache slab entries that have not yet been assigned.
 + */
  enum contains_result {
 -      CONTAINS_UNKNOWN = -1,
 -      CONTAINS_NO = 0,
 -      CONTAINS_YES = 1
 +      CONTAINS_UNKNOWN = 0,
 +      CONTAINS_NO,
 +      CONTAINS_YES
 +};
 +
 +define_commit_slab(contains_cache, enum contains_result);
 +
 +struct ref_filter_cbdata {
 +      struct ref_array *array;
 +      struct ref_filter *filter;
 +      struct contains_cache contains_cache;
 +      struct contains_cache no_contains_cache;
  };
  
  /*
@@@ -1524,24 -1504,24 +1524,24 @@@ static int in_commit_list(const struct 
   * 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)
 +                                        const struct commit_list *want,
 +                                        struct contains_cache *cache)
  {
 -      /* 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;
 +      enum contains_result *cached = contains_cache_at(cache, candidate);
 +
 +      /* If we already have the answer cached, return that. */
 +      if (*cached)
 +              return *cached;
 +
        /* or are we it? */
        if (in_commit_list(want, candidate)) {
 -              candidate->object.flags |= TMP_MARK;
 -              return 1;
 +              *cached = CONTAINS_YES;
 +              return CONTAINS_YES;
        }
  
 -      if (parse_commit(candidate) < 0)
 -              return 0;
 -
 -      return -1;
 +      /* Otherwise, we don't know; prepare to recurse */
 +      parse_commit_or_die(candidate);
 +      return CONTAINS_UNKNOWN;
  }
  
  static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
  }
  
  static enum contains_result contains_tag_algo(struct commit *candidate,
 -              const struct commit_list *want)
 +                                            const struct commit_list *want,
 +                                            struct contains_cache *cache)
  {
        struct contains_stack contains_stack = { 0, 0, NULL };
 -      int result = contains_test(candidate, want);
 +      enum contains_result result = contains_test(candidate, want, cache);
  
        if (result != CONTAINS_UNKNOWN)
                return result;
                struct commit_list *parents = entry->parents;
  
                if (!parents) {
 -                      commit->object.flags |= UNINTERESTING;
 +                      *contains_cache_at(cache, commit) = CONTAINS_NO;
                        contains_stack.nr--;
                }
                /*
                 * If we just popped the stack, parents->item has been marked,
 -               * therefore contains_test will return a meaningful 0 or 1.
 +               * therefore contains_test will return a meaningful yes/no.
                 */
 -              else switch (contains_test(parents->item, want)) {
 +              else switch (contains_test(parents->item, want, cache)) {
                case CONTAINS_YES:
 -                      commit->object.flags |= TMP_MARK;
 +                      *contains_cache_at(cache, commit) = CONTAINS_YES;
                        contains_stack.nr--;
                        break;
                case CONTAINS_NO:
                }
        }
        free(contains_stack.contains_stack);
 -      return contains_test(candidate, want);
 +      return contains_test(candidate, want, cache);
  }
  
 -static int commit_contains(struct ref_filter *filter, struct commit *commit)
 +static int commit_contains(struct ref_filter *filter, struct commit *commit,
 +                         struct commit_list *list, struct contains_cache *cache)
  {
        if (filter->with_commit_tag_algo)
 -              return contains_tag_algo(commit, filter->with_commit);
 -      return is_descendant_of(commit, filter->with_commit);
 +              return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
 +      return is_descendant_of(commit, list);
  }
  
  /*
@@@ -1624,7 -1602,7 +1624,7 @@@ static int match_pattern(const struct r
               skip_prefix(refname, "refs/", &refname));
  
        for (; *patterns; patterns++) {
 -              if (!wildmatch(*patterns, refname, flags, NULL))
 +              if (!wildmatch(*patterns, refname, flags))
                        return 1;
        }
        return 0;
@@@ -1655,7 -1633,7 +1655,7 @@@ static int match_name_as_path(const str
                     refname[plen] == '/' ||
                     p[plen-1] == '/'))
                        return 1;
 -              if (!wildmatch(p, refname, WM_PATHNAME, NULL))
 +              if (!wildmatch(p, refname, WM_PATHNAME))
                        return 1;
        }
        return 0;
@@@ -1671,68 -1649,6 +1671,68 @@@ static int filter_pattern_match(struct 
        return match_pattern(filter, refname);
  }
  
 +/*
 + * Find the longest prefix of pattern we can pass to
 + * `for_each_fullref_in()`, namely the part of pattern preceding the
 + * first glob character. (Note that `for_each_fullref_in()` is
 + * perfectly happy working with a prefix that doesn't end at a
 + * pathname component boundary.)
 + */
 +static void find_longest_prefix(struct strbuf *out, const char *pattern)
 +{
 +      const char *p;
 +
 +      for (p = pattern; *p && !is_glob_special(*p); p++)
 +              ;
 +
 +      strbuf_add(out, pattern, p - pattern);
 +}
 +
 +/*
 + * This is the same as for_each_fullref_in(), but it tries to iterate
 + * only over the patterns we'll care about. Note that it _doesn't_ do a full
 + * pattern match, so the callback still has to match each ref individually.
 + */
 +static int for_each_fullref_in_pattern(struct ref_filter *filter,
 +                                     each_ref_fn cb,
 +                                     void *cb_data,
 +                                     int broken)
 +{
 +      struct strbuf prefix = STRBUF_INIT;
 +      int ret;
 +
 +      if (!filter->match_as_path) {
 +              /*
 +               * in this case, the patterns are applied after
 +               * prefixes like "refs/heads/" etc. are stripped off,
 +               * so we have to look at everything:
 +               */
 +              return for_each_fullref_in("", cb, cb_data, broken);
 +      }
 +
 +      if (!filter->name_patterns[0]) {
 +              /* no patterns; we have to look at everything */
 +              return for_each_fullref_in("", cb, cb_data, broken);
 +      }
 +
 +      if (filter->name_patterns[1]) {
 +              /*
 +               * multiple patterns; in theory this could still work as long
 +               * as the patterns are disjoint. We'd just make multiple calls
 +               * to for_each_ref(). But if they're not disjoint, we'd end up
 +               * reporting the same ref multiple times. So let's punt on that
 +               * for now.
 +               */
 +              return for_each_fullref_in("", cb, cb_data, broken);
 +      }
 +
 +      find_longest_prefix(&prefix, filter->name_patterns[0]);
 +
 +      ret = for_each_fullref_in(prefix.buf, cb, cb_data, broken);
 +      strbuf_release(&prefix);
 +      return ret;
 +}
 +
  /*
   * 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
   * 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)
 +static const struct object_id *match_points_at(struct oid_array *points_at,
 +                                             const struct object_id *oid,
 +                                             const char *refname)
  {
 -      const unsigned char *tagged_sha1 = NULL;
 +      const struct object_id *tagged_oid = NULL;
        struct object *obj;
  
 -      if (sha1_array_lookup(points_at, sha1) >= 0)
 -              return sha1;
 -      obj = parse_object(sha1);
 +      if (oid_array_lookup(points_at, oid) >= 0)
 +              return oid;
 +      obj = parse_object(oid);
        if (!obj)
                die(_("malformed object at '%s'"), refname);
        if (obj->type == OBJ_TAG)
 -              tagged_sha1 = ((struct tag *)obj)->tagged->oid.hash;
 -      if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
 -              return tagged_sha1;
 +              tagged_oid = &((struct tag *)obj)->tagged->oid;
 +      if (tagged_oid && oid_array_lookup(points_at, tagged_oid) >= 0)
 +              return tagged_oid;
        return NULL;
  }
  
@@@ -1772,13 -1688,13 +1772,13 @@@ static struct ref_array_item *new_ref_a
  {
        struct ref_array_item *ref;
        FLEX_ALLOC_STR(ref, refname, refname);
 -      hashcpy(ref->objectname, objectname);
 +      hashcpy(ref->objectname.hash, objectname);
        ref->flag = flag;
  
        return ref;
  }
  
 -static int filter_ref_kind(struct ref_filter *filter, const char *refname)
 +static int ref_kind_from_refname(const char *refname)
  {
        unsigned int i;
  
                { "refs/tags/", FILTER_REFS_TAGS}
        };
  
 -      if (filter->kind == FILTER_REFS_BRANCHES ||
 -          filter->kind == FILTER_REFS_REMOTES ||
 -          filter->kind == FILTER_REFS_TAGS)
 -              return filter->kind;
 -      else if (!strcmp(refname, "HEAD"))
 +      if (!strcmp(refname, "HEAD"))
                return FILTER_REFS_DETACHED_HEAD;
  
        for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
        return FILTER_REFS_OTHERS;
  }
  
 +static int filter_ref_kind(struct ref_filter *filter, const char *refname)
 +{
 +      if (filter->kind == FILTER_REFS_BRANCHES ||
 +          filter->kind == FILTER_REFS_REMOTES ||
 +          filter->kind == FILTER_REFS_TAGS)
 +              return filter->kind;
 +      return ref_kind_from_refname(refname);
 +}
 +
  /*
   * A call-back given to for_each_ref().  Filter refs and keep them for
   * later object processing.
@@@ -1841,7 -1752,7 +1841,7 @@@ static int ref_filter_handler(const cha
        if (!filter_pattern_match(filter, refname))
                return 0;
  
 -      if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
 +      if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))
                return 0;
  
        /*
         * 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 || filter->verbose) {
 -              commit = lookup_commit_reference_gently(oid->hash, 1);
 +      if (filter->merge_commit || filter->with_commit || filter->no_commit || filter->verbose) {
 +              commit = lookup_commit_reference_gently(oid, 1);
                if (!commit)
                        return 0;
 -              /* We perform the filtering for the '--contains' option */
 +              /* We perform the filtering for the '--contains' option... */
                if (filter->with_commit &&
 -                  !commit_contains(filter, commit))
 +                  !commit_contains(filter, commit, filter->with_commit, &ref_cbdata->contains_cache))
 +                      return 0;
 +              /* ...or for the `--no-contains' option */
 +              if (filter->no_commit &&
 +                  commit_contains(filter, commit, filter->no_commit, &ref_cbdata->no_contains_cache))
                        return 0;
        }
  
@@@ -1891,7 -1798,8 +1891,7 @@@ void ref_array_clear(struct ref_array *
  
        for (i = 0; i < array->nr; i++)
                free_array_item(array->items[i]);
 -      free(array->items);
 -      array->items = NULL;
 +      FREE_AND_NULL(array->items);
        array->nr = array->alloc = 0;
  }
  
@@@ -1958,9 -1866,6 +1958,9 @@@ int filter_refs(struct ref_array *array
                broken = 1;
        filter->kind = type & FILTER_REFS_KIND_MASK;
  
 +      init_contains_cache(&ref_cbdata.contains_cache);
 +      init_contains_cache(&ref_cbdata.no_contains_cache);
 +
        /*  Simple per-ref filtering */
        if (!filter->kind)
                die("filter_refs: invalid type");
                else if (filter->kind == FILTER_REFS_TAGS)
                        ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
                else if (filter->kind & FILTER_REFS_ALL)
 -                      ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken);
 +                      ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata, broken);
                if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
                        head_ref(ref_filter_handler, &ref_cbdata);
        }
  
 +      clear_contains_cache(&ref_cbdata.contains_cache);
 +      clear_contains_cache(&ref_cbdata.no_contains_cache);
  
        /*  Filters that need revision walking */
        if (filter->merge_commit)
@@@ -2008,9 -1911,9 +2008,9 @@@ static int cmp_ref_sorting(struct ref_s
        else if (cmp_type == FIELD_STR)
                cmp = cmp_fn(va->s, vb->s);
        else {
 -              if (va->ul < vb->ul)
 +              if (va->value < vb->value)
                        cmp = -1;
 -              else if (va->ul == vb->ul)
 +              else if (va->value == vb->value)
                        cmp = cmp_fn(a->refname, b->refname);
                else
                        cmp = 1;
        return (s->reverse) ? -cmp : cmp;
  }
  
 -static struct ref_sorting *ref_sorting;
 -static int compare_refs(const void *a_, const void *b_)
 +static int compare_refs(const void *a_, const void *b_, void *ref_sorting)
  {
        struct ref_array_item *a = *((struct ref_array_item **)a_);
        struct ref_array_item *b = *((struct ref_array_item **)b_);
  
  void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
  {
 -      ref_sorting = sorting;
 -      QSORT(array->items, array->nr, compare_refs);
 +      QSORT_S(array->items, array->nr, compare_refs, sorting);
  }
  
  static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
@@@ -2107,16 -2012,6 +2107,16 @@@ void show_ref_array_item(struct ref_arr
        putchar('\n');
  }
  
 +void pretty_print_ref(const char *name, const unsigned char *sha1,
 +              const char *format)
 +{
 +      struct ref_array_item *ref_item;
 +      ref_item = new_ref_array_item(name, sha1, 0);
 +      ref_item->kind = ref_kind_from_refname(name);
 +      show_ref_array_item(ref_item, format, 0);
 +      free_array_item(ref_item);
 +}
 +
  /*  If no sorting option is given, use refname to sort as default */
  struct ref_sorting *ref_default_sorting(void)
  {
@@@ -2157,25 -2052,16 +2157,25 @@@ int parse_opt_ref_sorting(const struct 
  int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
  {
        struct ref_filter *rf = opt->value;
 -      unsigned char sha1[20];
 +      struct object_id oid;
 +      int no_merged = starts_with(opt->long_name, "no");
 +
 +      if (rf->merge) {
 +              if (no_merged) {
 +                      return opterror(opt, "is incompatible with --merged", 0);
 +              } else {
 +                      return opterror(opt, "is incompatible with --no-merged", 0);
 +              }
 +      }
  
 -      rf->merge = starts_with(opt->long_name, "no")
 +      rf->merge = no_merged
                ? REF_FILTER_MERGED_OMIT
                : REF_FILTER_MERGED_INCLUDE;
  
 -      if (get_sha1(arg, sha1))
 +      if (get_oid(arg, &oid))
                die(_("malformed object name %s"), arg);
  
 -      rf->merge_commit = lookup_commit_reference_gently(sha1, 0);
 +      rf->merge_commit = lookup_commit_reference_gently(&oid, 0);
        if (!rf->merge_commit)
                return opterror(opt, "must point to a commit", 0);