Merge branch 'jk/free-tree-buffer'
authorJunio C Hamano <gitster@pobox.com>
Tue, 17 Sep 2013 18:37:33 +0000 (11:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 17 Sep 2013 18:37:33 +0000 (11:37 -0700)
* jk/free-tree-buffer:
clear parsed flag when we free tree buffers

1  2 
builtin/fsck.c
builtin/index-pack.c
builtin/reflog.c
http-push.c
revision.c
tree.c
tree.h
diff --combined builtin/fsck.c
index 39fa5e86d4d54e2f9c40d61ae78778078c56f5a8,579fdcceb9a7c1a78767144f66f5bd21a65cdcb1..97ce678c6ba63afeb3deedfd469c4a53a475a1be
@@@ -16,6 -16,7 +16,7 @@@
  
  #define REACHABLE 0x0001
  #define SEEN      0x0002
+ #define HAS_OBJ   0x0004
  
  static int show_root;
  static int show_tags;
@@@ -101,7 -102,7 +102,7 @@@ static int mark_object(struct object *o
        if (obj->flags & REACHABLE)
                return 0;
        obj->flags |= REACHABLE;
-       if (!obj->parsed) {
+       if (!(obj->flags & HAS_OBJ)) {
                if (parent && !has_sha1_file(obj->sha1)) {
                        printf("broken link from %7s %s\n",
                                 typename(parent->type), sha1_to_hex(parent->sha1));
                return 1;
        }
  
 -      add_object_array(obj, (void *) parent, &pending);
 +      add_object_array(obj, NULL, &pending);
        return 0;
  }
  
@@@ -127,16 -128,13 +128,13 @@@ static int traverse_one_object(struct o
        struct tree *tree = NULL;
  
        if (obj->type == OBJ_TREE) {
-               obj->parsed = 0;
                tree = (struct tree *)obj;
                if (parse_tree(tree) < 0)
                        return 1; /* error already displayed */
        }
        result = fsck_walk(obj, mark_object, obj);
-       if (tree) {
-               free(tree->buffer);
-               tree->buffer = NULL;
-       }
+       if (tree)
+               free_tree_buffer(tree);
        return result;
  }
  
@@@ -178,7 -176,7 +176,7 @@@ static void check_reachable_object(stru
         * except if it was in a pack-file and we didn't
         * do a full fsck
         */
-       if (!obj->parsed) {
+       if (!(obj->flags & HAS_OBJ)) {
                if (has_sha1_pack(obj->sha1))
                        return; /* it is in pack - forget about it */
                printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
@@@ -306,8 -304,7 +304,7 @@@ static int fsck_obj(struct object *obj
        if (obj->type == OBJ_TREE) {
                struct tree *item = (struct tree *) obj;
  
-               free(item->buffer);
-               item->buffer = NULL;
+               free_tree_buffer(item);
        }
  
        if (obj->type == OBJ_COMMIT) {
@@@ -340,6 -337,7 +337,7 @@@ static int fsck_sha1(const unsigned cha
                return error("%s: object corrupt or missing",
                             sha1_to_hex(sha1));
        }
+       obj->flags |= HAS_OBJ;
        return fsck_obj(obj);
  }
  
@@@ -352,6 -350,7 +350,7 @@@ static int fsck_obj_buffer(const unsign
                errors_found |= ERROR_OBJECT;
                return error("%s: object corrupt or missing", sha1_to_hex(sha1));
        }
+       obj->flags = HAS_OBJ;
        return fsck_obj(obj);
  }
  
@@@ -611,15 -610,15 +610,15 @@@ static char const * const fsck_usage[] 
  
  static struct option fsck_opts[] = {
        OPT__VERBOSE(&verbose, N_("be verbose")),
 -      OPT_BOOLEAN(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
 +      OPT_BOOL(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
        OPT_BOOL(0, "dangling", &show_dangling, N_("show dangling objects")),
 -      OPT_BOOLEAN(0, "tags", &show_tags, N_("report tags")),
 -      OPT_BOOLEAN(0, "root", &show_root, N_("report root nodes")),
 -      OPT_BOOLEAN(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
 -      OPT_BOOLEAN(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
 -      OPT_BOOLEAN(0, "full", &check_full, N_("also consider packs and alternate objects")),
 -      OPT_BOOLEAN(0, "strict", &check_strict, N_("enable more strict checking")),
 -      OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
 +      OPT_BOOL(0, "tags", &show_tags, N_("report tags")),
 +      OPT_BOOL(0, "root", &show_root, N_("report root nodes")),
 +      OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
 +      OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
 +      OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")),
 +      OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")),
 +      OPT_BOOL(0, "lost-found", &write_lost_and_found,
                                N_("write dangling objects in .git/lost-found")),
        OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
        OPT_END(),
diff --combined builtin/index-pack.c
index 9c1cfac4427ef8d8fbca0834e1c8b644ceddf998,20cf284e869e2e37eded2c9bd990453461c75359..9e9eb4b74e8335f097c7cc9e261b051feda0bf0d
@@@ -77,10 -77,8 +77,10 @@@ static int nr_threads
  
  static int from_stdin;
  static int strict;
 +static int do_fsck_object;
  static int verbose;
  static int show_stat;
 +static int check_self_contained_and_connected;
  
  static struct progress *progress;
  
@@@ -189,13 -187,13 +189,13 @@@ static int mark_link(struct object *obj
  
  /* The content of each linked object must have been checked
     or it must be already present in the object database */
 -static void check_object(struct object *obj)
 +static unsigned check_object(struct object *obj)
  {
        if (!obj)
 -              return;
 +              return 0;
  
        if (!(obj->flags & FLAG_LINK))
 -              return;
 +              return 0;
  
        if (!(obj->flags & FLAG_CHECKED)) {
                unsigned long size;
                if (type != obj->type || type <= 0)
                        die(_("object of unexpected type"));
                obj->flags |= FLAG_CHECKED;
 -              return;
 +              return 1;
        }
 +
 +      return 0;
  }
  
 -static void check_objects(void)
 +static unsigned check_objects(void)
  {
 -      unsigned i, max;
 +      unsigned i, max, foreign_nr = 0;
  
        max = get_max_object_index();
        for (i = 0; i < max; i++)
 -              check_object(get_indexed_object(i));
 +              foreign_nr += check_object(get_indexed_object(i));
 +      return foreign_nr;
  }
  
  
@@@ -752,7 -747,8 +752,7 @@@ static void sha1_object(const void *dat
                        int eaten;
                        void *buf = (void *) data;
  
 -                      if (!buf)
 -                              buf = new_data = get_data_from_pack(obj_entry);
 +                      assert(data && "data can only be NULL for large _blobs_");
  
                        /*
                         * we do not need to free the memory here, as the
                        obj = parse_object_buffer(sha1, type, size, buf, &eaten);
                        if (!obj)
                                die(_("invalid %s"), typename(type));
 -                      if (fsck_object(obj, 1, fsck_error_function))
 +                      if (do_fsck_object &&
 +                          fsck_object(obj, 1, fsck_error_function))
                                die(_("Error in object"));
                        if (fsck_walk(obj, mark_link, NULL))
                                die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
                        if (obj->type == OBJ_TREE) {
                                struct tree *item = (struct tree *) obj;
                                item->buffer = NULL;
+                               obj->parsed = 0;
                        }
                        if (obj->type == OBJ_COMMIT) {
                                struct commit *commit = (struct commit *) obj;
@@@ -1496,7 -1492,6 +1497,7 @@@ int cmd_index_pack(int argc, const cha
        struct pack_idx_entry **idx_objects;
        struct pack_idx_option opts;
        unsigned char pack_sha1[20];
 +      unsigned foreign_nr = 1;        /* zero is a "good" value, assume bad */
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(index_pack_usage);
                                fix_thin_pack = 1;
                        } else if (!strcmp(arg, "--strict")) {
                                strict = 1;
 +                              do_fsck_object = 1;
 +                      } else if (!strcmp(arg, "--check-self-contained-and-connected")) {
 +                              strict = 1;
 +                              check_self_contained_and_connected = 1;
                        } else if (!strcmp(arg, "--verify")) {
                                verify = 1;
                        } else if (!strcmp(arg, "--verify-stat")) {
        conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
        free(deltas);
        if (strict)
 -              check_objects();
 +              foreign_nr = check_objects();
  
        if (show_stat)
                show_pack_info(stat_only);
        if (index_name == NULL)
                free((void *) curr_index);
  
 +      /*
 +       * Let the caller know this pack is not self contained
 +       */
 +      if (check_self_contained_and_connected && foreign_nr)
 +              return 1;
 +
        return 0;
  }
diff --combined builtin/reflog.c
index 54184b3d133bb66db272fd21ad3cad53a4a9a5cb,9f121cd587060fc61eb3f39d8f5418ddc225c498..ba27f7cc614214eb32f60b684b3617c39a0066ef
@@@ -94,8 -94,7 +94,7 @@@ static int tree_is_complete(const unsig
                        complete = 0;
                }
        }
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_tree_buffer(tree);
  
        if (complete)
                tree->object.flags |= SEEN;
@@@ -496,9 -495,11 +495,9 @@@ static int parse_expire_cfg_value(cons
  {
        if (!value)
                return config_error_nonbool(var);
 -      if (!strcmp(value, "never") || !strcmp(value, "false")) {
 -              *expire = 0;
 -              return 0;
 -      }
 -      *expire = approxidate(value);
 +      if (parse_expiry_date(value, expire))
 +              return error(_("%s' for '%s' is not a valid timestamp"),
 +                           value, var);
        return 0;
  }
  
@@@ -612,13 -613,11 +611,13 @@@ static int cmd_reflog_expire(int argc, 
                if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
                        cb.dry_run = 1;
                else if (!prefixcmp(arg, "--expire=")) {
 -                      cb.expire_total = approxidate(arg + 9);
 +                      if (parse_expiry_date(arg + 9, &cb.expire_total))
 +                              die(_("'%s' is not a valid timestamp"), arg);
                        explicit_expiry |= EXPIRE_TOTAL;
                }
                else if (!prefixcmp(arg, "--expire-unreachable=")) {
 -                      cb.expire_unreachable = approxidate(arg + 21);
 +                      if (parse_expiry_date(arg + 21, &cb.expire_unreachable))
 +                              die(_("'%s' is not a valid timestamp"), arg);
                        explicit_expiry |= EXPIRE_UNREACH;
                }
                else if (!strcmp(arg, "--stale-fix"))
diff --combined http-push.c
index 6dad188b5f31d47fdce86e5104aa3c6b899ebed7,c13b44184dd94272331007ad75ed3716c12c9307..eea158a8785c4d7c87724146c141edadac0f8dfe
@@@ -663,7 -663,7 +663,7 @@@ static void add_fetch_request(struct ob
  
  static int add_send_request(struct object *obj, struct remote_lock *lock)
  {
 -      struct transfer_request *request = request_queue_head;
 +      struct transfer_request *request;
        struct packed_git *target;
  
        /* Keep locks active */
@@@ -1330,8 -1330,7 +1330,7 @@@ static struct object_list **process_tre
                        break;
                }
  
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_tree_buffer(tree);
        return p;
  }
  
diff --combined revision.c
index 6230a80a77bf261d47adcb97a8bd62bef4f37087,2190267e9a462b1912b86256ea88abf40a37abed..172b0d3b2c090c4b0bcce40b5cb19d5e544a0aed
@@@ -13,9 -13,7 +13,9 @@@
  #include "decorate.h"
  #include "log-tree.h"
  #include "string-list.h"
 +#include "line-log.h"
  #include "mailmap.h"
 +#include "commit-slab.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -72,8 -70,7 +72,8 @@@ static int show_path_truncated(FILE *ou
        return ours || emitted;
  }
  
 -void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
 +void show_object_with_name(FILE *out, struct object *obj,
 +                         const struct name_path *path, const char *component)
  {
        struct name_path leaf;
        leaf.up = (struct name_path *)path;
@@@ -90,9 -87,7 +90,9 @@@ void add_object(struct object *obj
                struct name_path *path,
                const char *name)
  {
 -      add_object_array(obj, path_name(path, name), p);
 +      char *pn = path_name(path, name);
 +      add_object_array(obj, pn, p);
 +      free(pn);
  }
  
  static void mark_blob_uninteresting(struct blob *blob)
@@@ -139,8 -134,7 +139,7 @@@ void mark_tree_uninteresting(struct tre
         * We don't care about the tree any more
         * after it has been marked uninteresting.
         */
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_tree_buffer(tree);
  }
  
  void mark_parents_uninteresting(struct commit *commit)
        }
  }
  
 -static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
 +static void add_pending_object_with_mode(struct rev_info *revs,
 +                                       struct object *obj,
 +                                       const char *name, unsigned mode)
  {
        if (!obj)
                return;
        add_object_array_with_mode(obj, name, &revs->pending, mode);
  }
  
 -void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
 +void add_pending_object(struct rev_info *revs,
 +                      struct object *obj, const char *name)
  {
        add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
  }
@@@ -234,9 -225,7 +233,9 @@@ void add_head_to_pending(struct rev_inf
        add_pending_object(revs, obj, "HEAD");
  }
  
 -static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
 +static struct object *get_reference(struct rev_info *revs, const char *name,
 +                                  const unsigned char *sha1,
 +                                  unsigned int flags)
  {
        struct object *object;
  
@@@ -257,8 -246,7 +256,8 @@@ void add_pending_sha1(struct rev_info *
        add_pending_object(revs, object, name);
  }
  
 -static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
 +static struct commit *handle_commit(struct rev_info *revs,
 +                                  struct object *object, const char *name)
  {
        unsigned long flags = object->flags;
  
@@@ -343,80 -331,6 +342,80 @@@ static int everybody_uninteresting(stru
        return 1;
  }
  
 +/*
 + * A definition of "relevant" commit that we can use to simplify limited graphs
 + * by eliminating side branches.
 + *
 + * A "relevant" commit is one that is !UNINTERESTING (ie we are including it
 + * in our list), or that is a specified BOTTOM commit. Then after computing
 + * a limited list, during processing we can generally ignore boundary merges
 + * coming from outside the graph, (ie from irrelevant parents), and treat
 + * those merges as if they were single-parent. TREESAME is defined to consider
 + * only relevant parents, if any. If we are TREESAME to our on-graph parents,
 + * we don't care if we were !TREESAME to non-graph parents.
 + *
 + * Treating bottom commits as relevant ensures that a limited graph's
 + * connection to the actual bottom commit is not viewed as a side branch, but
 + * treated as part of the graph. For example:
 + *
 + *   ....Z...A---X---o---o---B
 + *        .     /
 + *         W---Y
 + *
 + * When computing "A..B", the A-X connection is at least as important as
 + * Y-X, despite A being flagged UNINTERESTING.
 + *
 + * And when computing --ancestry-path "A..B", the A-X connection is more
 + * important than Y-X, despite both A and Y being flagged UNINTERESTING.
 + */
 +static inline int relevant_commit(struct commit *commit)
 +{
 +      return (commit->object.flags & (UNINTERESTING | BOTTOM)) != UNINTERESTING;
 +}
 +
 +/*
 + * Return a single relevant commit from a parent list. If we are a TREESAME
 + * commit, and this selects one of our parents, then we can safely simplify to
 + * that parent.
 + */
 +static struct commit *one_relevant_parent(const struct rev_info *revs,
 +                                        struct commit_list *orig)
 +{
 +      struct commit_list *list = orig;
 +      struct commit *relevant = NULL;
 +
 +      if (!orig)
 +              return NULL;
 +
 +      /*
 +       * For 1-parent commits, or if first-parent-only, then return that
 +       * first parent (even if not "relevant" by the above definition).
 +       * TREESAME will have been set purely on that parent.
 +       */
 +      if (revs->first_parent_only || !orig->next)
 +              return orig->item;
 +
 +      /*
 +       * For multi-parent commits, identify a sole relevant parent, if any.
 +       * If we have only one relevant parent, then TREESAME will be set purely
 +       * with regard to that parent, and we can simplify accordingly.
 +       *
 +       * If we have more than one relevant parent, or no relevant parents
 +       * (and multiple irrelevant ones), then we can't select a parent here
 +       * and return NULL.
 +       */
 +      while (list) {
 +              struct commit *commit = list->item;
 +              list = list->next;
 +              if (relevant_commit(commit)) {
 +                      if (relevant)
 +                              return NULL;
 +                      relevant = commit;
 +              }
 +      }
 +      return relevant;
 +}
 +
  /*
   * The goal is to get REV_TREE_NEW as the result only if the
   * diff consists of all '+' (and no other changes), REV_TREE_OLD
@@@ -453,8 -367,7 +452,8 @@@ static void file_change(struct diff_opt
        DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
 -static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct commit *commit)
 +static int rev_compare_tree(struct rev_info *revs,
 +                          struct commit *parent, struct commit *commit)
  {
        struct tree *t1 = parent->tree;
        struct tree *t2 = commit->tree;
@@@ -515,125 -428,10 +514,125 @@@ static int rev_same_tree_as_empty(struc
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
  }
  
 +struct treesame_state {
 +      unsigned int nparents;
 +      unsigned char treesame[FLEX_ARRAY];
 +};
 +
 +static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)
 +{
 +      unsigned n = commit_list_count(commit->parents);
 +      struct treesame_state *st = xcalloc(1, sizeof(*st) + n);
 +      st->nparents = n;
 +      add_decoration(&revs->treesame, &commit->object, st);
 +      return st;
 +}
 +
 +/*
 + * Must be called immediately after removing the nth_parent from a commit's
 + * parent list, if we are maintaining the per-parent treesame[] decoration.
 + * This does not recalculate the master TREESAME flag - update_treesame()
 + * should be called to update it after a sequence of treesame[] modifications
 + * that may have affected it.
 + */
 +static int compact_treesame(struct rev_info *revs, struct commit *commit, unsigned nth_parent)
 +{
 +      struct treesame_state *st;
 +      int old_same;
 +
 +      if (!commit->parents) {
 +              /*
 +               * Have just removed the only parent from a non-merge.
 +               * Different handling, as we lack decoration.
 +               */
 +              if (nth_parent != 0)
 +                      die("compact_treesame %u", nth_parent);
 +              old_same = !!(commit->object.flags & TREESAME);
 +              if (rev_same_tree_as_empty(revs, commit))
 +                      commit->object.flags |= TREESAME;
 +              else
 +                      commit->object.flags &= ~TREESAME;
 +              return old_same;
 +      }
 +
 +      st = lookup_decoration(&revs->treesame, &commit->object);
 +      if (!st || nth_parent >= st->nparents)
 +              die("compact_treesame %u", nth_parent);
 +
 +      old_same = st->treesame[nth_parent];
 +      memmove(st->treesame + nth_parent,
 +              st->treesame + nth_parent + 1,
 +              st->nparents - nth_parent - 1);
 +
 +      /*
 +       * If we've just become a non-merge commit, update TREESAME
 +       * immediately, and remove the no-longer-needed decoration.
 +       * If still a merge, defer update until update_treesame().
 +       */
 +      if (--st->nparents == 1) {
 +              if (commit->parents->next)
 +                      die("compact_treesame parents mismatch");
 +              if (st->treesame[0] && revs->dense)
 +                      commit->object.flags |= TREESAME;
 +              else
 +                      commit->object.flags &= ~TREESAME;
 +              free(add_decoration(&revs->treesame, &commit->object, NULL));
 +      }
 +
 +      return old_same;
 +}
 +
 +static unsigned update_treesame(struct rev_info *revs, struct commit *commit)
 +{
 +      if (commit->parents && commit->parents->next) {
 +              unsigned n;
 +              struct treesame_state *st;
 +              struct commit_list *p;
 +              unsigned relevant_parents;
 +              unsigned relevant_change, irrelevant_change;
 +
 +              st = lookup_decoration(&revs->treesame, &commit->object);
 +              if (!st)
 +                      die("update_treesame %s", sha1_to_hex(commit->object.sha1));
 +              relevant_parents = 0;
 +              relevant_change = irrelevant_change = 0;
 +              for (p = commit->parents, n = 0; p; n++, p = p->next) {
 +                      if (relevant_commit(p->item)) {
 +                              relevant_change |= !st->treesame[n];
 +                              relevant_parents++;
 +                      } else
 +                              irrelevant_change |= !st->treesame[n];
 +              }
 +              if (relevant_parents ? relevant_change : irrelevant_change)
 +                      commit->object.flags &= ~TREESAME;
 +              else
 +                      commit->object.flags |= TREESAME;
 +      }
 +
 +      return commit->object.flags & TREESAME;
 +}
 +
 +static inline int limiting_can_increase_treesame(const struct rev_info *revs)
 +{
 +      /*
 +       * TREESAME is irrelevant unless prune && dense;
 +       * if simplify_history is set, we can't have a mixture of TREESAME and
 +       *    !TREESAME INTERESTING parents (and we don't have treesame[]
 +       *    decoration anyway);
 +       * if first_parent_only is set, then the TREESAME flag is locked
 +       *    against the first parent (and again we lack treesame[] decoration).
 +       */
 +      return revs->prune && revs->dense &&
 +             !revs->simplify_history &&
 +             !revs->first_parent_only;
 +}
 +
  static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
  {
        struct commit_list **pp, *parent;
 -      int tree_changed = 0, tree_same = 0, nth_parent = 0;
 +      struct treesame_state *ts = NULL;
 +      int relevant_change = 0, irrelevant_change = 0;
 +      int relevant_parents, nth_parent;
  
        /*
         * If we don't do pruning, everything is interesting
        if (!revs->dense && !commit->parents->next)
                return;
  
 -      pp = &commit->parents;
 -      while ((parent = *pp) != NULL) {
 +      for (pp = &commit->parents, nth_parent = 0, relevant_parents = 0;
 +           (parent = *pp) != NULL;
 +           pp = &parent->next, nth_parent++) {
                struct commit *p = parent->item;
 +              if (relevant_commit(p))
 +                      relevant_parents++;
  
 -              /*
 -               * Do not compare with later parents when we care only about
 -               * the first parent chain, in order to avoid derailing the
 -               * traversal to follow a side branch that brought everything
 -               * in the path we are limited to by the pathspec.
 -               */
 -              if (revs->first_parent_only && nth_parent++)
 -                      break;
 +              if (nth_parent == 1) {
 +                      /*
 +                       * This our second loop iteration - so we now know
 +                       * we're dealing with a merge.
 +                       *
 +                       * Do not compare with later parents when we care only about
 +                       * the first parent chain, in order to avoid derailing the
 +                       * traversal to follow a side branch that brought everything
 +                       * in the path we are limited to by the pathspec.
 +                       */
 +                      if (revs->first_parent_only)
 +                              break;
 +                      /*
 +                       * If this will remain a potentially-simplifiable
 +                       * merge, remember per-parent treesame if needed.
 +                       * Initialise the array with the comparison from our
 +                       * first iteration.
 +                       */
 +                      if (revs->treesame.name &&
 +                          !revs->simplify_history &&
 +                          !(commit->object.flags & UNINTERESTING)) {
 +                              ts = initialise_treesame(revs, commit);
 +                              if (!(irrelevant_change || relevant_change))
 +                                      ts->treesame[0] = 1;
 +                      }
 +              }
                if (parse_commit(p) < 0)
                        die("cannot simplify commit %s (because of %s)",
                            sha1_to_hex(commit->object.sha1),
                            sha1_to_hex(p->object.sha1));
                switch (rev_compare_tree(revs, p, commit)) {
                case REV_TREE_SAME:
 -                      tree_same = 1;
 -                      if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
 +                      if (!revs->simplify_history || !relevant_commit(p)) {
                                /* Even if a merge with an uninteresting
                                 * side branch brought the entire change
                                 * we are interested in, we do not want
                                 * to lose the other branches of this
                                 * merge, so we just keep going.
                                 */
 -                              pp = &parent->next;
 +                              if (ts)
 +                                      ts->treesame[nth_parent] = 1;
                                continue;
                        }
                        parent->next = NULL;
                /* fallthrough */
                case REV_TREE_OLD:
                case REV_TREE_DIFFERENT:
 -                      tree_changed = 1;
 -                      pp = &parent->next;
 +                      if (relevant_commit(p))
 +                              relevant_change = 1;
 +                      else
 +                              irrelevant_change = 1;
                        continue;
                }
                die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
 -      if (tree_changed && !tree_same)
 -              return;
 -      commit->object.flags |= TREESAME;
 +
 +      /*
 +       * TREESAME is straightforward for single-parent commits. For merge
 +       * commits, it is most useful to define it so that "irrelevant"
 +       * parents cannot make us !TREESAME - if we have any relevant
 +       * parents, then we only consider TREESAMEness with respect to them,
 +       * allowing irrelevant merges from uninteresting branches to be
 +       * simplified away. Only if we have only irrelevant parents do we
 +       * base TREESAME on them. Note that this logic is replicated in
 +       * update_treesame, which should be kept in sync.
 +       */
 +      if (relevant_parents ? !relevant_change : !irrelevant_change)
 +              commit->object.flags |= TREESAME;
  }
  
  static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
@@@ -1035,12 -800,16 +1034,12 @@@ static void limit_to_ancestry(struct co
   * to filter the result of "A..B" further to the ones that can actually
   * reach A.
   */
 -static struct commit_list *collect_bottom_commits(struct rev_info *revs)
 +static struct commit_list *collect_bottom_commits(struct commit_list *list)
  {
 -      struct commit_list *bottom = NULL;
 -      int i;
 -      for (i = 0; i < revs->cmdline.nr; i++) {
 -              struct rev_cmdline_entry *elem = &revs->cmdline.rev[i];
 -              if ((elem->flags & UNINTERESTING) &&
 -                  elem->item->type == OBJ_COMMIT)
 -                      commit_list_insert((struct commit *)elem->item, &bottom);
 -      }
 +      struct commit_list *elem, *bottom = NULL;
 +      for (elem = list; elem; elem = elem->next)
 +              if (elem->item->object.flags & BOTTOM)
 +                      commit_list_insert(elem->item, &bottom);
        return bottom;
  }
  
@@@ -1071,7 -840,7 +1070,7 @@@ static int limit_list(struct rev_info *
        struct commit_list *bottom = NULL;
  
        if (revs->ancestry_path) {
 -              bottom = collect_bottom_commits(revs);
 +              bottom = collect_bottom_commits(list);
                if (!bottom)
                        die("--ancestry-path given but there are no bottom commits");
        }
                free_commit_list(bottom);
        }
  
 +      /*
 +       * Check if any commits have become TREESAME by some of their parents
 +       * becoming UNINTERESTING.
 +       */
 +      if (limiting_can_increase_treesame(revs))
 +              for (list = newlist; list; list = list->next) {
 +                      struct commit *c = list->item;
 +                      if (c->object.flags & (UNINTERESTING | TREESAME))
 +                              continue;
 +                      update_treesame(revs, c);
 +              }
 +
        revs->commits = newlist;
        return 0;
  }
  
 +/*
 + * Add an entry to refs->cmdline with the specified information.
 + * *name is copied.
 + */
  static void add_rev_cmdline(struct rev_info *revs,
                            struct object *item,
                            const char *name,
  
        ALLOC_GROW(info->rev, nr + 1, info->alloc);
        info->rev[nr].item = item;
 -      info->rev[nr].name = name;
 +      info->rev[nr].name = xstrdup(name);
        info->rev[nr].whence = whence;
        info->rev[nr].flags = flags;
        info->nr++;
  }
  
 +static void add_rev_cmdline_list(struct rev_info *revs,
 +                               struct commit_list *commit_list,
 +                               int whence,
 +                               unsigned flags)
 +{
 +      while (commit_list) {
 +              struct object *object = &commit_list->item->object;
 +              add_rev_cmdline(revs, object, sha1_to_hex(object->sha1),
 +                              whence, flags);
 +              commit_list = commit_list->next;
 +      }
 +}
 +
  struct all_refs_cb {
        int all_flags;
        int warned_bad_reflog;
@@@ -1259,7 -999,7 +1258,7 @@@ static int add_parents_only(struct rev_
        const char *arg = arg_;
  
        if (*arg == '^') {
 -              flags ^= UNINTERESTING;
 +              flags ^= UNINTERESTING | BOTTOM;
                arg++;
        }
        if (get_sha1_committish(arg, sha1))
@@@ -1297,7 -1037,7 +1296,7 @@@ void init_revisions(struct rev_info *re
        DIFF_OPT_SET(&revs->pruning, QUICK);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
 -      revs->lifo = 1;
 +      revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
        revs->dense = 1;
        revs->prefix = prefix;
        revs->max_age = -1;
@@@ -1351,15 -1091,14 +1350,15 @@@ static void prepare_show_merge(struct r
        add_pending_object(revs, &head->object, "HEAD");
        add_pending_object(revs, &other->object, "MERGE_HEAD");
        bases = get_merge_bases(head, other, 1);
 -      add_pending_commit_list(revs, bases, UNINTERESTING);
 +      add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);
 +      add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);
        free_commit_list(bases);
        head->object.flags |= SYMMETRIC_LEFT;
  
        if (!active_nr)
                read_cache();
        for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
                if (ce_path_match(ce, &revs->prune_data)) {
                        i++;
        }
        free_pathspec(&revs->prune_data);
 -      init_pathspec(&revs->prune_data, prune);
 +      parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC, 0, "", prune);
        revs->limited = 1;
  }
  
@@@ -1388,15 -1127,13 +1387,15 @@@ int handle_revision_arg(const char *arg
        int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
        unsigned get_sha1_flags = 0;
  
 +      flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
 +
        dotdot = strstr(arg, "..");
        if (dotdot) {
                unsigned char from_sha1[20];
                const char *next = dotdot + 2;
                const char *this = arg;
                int symmetric = *next == '.';
 -              unsigned int flags_exclude = flags ^ UNINTERESTING;
 +              unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
                static const char head_by_default[] = "HEAD";
                unsigned int a_flags;
  
  
                        if (symmetric) {
                                exclude = get_merge_bases(a, b, 1);
 +                              add_rev_cmdline_list(revs, exclude,
 +                                                   REV_CMD_MERGE_BASE,
 +                                                   flags_exclude);
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
        dotdot = strstr(arg, "^!");
        if (dotdot && !dotdot[2]) {
                *dotdot = 0;
 -              if (!add_parents_only(revs, arg, flags ^ UNINTERESTING))
 +              if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))
                        *dotdot = '^';
        }
  
        local_flags = 0;
        if (*arg == '^') {
 -              local_flags = UNINTERESTING;
 +              local_flags = UNINTERESTING | BOTTOM;
                arg++;
        }
  
@@@ -1541,7 -1275,7 +1540,7 @@@ static void read_revisions_from_stdin(s
                        }
                        die("options not supported in --stdin mode");
                }
 -              if (handle_revision_arg(xstrdup(sb.buf), revs, 0,
 +              if (handle_revision_arg(sb.buf, revs, 0,
                                        REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
@@@ -1639,7 -1373,7 +1638,7 @@@ static int handle_revision_opt(struct r
        } else if (!strcmp(arg, "--merge")) {
                revs->show_merge = 1;
        } else if (!strcmp(arg, "--topo-order")) {
 -              revs->lifo = 1;
 +              revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
                revs->topo_order = 1;
        } else if (!strcmp(arg, "--simplify-merges")) {
                revs->simplify_merges = 1;
                revs->prune = 1;
                load_ref_decorations(DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
 -              revs->lifo = 0;
 +              revs->sort_order = REV_SORT_BY_COMMIT_DATE;
 +              revs->topo_order = 1;
 +      } else if (!strcmp(arg, "--author-date-order")) {
 +              revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
                revs->topo_order = 1;
        } else if (!prefixcmp(arg, "--early-output")) {
                int count = 100;
@@@ -1958,7 -1689,7 +1957,7 @@@ static int handle_revision_pseudo_opt(c
                handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
        } else if (!strcmp(arg, "--bisect")) {
                handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
 -              handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);
 +              handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
                revs->bisect = 1;
        } else if (!strcmp(arg, "--tags")) {
                handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
        } else if (!strcmp(arg, "--reflog")) {
                handle_reflog(revs, *flags);
        } else if (!strcmp(arg, "--not")) {
 -              *flags ^= UNINTERESTING;
 +              *flags ^= UNINTERESTING | BOTTOM;
        } else if (!strcmp(arg, "--no-walk")) {
                revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
        } else if (!prefixcmp(arg, "--no-walk=")) {
@@@ -2121,8 -1852,8 +2120,8 @@@ int setup_revisions(int argc, const cha
                 */
                ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
                prune_data.path[prune_data.nr++] = NULL;
 -              init_pathspec(&revs->prune_data,
 -                            get_pathspec(revs->prefix, prune_data.path));
 +              parse_pathspec(&revs->prune_data, 0, 0,
 +                             revs->prefix, prune_data.path);
        }
  
        if (revs->def == NULL)
                revs->limited = 1;
  
        if (revs->prune_data.nr) {
 -              diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
 +              copy_pathspec(&revs->pruning.pathspec, &revs->prune_data);
                /* Can't prune commits with rename following: the paths change.. */
                if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
                        revs->prune = 1;
                if (!revs->full_diff)
 -                      diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
 +                      copy_pathspec(&revs->diffopt.pathspec,
 +                                    &revs->prune_data);
        }
        if (revs->combine_merges)
                revs->ignore_merges = 0;
        revs->diffopt.abbrev = revs->abbrev;
 +
 +      if (revs->line_level_traverse) {
 +              revs->limited = 1;
 +              revs->topo_order = 1;
 +      }
 +
        diff_setup_done(&revs->diffopt);
  
        grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
@@@ -2205,32 -1929,28 +2204,32 @@@ static void add_child(struct rev_info *
        l->next = add_decoration(&revs->children, &parent->object, l);
  }
  
 -static int remove_duplicate_parents(struct commit *commit)
 +static int remove_duplicate_parents(struct rev_info *revs, struct commit *commit)
  {
 +      struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
        struct commit_list **pp, *p;
        int surviving_parents;
  
        /* Examine existing parents while marking ones we have seen... */
        pp = &commit->parents;
 +      surviving_parents = 0;
        while ((p = *pp) != NULL) {
                struct commit *parent = p->item;
                if (parent->object.flags & TMP_MARK) {
                        *pp = p->next;
 +                      if (ts)
 +                              compact_treesame(revs, commit, surviving_parents);
                        continue;
                }
                parent->object.flags |= TMP_MARK;
 +              surviving_parents++;
                pp = &p->next;
        }
 -      /* count them while clearing the temporary mark */
 -      surviving_parents = 0;
 +      /* clear the temporary mark */
        for (p = commit->parents; p; p = p->next) {
                p->item->object.flags &= ~TMP_MARK;
 -              surviving_parents++;
        }
 +      /* no update_treesame() - removing duplicates can't affect TREESAME */
        return surviving_parents;
  }
  
@@@ -2250,157 -1970,9 +2249,157 @@@ static struct merge_simplify_state *loc
        return st;
  }
  
 +static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list *h = reduce_heads(commit->parents);
 +      int i = 0, marked = 0;
 +      struct commit_list *po, *pn;
 +
 +      /* Want these for sanity-checking only */
 +      int orig_cnt = commit_list_count(commit->parents);
 +      int cnt = commit_list_count(h);
 +
 +      /*
 +       * Not ready to remove items yet, just mark them for now, based
 +       * on the output of reduce_heads(). reduce_heads outputs the reduced
 +       * set in its original order, so this isn't too hard.
 +       */
 +      po = commit->parents;
 +      pn = h;
 +      while (po) {
 +              if (pn && po->item == pn->item) {
 +                      pn = pn->next;
 +                      i++;
 +              } else {
 +                      po->item->object.flags |= TMP_MARK;
 +                      marked++;
 +              }
 +              po=po->next;
 +      }
 +
 +      if (i != cnt || cnt+marked != orig_cnt)
 +              die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked);
 +
 +      free_commit_list(h);
 +
 +      return marked;
 +}
 +
 +static int mark_treesame_root_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list *p;
 +      int marked = 0;
 +
 +      for (p = commit->parents; p; p = p->next) {
 +              struct commit *parent = p->item;
 +              if (!parent->parents && (parent->object.flags & TREESAME)) {
 +                      parent->object.flags |= TMP_MARK;
 +                      marked++;
 +              }
 +      }
 +
 +      return marked;
 +}
 +
 +/*
 + * Awkward naming - this means one parent we are TREESAME to.
 + * cf mark_treesame_root_parents: root parents that are TREESAME (to an
 + * empty tree). Better name suggestions?
 + */
 +static int leave_one_treesame_to_parent(struct rev_info *revs, struct commit *commit)
 +{
 +      struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
 +      struct commit *unmarked = NULL, *marked = NULL;
 +      struct commit_list *p;
 +      unsigned n;
 +
 +      for (p = commit->parents, n = 0; p; p = p->next, n++) {
 +              if (ts->treesame[n]) {
 +                      if (p->item->object.flags & TMP_MARK) {
 +                              if (!marked)
 +                                      marked = p->item;
 +                      } else {
 +                              if (!unmarked) {
 +                                      unmarked = p->item;
 +                                      break;
 +                              }
 +                      }
 +              }
 +      }
 +
 +      /*
 +       * If we are TREESAME to a marked-for-deletion parent, but not to any
 +       * unmarked parents, unmark the first TREESAME parent. This is the
 +       * parent that the default simplify_history==1 scan would have followed,
 +       * and it doesn't make sense to omit that path when asking for a
 +       * simplified full history. Retaining it improves the chances of
 +       * understanding odd missed merges that took an old version of a file.
 +       *
 +       * Example:
 +       *
 +       *   I--------*X       A modified the file, but mainline merge X used
 +       *    \       /        "-s ours", so took the version from I. X is
 +       *     `-*A--'         TREESAME to I and !TREESAME to A.
 +       *
 +       * Default log from X would produce "I". Without this check,
 +       * --full-history --simplify-merges would produce "I-A-X", showing
 +       * the merge commit X and that it changed A, but not making clear that
 +       * it had just taken the I version. With this check, the topology above
 +       * is retained.
 +       *
 +       * Note that it is possible that the simplification chooses a different
 +       * TREESAME parent from the default, in which case this test doesn't
 +       * activate, and we _do_ drop the default parent. Example:
 +       *
 +       *   I------X         A modified the file, but it was reverted in B,
 +       *    \    /          meaning mainline merge X is TREESAME to both
 +       *    *A-*B           parents.
 +       *
 +       * Default log would produce "I" by following the first parent;
 +       * --full-history --simplify-merges will produce "I-A-B". But this is a
 +       * reasonable result - it presents a logical full history leading from
 +       * I to X, and X is not an important merge.
 +       */
 +      if (!unmarked && marked) {
 +              marked->object.flags &= ~TMP_MARK;
 +              return 1;
 +      }
 +
 +      return 0;
 +}
 +
 +static int remove_marked_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list **pp, *p;
 +      int nth_parent, removed = 0;
 +
 +      pp = &commit->parents;
 +      nth_parent = 0;
 +      while ((p = *pp) != NULL) {
 +              struct commit *parent = p->item;
 +              if (parent->object.flags & TMP_MARK) {
 +                      parent->object.flags &= ~TMP_MARK;
 +                      *pp = p->next;
 +                      free(p);
 +                      removed++;
 +                      compact_treesame(revs, commit, nth_parent);
 +                      continue;
 +              }
 +              pp = &p->next;
 +              nth_parent++;
 +      }
 +
 +      /* Removing parents can only increase TREESAMEness */
 +      if (removed && !(commit->object.flags & TREESAME))
 +              update_treesame(revs, commit);
 +
 +      return nth_parent;
 +}
 +
  static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
  {
        struct commit_list *p;
 +      struct commit *parent;
        struct merge_simplify_state *st, *pst;
        int cnt;
  
        }
  
        /*
 -       * Rewrite our list of parents.
 +       * Rewrite our list of parents. Note that this cannot
 +       * affect our TREESAME flags in any way - a commit is
 +       * always TREESAME to its simplification.
         */
        for (p = commit->parents; p; p = p->next) {
                pst = locate_simplify_state(revs, p->item);
        if (revs->first_parent_only)
                cnt = 1;
        else
 -              cnt = remove_duplicate_parents(commit);
 +              cnt = remove_duplicate_parents(revs, commit);
  
        /*
         * It is possible that we are a merge and one side branch
         * does not have any commit that touches the given paths;
 -       * in such a case, the immediate parents will be rewritten
 -       * to different commits.
 +       * in such a case, the immediate parent from that branch
 +       * will be rewritten to be the merge base.
         *
         *      o----X          X: the commit we are looking at;
         *     /    /           o: a commit that touches the paths;
         * ---o----'
         *
 -       * Further reduce the parents by removing redundant parents.
 +       * Further, a merge of an independent branch that doesn't
 +       * touch the path will reduce to a treesame root parent:
 +       *
 +       *  ----o----X          X: the commit we are looking at;
 +       *          /           o: a commit that touches the paths;
 +       *         r            r: a root commit not touching the paths
 +       *
 +       * Detect and simplify both cases.
         */
        if (1 < cnt) {
 -              struct commit_list *h = reduce_heads(commit->parents);
 -              cnt = commit_list_count(h);
 -              free_commit_list(commit->parents);
 -              commit->parents = h;
 +              int marked = mark_redundant_parents(revs, commit);
 +              marked += mark_treesame_root_parents(revs, commit);
 +              if (marked)
 +                      marked -= leave_one_treesame_to_parent(revs, commit);
 +              if (marked)
 +                      cnt = remove_marked_parents(revs, commit);
        }
  
        /*
         * A commit simplifies to itself if it is a root, if it is
         * UNINTERESTING, if it touches the given paths, or if it is a
 -       * merge and its parents simplifies to more than one commits
 +       * merge and its parents don't simplify to one relevant commit
         * (the first two cases are already handled at the beginning of
         * this function).
         *
 -       * Otherwise, it simplifies to what its sole parent simplifies to.
 +       * Otherwise, it simplifies to what its sole relevant parent
 +       * simplifies to.
         */
        if (!cnt ||
            (commit->object.flags & UNINTERESTING) ||
            !(commit->object.flags & TREESAME) ||
 -          (1 < cnt))
 +          (parent = one_relevant_parent(revs, commit->parents)) == NULL)
                st->simplified = commit;
        else {
 -              pst = locate_simplify_state(revs, commit->parents->item);
 +              pst = locate_simplify_state(revs, parent);
                st->simplified = pst->simplified;
        }
        return tail;
@@@ -2598,11 -2158,6 +2597,11 @@@ int prepare_revision_walk(struct rev_in
        if (!revs->leak_pending)
                free(list);
  
 +      /* Signal whether we need per-parent treesame decoration */
 +      if (revs->simplify_merges ||
 +          (revs->limited && limiting_can_increase_treesame(revs)))
 +              revs->treesame.name = "treesame";
 +
        if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
                commit_list_sort_by_date(&revs->commits);
        if (revs->no_walk)
                if (limit_list(revs) < 0)
                        return -1;
        if (revs->topo_order)
 -              sort_in_topological_order(&revs->commits, revs->lifo);
 +              sort_in_topological_order(&revs->commits, revs->sort_order);
 +      if (revs->line_level_traverse)
 +              line_log_filter(revs);
        if (revs->simplify_merges)
                simplify_merges(revs);
        if (revs->children.name)
        return 0;
  }
  
 -enum rewrite_result {
 -      rewrite_one_ok,
 -      rewrite_one_noparents,
 -      rewrite_one_error
 -};
 -
  static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
  {
        struct commit_list *cache = NULL;
                if (!revs->limited)
                        if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
                                return rewrite_one_error;
 -              if (p->parents && p->parents->next)
 -                      return rewrite_one_ok;
                if (p->object.flags & UNINTERESTING)
                        return rewrite_one_ok;
                if (!(p->object.flags & TREESAME))
                        return rewrite_one_ok;
                if (!p->parents)
                        return rewrite_one_noparents;
 -              *pp = p->parents->item;
 +              if ((p = one_relevant_parent(revs, p->parents)) == NULL)
 +                      return rewrite_one_ok;
 +              *pp = p;
        }
  }
  
 -static int rewrite_parents(struct rev_info *revs, struct commit *commit)
 +int rewrite_parents(struct rev_info *revs, struct commit *commit,
 +      rewrite_parent_fn_t rewrite_parent)
  {
        struct commit_list **pp = &commit->parents;
        while (*pp) {
                struct commit_list *parent = *pp;
 -              switch (rewrite_one(revs, &parent->item)) {
 +              switch (rewrite_parent(revs, &parent->item)) {
                case rewrite_one_ok:
                        break;
                case rewrite_one_noparents:
                }
                pp = &parent->next;
        }
 -      remove_duplicate_parents(commit);
 +      remove_duplicate_parents(revs, commit);
        return 0;
  }
  
@@@ -2765,7 -2323,7 +2764,7 @@@ static int commit_match(struct commit *
        return retval;
  }
  
 -static inline int want_ancestry(struct rev_info *revs)
 +static inline int want_ancestry(const struct rev_info *revs)
  {
        return (revs->rewrite_parents || revs->children.name);
  }
@@@ -2783,7 -2341,10 +2782,7 @@@ enum commit_action get_commit_action(st
        if (revs->min_age != -1 && (commit->date > revs->min_age))
                return commit_ignore;
        if (revs->min_parents || (revs->max_parents >= 0)) {
 -              int n = 0;
 -              struct commit_list *p;
 -              for (p = commit->parents; p; p = p->next)
 -                      n++;
 +              int n = commit_list_count(commit->parents);
                if ((n < revs->min_parents) ||
                    ((revs->max_parents >= 0) && (n > revs->max_parents)))
                        return commit_ignore;
        if (revs->prune && revs->dense) {
                /* Commit without changes? */
                if (commit->object.flags & TREESAME) {
 +                      int n;
 +                      struct commit_list *p;
                        /* drop merges unless we want parenthood */
                        if (!want_ancestry(revs))
                                return commit_ignore;
 -                      /* non-merge - always ignore it */
 -                      if (!commit->parents || !commit->parents->next)
 -                              return commit_ignore;
 +                      /*
 +                       * If we want ancestry, then need to keep any merges
 +                       * between relevant commits to tie together topology.
 +                       * For consistency with TREESAME and simplification
 +                       * use "relevant" here rather than just INTERESTING,
 +                       * to treat bottom commit(s) as part of the topology.
 +                       */
 +                      for (n = 0, p = commit->parents; p; p = p->next)
 +                              if (relevant_commit(p->item))
 +                                      if (++n >= 2)
 +                                              return commit_show;
 +                      return commit_ignore;
                }
        }
        return commit_show;
@@@ -2822,15 -2372,7 +2821,15 @@@ enum commit_action simplify_commit(stru
        if (action == commit_show &&
            !revs->show_all &&
            revs->prune && revs->dense && want_ancestry(revs)) {
 -              if (rewrite_parents(revs, commit) < 0)
 +              /*
 +               * --full-diff on simplified parents is no good: it
 +               * will show spurious changes from the commits that
 +               * were elided.  So we save the parents on the side
 +               * when --full-diff is in effect.
 +               */
 +              if (revs->full_diff)
 +                      save_parents(revs, commit);
 +              if (rewrite_parents(revs, commit, rewrite_one) < 0)
                        return commit_error;
        }
        return action;
@@@ -2849,7 -2391,6 +2848,7 @@@ static struct commit *get_revision_1(st
                free(entry);
  
                if (revs->reflog_info) {
 +                      save_parents(revs, commit);
                        fake_reflog_parent(revs->reflog_info, commit);
                        commit->object.flags &= ~(ADDED | SEEN | SHOWN);
                }
        return NULL;
  }
  
 -static void gc_boundary(struct object_array *array)
 +/*
 + * Return true for entries that have not yet been shown.  (This is an
 + * object_array_each_func_t.)
 + */
 +static int entry_unshown(struct object_array_entry *entry, void *cb_data_unused)
  {
 -      unsigned nr = array->nr;
 -      unsigned alloc = array->alloc;
 -      struct object_array_entry *objects = array->objects;
 +      return !(entry->item->flags & SHOWN);
 +}
  
 -      if (alloc <= nr) {
 -              unsigned i, j;
 -              for (i = j = 0; i < nr; i++) {
 -                      if (objects[i].item->flags & SHOWN)
 -                              continue;
 -                      if (i != j)
 -                              objects[j] = objects[i];
 -                      j++;
 -              }
 -              for (i = j; i < nr; i++)
 -                      objects[i].item = NULL;
 -              array->nr = j;
 -      }
 +/*
 + * If array is on the verge of a realloc, garbage-collect any entries
 + * that have already been shown to try to free up some space.
 + */
 +static void gc_boundary(struct object_array *array)
 +{
 +      if (array->nr == array->alloc)
 +              object_array_filter(array, entry_unshown, NULL);
  }
  
  static void create_boundary_commit_list(struct rev_info *revs)
         * If revs->topo_order is set, sort the boundary commits
         * in topological order
         */
 -      sort_in_topological_order(&revs->commits, revs->lifo);
 +      sort_in_topological_order(&revs->commits, revs->sort_order);
  }
  
  static struct commit *get_revision_internal(struct rev_info *revs)
@@@ -3049,8 -2592,6 +3048,8 @@@ struct commit *get_revision(struct rev_
        c = get_revision_internal(revs);
        if (c && revs->graph)
                graph_update(revs->graph, c);
 +      if (!c)
 +              free_saved_parents(revs);
        return c;
  }
  
@@@ -3082,54 -2623,3 +3081,54 @@@ 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);
 +}
diff --combined tree.c
index 549e5883a01bb31010861aad991993772d238309,1cbf60ea2377e588a63bd7980163b9a24daf8bac..c8c49d7b78174199da94d802e1ca7037866b5f04
--- 1/tree.c
--- 2/tree.c
+++ b/tree.c
@@@ -47,7 -47,7 +47,7 @@@ static int read_one_entry_quick(const u
  }
  
  static int read_tree_1(struct tree *tree, struct strbuf *base,
 -                     int stage, struct pathspec *pathspec,
 +                     int stage, const struct pathspec *pathspec,
                       read_tree_fn_t fn, void *context)
  {
        struct tree_desc desc;
  
  int read_tree_recursive(struct tree *tree,
                        const char *base, int baselen,
 -                      int stage, struct pathspec *pathspec,
 +                      int stage, const struct pathspec *pathspec,
                        read_tree_fn_t fn, void *context)
  {
        struct strbuf sb = STRBUF_INIT;
@@@ -159,7 -159,7 +159,7 @@@ int read_tree(struct tree *tree, int st
         * sort at the end.
         */
        for (i = 0; !fn && i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce) == stage)
                        fn = read_one_entry;
        }
@@@ -225,6 -225,14 +225,14 @@@ int parse_tree(struct tree *item
        return parse_tree_buffer(item, buffer, size);
  }
  
+ void free_tree_buffer(struct tree *tree)
+ {
+       free(tree->buffer);
+       tree->buffer = NULL;
+       tree->size = 0;
+       tree->object.parsed = 0;
+ }
  struct tree *parse_tree_indirect(const unsigned char *sha1)
  {
        struct object *obj = parse_object(sha1);
diff --combined tree.h
index 9dc90bac38dff0970840e507af980092af78d31d,601ab9c50d166d51e20322a5012b106514f90a71..d84ac63e511c30ae4e1c8ee2b9719d67fd9424da
--- 1/tree.h
--- 2/tree.h
+++ b/tree.h
@@@ -16,6 -16,7 +16,7 @@@ struct tree *lookup_tree(const unsigne
  int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
  
  int parse_tree(struct tree *tree);
+ void free_tree_buffer(struct tree *tree);
  
  /* Parses and returns the tree in the given ent, chasing tags and commits. */
  struct tree *parse_tree_indirect(const unsigned char *sha1);
@@@ -25,7 -26,7 +26,7 @@@ typedef int (*read_tree_fn_t)(const uns
  
  extern int read_tree_recursive(struct tree *tree,
                               const char *base, int baselen,
 -                             int stage, struct pathspec *pathspec,
 +                             int stage, const struct pathspec *pathspec,
                               read_tree_fn_t fn, void *context);
  
  extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec);