Merge branch 'kn/ref-filter-branch-list'
authorJunio C Hamano <gitster@pobox.com>
Mon, 29 May 2017 03:34:50 +0000 (12:34 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 May 2017 03:34:51 +0000 (12:34 +0900)
"git for-each-ref --format=..." with %(HEAD) in the format used to
resolve the HEAD symref as many times as it had processed refs,
which was wasteful, and "git branch" shared the same problem.

* kn/ref-filter-branch-list:
ref-filter: resolve HEAD when parsing %(HEAD) atom

1  2 
ref-filter.c
diff --combined ref-filter.c
index 6cc93dcd9f54ca4fad6f68b337e1f96b7c27996b,f4c68237275d49af3f1665f40788bc8287e3f879..3742abbf85ce4044641bc937a70504c059ed8822
@@@ -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;
@@@ -93,6 -92,7 +93,7 @@@ static struct used_atom 
                        unsigned int length;
                } objectname;
                struct refname_atom refname;
+               char *head;
        } u;
  } *used_atom;
  static int used_atom_cnt, need_tagged, need_symref;
@@@ -287,6 -287,12 +288,12 @@@ static void if_atom_parser(struct used_
        }
  }
  
 -      unsigned char unused[GIT_SHA1_RAWSZ];
+ static void head_atom_parser(struct used_atom *atom, const char *arg)
+ {
 -      atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused, NULL);
++      struct object_id unused;
++      atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused.hash, NULL);
+ }
  
  static struct {
        const char *name;
        { "push", FIELD_STR, remote_ref_atom_parser },
        { "symref", FIELD_STR, refname_atom_parser },
        { "flag" },
-       { "HEAD" },
+       { "HEAD", FIELD_STR, head_atom_parser },
        { "color", FIELD_STR, color_atom_parser },
        { "align", FIELD_STR, align_atom_parser },
        { "end" },
@@@ -351,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;
  };
  
@@@ -677,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;
@@@ -723,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)
@@@ -770,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;
@@@ -849,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 */
@@@ -1250,14 -1256,12 +1257,14 @@@ 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);
        }
@@@ -1293,14 -1297,14 +1300,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")) {
-                       const char *head;
-                       struct object_id oid;
-                       head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
-                                                 oid.hash, NULL);
-                       if (head && !strcmp(ref->refname, head))
+                       if (atom->u.head && !strcmp(ref->refname, atom->u.head))
                                v->s = "*";
                        else
                                v->s = " ";
        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);
@@@ -1471,23 -1470,10 +1473,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;
  };
  
  /*
@@@ -1518,24 -1504,24 +1520,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);
  }
  
  /*
@@@ -1678,22 -1662,22 +1680,22 @@@ static int filter_pattern_match(struct 
   * 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;
  }
  
@@@ -1704,13 -1688,13 +1706,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.
@@@ -1773,7 -1752,7 +1775,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,9 -1866,6 +1893,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");
                        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)
@@@ -1941,9 -1911,9 +1943,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)
@@@ -2040,16 -2012,6 +2042,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)
  {
@@@ -2090,25 -2052,16 +2092,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);