Merge branch 'jk/still-interesting' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 26 May 2015 20:49:26 +0000 (13:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 26 May 2015 20:49:26 +0000 (13:49 -0700)
"git rev-list --objects $old --not --all" to see if everything that
is reachable from $old is already connected to the existing refs
was very inefficient.

* jk/still-interesting:
limit_list: avoid quadratic behavior from still_interesting

1  2 
revision.c
diff --combined revision.c
index 6399a0412cc9005875e282a139200a4f918743f5,06f31d6fb5632c6d89f581f8d9282348cb6f743a..7ddbaa083e9e59d3db270b96b47a20e6c99bdaa3
@@@ -17,7 -17,6 +17,7 @@@
  #include "mailmap.h"
  #include "commit-slab.h"
  #include "dir.h"
 +#include "cache-tree.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -87,6 -86,16 +87,6 @@@ void show_object_with_name(FILE *out, s
        fputc('\n', out);
  }
  
 -void add_object(struct object *obj,
 -              struct object_array *p,
 -              struct name_path *path,
 -              const char *name)
 -{
 -      char *pn = path_name(path, name);
 -      add_object_array(obj, pn, p);
 -      free(pn);
 -}
 -
  static void mark_blob_uninteresting(struct blob *blob)
  {
        if (!blob)
@@@ -189,10 -198,9 +189,10 @@@ void mark_parents_uninteresting(struct 
        }
  }
  
 -static void add_pending_object_with_mode(struct rev_info *revs,
 +static void add_pending_object_with_path(struct rev_info *revs,
                                         struct object *obj,
 -                                       const char *name, unsigned mode)
 +                                       const char *name, unsigned mode,
 +                                       const char *path)
  {
        if (!obj)
                return;
                if (st)
                        return;
        }
 -      add_object_array_with_mode(obj, name, &revs->pending, mode);
 +      add_object_array_with_path(obj, name, &revs->pending, mode, path);
 +}
 +
 +static void add_pending_object_with_mode(struct rev_info *revs,
 +                                       struct object *obj,
 +                                       const char *name, unsigned mode)
 +{
 +      add_pending_object_with_path(revs, obj, name, mode, NULL);
  }
  
  void add_pending_object(struct rev_info *revs,
@@@ -264,12 -265,8 +264,12 @@@ void add_pending_sha1(struct rev_info *
  }
  
  static struct commit *handle_commit(struct rev_info *revs,
 -                                  struct object *object, const char *name)
 +                                  struct object_array_entry *entry)
  {
 +      struct object *object = entry->item;
 +      const char *name = entry->name;
 +      const char *path = entry->path;
 +      unsigned int mode = entry->mode;
        unsigned long flags = object->flags;
  
        /*
                        die("bad object %s", sha1_to_hex(tag->tagged->sha1));
                }
                object->flags |= flags;
 +              /*
 +               * We'll handle the tagged object by looping or dropping
 +               * through to the non-tag handlers below. Do not
 +               * propagate data from the tag's pending entry.
 +               */
 +              name = "";
 +              path = NULL;
 +              mode = 0;
        }
  
        /*
                        revs->limited = 1;
                }
                if (revs->show_source && !commit->util)
 -                      commit->util = (void *) name;
 +                      commit->util = xstrdup(name);
                return commit;
        }
  
                        mark_tree_contents_uninteresting(tree);
                        return NULL;
                }
 -              add_pending_object(revs, object, "");
 +              add_pending_object_with_path(revs, object, name, mode, path);
                return NULL;
        }
  
                        return NULL;
                if (flags & UNINTERESTING)
                        return NULL;
 -              add_pending_object(revs, object, "");
 +              add_pending_object_with_path(revs, object, name, mode, path);
                return NULL;
        }
        die("%s is unknown object", name);
  }
  
- static int everybody_uninteresting(struct commit_list *orig)
+ static int everybody_uninteresting(struct commit_list *orig,
+                                  struct commit **interesting_cache)
  {
        struct commit_list *list = orig;
+       if (*interesting_cache) {
+               struct commit *commit = *interesting_cache;
+               if (!(commit->object.flags & UNINTERESTING))
+                       return 0;
+       }
        while (list) {
                struct commit *commit = list->item;
                list = list->next;
                if (commit->object.flags & UNINTERESTING)
                        continue;
+               if (interesting_cache)
+                       *interesting_cache = commit;
                return 0;
        }
        return 1;
@@@ -484,7 -483,7 +494,7 @@@ static int rev_compare_tree(struct rev_
                 * If we are simplifying by decoration, then the commit
                 * is worth showing if it has a tag pointing at it.
                 */
 -              if (lookup_decoration(&name_decoration, &commit->object))
 +              if (get_name_decoration(&commit->object))
                        return REV_TREE_DIFFERENT;
                /*
                 * A commit that is not pointed by a tag is uninteresting
@@@ -940,7 -939,8 +950,8 @@@ static void cherry_pick_list(struct com
  /* How many extra uninteresting commits we want to see.. */
  #define SLOP 5
  
- static int still_interesting(struct commit_list *src, unsigned long date, int slop)
+ static int still_interesting(struct commit_list *src, unsigned long date, int slop,
+                            struct commit **interesting_cache)
  {
        /*
         * No source list at all? We're definitely done..
         * Does the source list still have interesting commits in
         * it? Definitely not done..
         */
-       if (!everybody_uninteresting(src))
+       if (!everybody_uninteresting(src, interesting_cache))
                return SLOP;
  
        /* Ok, we're closing in.. */
@@@ -1078,6 -1078,7 +1089,7 @@@ static int limit_list(struct rev_info *
        struct commit_list *newlist = NULL;
        struct commit_list **p = &newlist;
        struct commit_list *bottom = NULL;
+       struct commit *interesting_cache = NULL;
  
        if (revs->ancestry_path) {
                bottom = collect_bottom_commits(list);
                list = list->next;
                free(entry);
  
+               if (commit == interesting_cache)
+                       interesting_cache = NULL;
                if (revs->max_age != -1 && (commit->date < revs->max_age))
                        obj->flags |= UNINTERESTING;
                if (add_parents_to_list(revs, commit, &list, NULL) < 0)
                        mark_parents_uninteresting(commit);
                        if (revs->show_all)
                                p = &commit_list_insert(commit, p)->next;
-                       slop = still_interesting(list, date, slop);
+                       slop = still_interesting(list, date, slop, &interesting_cache);
                        if (slop)
                                continue;
                        /* If showing all, add the whole pending list to the end */
@@@ -1286,7 -1290,7 +1301,7 @@@ static int handle_one_reflog(const cha
        return 0;
  }
  
 -static void handle_reflog(struct rev_info *revs, unsigned flags)
 +void add_reflogs_to_pending(struct rev_info *revs, unsigned flags)
  {
        struct all_refs_cb cb;
        cb.all_revs = revs;
        for_each_reflog(handle_one_reflog, &cb);
  }
  
 +static void add_cache_tree(struct cache_tree *it, struct rev_info *revs,
 +                         struct strbuf *path)
 +{
 +      size_t baselen = path->len;
 +      int i;
 +
 +      if (it->entry_count >= 0) {
 +              struct tree *tree = lookup_tree(it->sha1);
 +              add_pending_object_with_path(revs, &tree->object, "",
 +                                           040000, path->buf);
 +      }
 +
 +      for (i = 0; i < it->subtree_nr; i++) {
 +              struct cache_tree_sub *sub = it->down[i];
 +              strbuf_addf(path, "%s%s", baselen ? "/" : "", sub->name);
 +              add_cache_tree(sub->cache_tree, revs, path);
 +              strbuf_setlen(path, baselen);
 +      }
 +
 +}
 +
 +void add_index_objects_to_pending(struct rev_info *revs, unsigned flags)
 +{
 +      int i;
 +
 +      read_cache();
 +      for (i = 0; i < active_nr; i++) {
 +              struct cache_entry *ce = active_cache[i];
 +              struct blob *blob;
 +
 +              if (S_ISGITLINK(ce->ce_mode))
 +                      continue;
 +
 +              blob = lookup_blob(ce->sha1);
 +              if (!blob)
 +                      die("unable to add index blob to traversal");
 +              add_pending_object_with_path(revs, &blob->object, "",
 +                                           ce->ce_mode, ce->name);
 +      }
 +
 +      if (active_cache_tree) {
 +              struct strbuf path = STRBUF_INIT;
 +              add_cache_tree(active_cache_tree, revs, &path);
 +              strbuf_release(&path);
 +      }
 +}
 +
  static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
  {
        unsigned char sha1[20];
@@@ -1441,7 -1398,7 +1456,7 @@@ static void prepare_show_merge(struct r
        other = lookup_commit_or_die(sha1, "MERGE_HEAD");
        add_pending_object(revs, &head->object, "HEAD");
        add_pending_object(revs, &other->object, "MERGE_HEAD");
 -      bases = get_merge_bases(head, other, 1);
 +      bases = get_merge_bases(head, other);
        add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);
        add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);
        free_commit_list(bases);
                        continue;
                if (ce_path_match(ce, &revs->prune_data, NULL)) {
                        prune_num++;
 -                      prune = xrealloc(prune, sizeof(*prune) * prune_num);
 +                      REALLOC_ARRAY(prune, prune_num);
                        prune[prune_num-2] = ce->name;
                        prune[prune_num-1] = NULL;
                }
@@@ -1546,7 -1503,7 +1561,7 @@@ int handle_revision_arg(const char *arg
                                     : lookup_commit_reference(b_obj->sha1));
                                if (!a || !b)
                                        goto missing;
 -                              exclude = get_merge_bases(a, b, 1);
 +                              exclude = get_merge_bases(a, b);
                                add_rev_cmdline_list(revs, exclude,
                                                     REV_CMD_MERGE_BASE,
                                                     flags_exclude);
@@@ -1691,7 -1648,6 +1706,7 @@@ static int handle_revision_opt(struct r
            !strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
            !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
            !strcmp(arg, "--bisect") || starts_with(arg, "--glob=") ||
 +          !strcmp(arg, "--indexed-objects") ||
            starts_with(arg, "--exclude=") ||
            starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
            starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
                revs->tree_objects = 1;
                revs->blob_objects = 1;
                revs->edge_hint = 1;
 +      } else if (!strcmp(arg, "--objects-edge-aggressive")) {
 +              revs->tag_objects = 1;
 +              revs->tree_objects = 1;
 +              revs->blob_objects = 1;
 +              revs->edge_hint = 1;
 +              revs->edge_hint_aggressive = 1;
        } else if (!strcmp(arg, "--verify-objects")) {
                revs->tag_objects = 1;
                revs->tree_objects = 1;
        } else if (!strcmp(arg, "--pretty")) {
                revs->verbose_header = 1;
                revs->pretty_given = 1;
 -              get_commit_format(arg+8, revs);
 +              get_commit_format(NULL, revs);
        } else if (starts_with(arg, "--pretty=") || starts_with(arg, "--format=")) {
                /*
                 * Detached form ("--pretty X" as opposed to "--pretty=X")
                grep_set_pattern_type_option(GREP_PATTERN_TYPE_PCRE, &revs->grep_filter);
        } else if (!strcmp(arg, "--all-match")) {
                revs->grep_filter.all_match = 1;
 +      } else if (!strcmp(arg, "--invert-grep")) {
 +              revs->invert_grep = 1;
        } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
                if (strcmp(optarg, "none"))
                        git_log_output_encoding = xstrdup(optarg);
@@@ -2128,9 -2076,7 +2143,9 @@@ static int handle_revision_pseudo_opt(c
                for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
                clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--reflog")) {
 -              handle_reflog(revs, *flags);
 +              add_reflogs_to_pending(revs, *flags);
 +      } else if (!strcmp(arg, "--indexed-objects")) {
 +              add_index_objects_to_pending(revs, *flags);
        } else if (!strcmp(arg, "--not")) {
                *flags ^= UNINTERESTING | BOTTOM;
        } else if (!strcmp(arg, "--no-walk")) {
@@@ -2339,14 -2285,9 +2354,14 @@@ int setup_revisions(int argc, const cha
  
        if (revs->reflog_info && revs->graph)
                die("cannot combine --walk-reflogs with --graph");
 +      if (revs->no_walk && revs->graph)
 +              die("cannot combine --no-walk with --graph");
        if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
                die("cannot use --grep-reflog without --walk-reflogs");
  
 +      if (revs->first_parent_only && revs->bisect)
 +              die(_("--first-parent is incompatible with --bisect"));
 +
        return left;
  }
  
@@@ -2730,26 -2671,26 +2745,26 @@@ void reset_revision_walk(void
  
  int prepare_revision_walk(struct rev_info *revs)
  {
 -      int nr = revs->pending.nr;
 -      struct object_array_entry *e, *list;
 +      int i;
 +      struct object_array old_pending;
        struct commit_list **next = &revs->commits;
  
 -      e = list = revs->pending.objects;
 +      memcpy(&old_pending, &revs->pending, sizeof(old_pending));
        revs->pending.nr = 0;
        revs->pending.alloc = 0;
        revs->pending.objects = NULL;
 -      while (--nr >= 0) {
 -              struct commit *commit = handle_commit(revs, e->item, e->name);
 +      for (i = 0; i < old_pending.nr; i++) {
 +              struct object_array_entry *e = old_pending.objects + i;
 +              struct commit *commit = handle_commit(revs, e);
                if (commit) {
                        if (!(commit->object.flags & SEEN)) {
                                commit->object.flags |= SEEN;
                                next = commit_list_append(commit, next);
                        }
                }
 -              e++;
        }
        if (!revs->leak_pending)
 -              free(list);
 +              object_array_clear(&old_pending);
  
        /* Signal whether we need per-parent treesame decoration */
        if (revs->simplify_merges ||
@@@ -2922,7 -2863,7 +2937,7 @@@ static int commit_match(struct commit *
                                     (char *)message, strlen(message));
        strbuf_release(&buf);
        unuse_commit_buffer(commit, message);
 -      return retval;
 +      return opt->invert_grep ? !retval : retval;
  }
  
  static inline int want_ancestry(const struct rev_info *revs)
@@@ -2975,61 -2916,6 +2990,61 @@@ enum commit_action get_commit_action(st
        return commit_show;
  }
  
 +define_commit_slab(saved_parents, struct commit_list *);
 +
 +#define EMPTY_PARENT_LIST ((struct commit_list *)-1)
 +
 +/*
 + * You may only call save_parents() once per commit (this is checked
 + * for non-root commits).
 + */
 +static void save_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list **pp;
 +
 +      if (!revs->saved_parents_slab) {
 +              revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents));
 +              init_saved_parents(revs->saved_parents_slab);
 +      }
 +
 +      pp = saved_parents_at(revs->saved_parents_slab, commit);
 +
 +      /*
 +       * When walking with reflogs, we may visit the same commit
 +       * several times: once for each appearance in the reflog.
 +       *
 +       * In this case, save_parents() will be called multiple times.
 +       * We want to keep only the first set of parents.  We need to
 +       * store a sentinel value for an empty (i.e., NULL) parent
 +       * list to distinguish it from a not-yet-saved list, however.
 +       */
 +      if (*pp)
 +              return;
 +      if (commit->parents)
 +              *pp = copy_commit_list(commit->parents);
 +      else
 +              *pp = EMPTY_PARENT_LIST;
 +}
 +
 +static void free_saved_parents(struct rev_info *revs)
 +{
 +      if (revs->saved_parents_slab)
 +              clear_saved_parents(revs->saved_parents_slab);
 +}
 +
 +struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
 +{
 +      struct commit_list *parents;
 +
 +      if (!revs->saved_parents_slab)
 +              return commit->parents;
 +
 +      parents = *saved_parents_at(revs->saved_parents_slab, commit);
 +      if (parents == EMPTY_PARENT_LIST)
 +              return NULL;
 +      return parents;
 +}
 +
  enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
  {
        enum commit_action action = get_commit_action(revs, commit);
@@@ -3329,3 -3215,54 +3344,3 @@@ void put_revision_mark(const struct rev
        fputs(mark, stdout);
        putchar(' ');
  }
 -
 -define_commit_slab(saved_parents, struct commit_list *);
 -
 -#define EMPTY_PARENT_LIST ((struct commit_list *)-1)
 -
 -void save_parents(struct rev_info *revs, struct commit *commit)
 -{
 -      struct commit_list **pp;
 -
 -      if (!revs->saved_parents_slab) {
 -              revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents));
 -              init_saved_parents(revs->saved_parents_slab);
 -      }
 -
 -      pp = saved_parents_at(revs->saved_parents_slab, commit);
 -
 -      /*
 -       * When walking with reflogs, we may visit the same commit
 -       * several times: once for each appearance in the reflog.
 -       *
 -       * In this case, save_parents() will be called multiple times.
 -       * We want to keep only the first set of parents.  We need to
 -       * store a sentinel value for an empty (i.e., NULL) parent
 -       * list to distinguish it from a not-yet-saved list, however.
 -       */
 -      if (*pp)
 -              return;
 -      if (commit->parents)
 -              *pp = copy_commit_list(commit->parents);
 -      else
 -              *pp = EMPTY_PARENT_LIST;
 -}
 -
 -struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
 -{
 -      struct commit_list *parents;
 -
 -      if (!revs->saved_parents_slab)
 -              return commit->parents;
 -
 -      parents = *saved_parents_at(revs->saved_parents_slab, commit);
 -      if (parents == EMPTY_PARENT_LIST)
 -              return NULL;
 -      return parents;
 -}
 -
 -void free_saved_parents(struct rev_info *revs)
 -{
 -      if (revs->saved_parents_slab)
 -              clear_saved_parents(revs->saved_parents_slab);
 -}