Merge branch 'jk/report-fail-to-read-objects-better'
[gitweb.git] / revision.c
index 9daaf35801106ea33547b5389953890fa76bbaa1..3818b4628d0b5b4fa74428decaa940bef2804856 100644 (file)
@@ -15,6 +15,8 @@
 #include "string-list.h"
 #include "line-log.h"
 #include "mailmap.h"
+#include "commit-slab.h"
+#include "dir.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -103,17 +105,12 @@ static void mark_blob_uninteresting(struct blob *blob)
        blob->object.flags |= UNINTERESTING;
 }
 
-void mark_tree_uninteresting(struct tree *tree)
+static void mark_tree_contents_uninteresting(struct tree *tree)
 {
        struct tree_desc desc;
        struct name_entry entry;
        struct object *obj = &tree->object;
 
-       if (!tree)
-               return;
-       if (obj->flags & UNINTERESTING)
-               return;
-       obj->flags |= UNINTERESTING;
        if (!has_sha1_file(obj->sha1))
                return;
        if (parse_tree(tree) < 0)
@@ -138,8 +135,19 @@ void mark_tree_uninteresting(struct tree *tree)
         * 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_tree_uninteresting(struct tree *tree)
+{
+       struct object *obj = &tree->object;
+
+       if (!tree)
+               return;
+       if (obj->flags & UNINTERESTING)
+               return;
+       obj->flags |= UNINTERESTING;
+       mark_tree_contents_uninteresting(tree);
 }
 
 void mark_parents_uninteresting(struct commit *commit)
@@ -200,7 +208,7 @@ static void add_pending_object_with_mode(struct rev_info *revs,
                revs->no_walk = 0;
        if (revs->reflog_info && obj->type == OBJ_COMMIT) {
                struct strbuf buf = STRBUF_INIT;
-               int len = interpret_branch_name(name, &buf);
+               int len = interpret_branch_name(name, 0, &buf);
                int st;
 
                if (0 < len && name[len] && buf.len)
@@ -276,6 +284,7 @@ static struct commit *handle_commit(struct rev_info *revs,
                                return NULL;
                        die("bad object %s", sha1_to_hex(tag->tagged->sha1));
                }
+               object->flags |= flags;
        }
 
        /*
@@ -287,7 +296,6 @@ static struct commit *handle_commit(struct rev_info *revs,
                if (parse_commit(commit) < 0)
                        die("unable to parse commit %s", name);
                if (flags & UNINTERESTING) {
-                       commit->object.flags |= UNINTERESTING;
                        mark_parents_uninteresting(commit);
                        revs->limited = 1;
                }
@@ -305,7 +313,7 @@ static struct commit *handle_commit(struct rev_info *revs,
                if (!revs->tree_objects)
                        return NULL;
                if (flags & UNINTERESTING) {
-                       mark_tree_uninteresting(tree);
+                       mark_tree_contents_uninteresting(tree);
                        return NULL;
                }
                add_pending_object(revs, object, "");
@@ -316,13 +324,10 @@ static struct commit *handle_commit(struct rev_info *revs,
         * Blob object? You know the drill by now..
         */
        if (object->type == OBJ_BLOB) {
-               struct blob *blob = (struct blob *)object;
                if (!revs->blob_objects)
                        return NULL;
-               if (flags & UNINTERESTING) {
-                       mark_blob_uninteresting(blob);
+               if (flags & UNINTERESTING)
                        return NULL;
-               }
                add_pending_object(revs, object, "");
                return NULL;
        }
@@ -492,24 +497,14 @@ static int rev_compare_tree(struct rev_info *revs,
 static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
 {
        int retval;
-       void *tree;
-       unsigned long size;
-       struct tree_desc empty, real;
        struct tree *t1 = commit->tree;
 
        if (!t1)
                return 0;
 
-       tree = read_object_with_reference(t1->object.sha1, tree_type, &size, NULL);
-       if (!tree)
-               return 0;
-       init_tree_desc(&real, tree, size);
-       init_tree_desc(&empty, "", 0);
-
        tree_difference = REV_TREE_SAME;
        DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
-       retval = diff_tree(&empty, &real, "", &revs->pruning);
-       free(tree);
+       retval = diff_tree_sha1(NULL, t1->object.sha1, "", &revs->pruning);
 
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
 }
@@ -779,6 +774,10 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
                return 0;
        commit->object.flags |= ADDED;
 
+       if (revs->include_check &&
+           !revs->include_check(commit, revs->include_check_data))
+               return 0;
+
        /*
         * If the commit is uninteresting, don't try to
         * prune parents - we want the maximal uninteresting
@@ -1180,14 +1179,14 @@ struct all_refs_cb {
        const char *name_for_errormsg;
 };
 
-static int ref_excluded(struct rev_info *revs, const char *path)
+int ref_excluded(struct string_list *ref_excludes, const char *path)
 {
        struct string_list_item *item;
 
-       if (!revs->ref_excludes)
+       if (!ref_excludes)
                return 0;
-       for_each_string_list_item(item, revs->ref_excludes) {
-               if (!fnmatch(item->string, path, 0))
+       for_each_string_list_item(item, ref_excludes) {
+               if (!wildmatch(item->string, path, 0, NULL))
                        return 1;
        }
        return 0;
@@ -1198,7 +1197,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        struct all_refs_cb *cb = cb_data;
        struct object *object;
 
-       if (ref_excluded(cb->all_revs, path))
+       if (ref_excluded(cb->all_revs->ref_excludes, path))
            return 0;
 
        object = get_reference(cb->all_revs, path, sha1, cb->all_flags);
@@ -1214,22 +1213,22 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
        cb->all_flags = flags;
 }
 
-static void clear_ref_exclusion(struct rev_info *revs)
+void clear_ref_exclusion(struct string_list **ref_excludes_p)
 {
-       if (revs->ref_excludes) {
-               string_list_clear(revs->ref_excludes, 0);
-               free(revs->ref_excludes);
+       if (*ref_excludes_p) {
+               string_list_clear(*ref_excludes_p, 0);
+               free(*ref_excludes_p);
        }
-       revs->ref_excludes = NULL;
+       *ref_excludes_p = NULL;
 }
 
-static void add_ref_exclusion(struct rev_info *revs, const char *exclude)
+void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude)
 {
-       if (!revs->ref_excludes) {
-               revs->ref_excludes = xcalloc(1, sizeof(*revs->ref_excludes));
-               revs->ref_excludes->strdup_strings = 1;
+       if (!*ref_excludes_p) {
+               *ref_excludes_p = xcalloc(1, sizeof(**ref_excludes_p));
+               (*ref_excludes_p)->strdup_strings = 1;
        }
-       string_list_append(revs->ref_excludes, exclude);
+       string_list_append(*ref_excludes_p, exclude);
 }
 
 static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags,
@@ -1396,7 +1395,7 @@ static void prepare_show_merge(struct rev_info *revs)
                const struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
-               if (ce_path_match(ce, &revs->prune_data)) {
+               if (ce_path_match(ce, &revs->prune_data, NULL)) {
                        prune_num++;
                        prune = xrealloc(prune, sizeof(*prune) * prune_num);
                        prune[prune_num-2] = ce->name;
@@ -1407,7 +1406,8 @@ static void prepare_show_merge(struct rev_info *revs)
                        i++;
        }
        free_pathspec(&revs->prune_data);
-       init_pathspec(&revs->prune_data, prune);
+       parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
+                      PATHSPEC_PREFER_FULL | PATHSPEC_LITERAL_PATH, "", prune);
        revs->limited = 1;
 }
 
@@ -1454,26 +1454,40 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
                }
                if (!get_sha1_committish(this, from_sha1) &&
                    !get_sha1_committish(next, sha1)) {
-                       struct commit *a, *b;
-                       struct commit_list *exclude;
-
-                       a = lookup_commit_reference(from_sha1);
-                       b = lookup_commit_reference(sha1);
-                       if (!a || !b) {
-                               if (revs->ignore_missing)
-                                       return 0;
-                               die(symmetric ?
-                                   "Invalid symmetric difference expression %s...%s" :
-                                   "Invalid revision range %s..%s",
-                                   arg, next);
-                       }
+                       struct object *a_obj, *b_obj;
 
                        if (!cant_be_filename) {
                                *dotdot = '.';
                                verify_non_filename(revs->prefix, arg);
                        }
 
-                       if (symmetric) {
+                       a_obj = parse_object(from_sha1);
+                       b_obj = parse_object(sha1);
+                       if (!a_obj || !b_obj) {
+                       missing:
+                               if (revs->ignore_missing)
+                                       return 0;
+                               die(symmetric
+                                   ? "Invalid symmetric difference expression %s"
+                                   : "Invalid revision range %s", arg);
+                       }
+
+                       if (!symmetric) {
+                               /* just A..B */
+                               a_flags = flags_exclude;
+                       } else {
+                               /* A...B -- find merge bases between the two */
+                               struct commit *a, *b;
+                               struct commit_list *exclude;
+
+                               a = (a_obj->type == OBJ_COMMIT
+                                    ? (struct commit *)a_obj
+                                    : lookup_commit_reference(a_obj->sha1));
+                               b = (b_obj->type == OBJ_COMMIT
+                                    ? (struct commit *)b_obj
+                                    : lookup_commit_reference(b_obj->sha1));
+                               if (!a || !b)
+                                       goto missing;
                                exclude = get_merge_bases(a, b, 1);
                                add_rev_cmdline_list(revs, exclude,
                                                     REV_CMD_MERGE_BASE,
@@ -1481,17 +1495,18 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
+
                                a_flags = flags | SYMMETRIC_LEFT;
-                       } else
-                               a_flags = flags_exclude;
-                       a->object.flags |= a_flags;
-                       b->object.flags |= flags;
-                       add_rev_cmdline(revs, &a->object, this,
+                       }
+
+                       a_obj->flags |= a_flags;
+                       b_obj->flags |= flags;
+                       add_rev_cmdline(revs, a_obj, this,
                                        REV_CMD_LEFT, a_flags);
-                       add_rev_cmdline(revs, &b->object, next,
+                       add_rev_cmdline(revs, b_obj, next,
                                        REV_CMD_RIGHT, flags);
-                       add_pending_object(revs, &a->object, this);
-                       add_pending_object(revs, &b->object, next);
+                       add_pending_object(revs, a_obj, this);
+                       add_pending_object(revs, b_obj, next);
                        return 0;
                }
                *dotdot = '.';
@@ -1538,7 +1553,7 @@ struct cmdline_pathspec {
 static void append_prune_data(struct cmdline_pathspec *prune, const char **av)
 {
        while (*av) {
-               ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
+               ALLOC_GROW(prune->path, prune->nr + 1, prune->alloc);
                prune->path[prune->nr++] = *(av++);
        }
 }
@@ -1550,7 +1565,7 @@ static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb,
                int len = sb->len;
                if (len && sb->buf[len - 1] == '\n')
                        sb->buf[--len] = '\0';
-               ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
+               ALLOC_GROW(prune->path, prune->nr + 1, prune->alloc);
                prune->path[prune->nr++] = xstrdup(sb->buf);
        }
 }
@@ -1560,6 +1575,10 @@ static void read_revisions_from_stdin(struct rev_info *revs,
 {
        struct strbuf sb;
        int seen_dashdash = 0;
+       int save_warning;
+
+       save_warning = warn_on_object_refname_ambiguity;
+       warn_on_object_refname_ambiguity = 0;
 
        strbuf_init(&sb, 1000);
        while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
@@ -1581,7 +1600,9 @@ static void read_revisions_from_stdin(struct rev_info *revs,
        }
        if (seen_dashdash)
                read_pathspec_from_stdin(revs, &sb, prune);
+
        strbuf_release(&sb);
+       warn_on_object_refname_ambiguity = save_warning;
 }
 
 static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
@@ -1611,10 +1632,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
            !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
            !strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
            !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
-           !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") ||
-           !prefixcmp(arg, "--exclude=") ||
-           !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") ||
-           !prefixcmp(arg, "--remotes=") || !prefixcmp(arg, "--no-walk="))
+           !strcmp(arg, "--bisect") || starts_with(arg, "--glob=") ||
+           starts_with(arg, "--exclude=") ||
+           starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
+           starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
        {
                unkv[(*unkc)++] = arg;
                return 1;
@@ -1637,7 +1658,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->max_count = atoi(argv[1]);
                revs->no_walk = 0;
                return 2;
-       } else if (!prefixcmp(arg, "-n")) {
+       } else if (starts_with(arg, "-n")) {
                revs->max_count = atoi(arg + 2);
                revs->no_walk = 0;
        } else if ((argcount = parse_long_opt("max-age", argv, &optarg))) {
@@ -1697,7 +1718,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } 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")) {
+       } else if (starts_with(arg, "--early-output")) {
                int count = 100;
                switch (arg[14]) {
                case '=':
@@ -1722,13 +1743,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->min_parents = 2;
        } else if (!strcmp(arg, "--no-merges")) {
                revs->max_parents = 1;
-       } else if (!prefixcmp(arg, "--min-parents=")) {
+       } else if (starts_with(arg, "--min-parents=")) {
                revs->min_parents = atoi(arg+14);
-       } else if (!prefixcmp(arg, "--no-min-parents")) {
+       } else if (starts_with(arg, "--no-min-parents")) {
                revs->min_parents = 0;
-       } else if (!prefixcmp(arg, "--max-parents=")) {
+       } else if (starts_with(arg, "--max-parents=")) {
                revs->max_parents = atoi(arg+14);
-       } else if (!prefixcmp(arg, "--no-max-parents")) {
+       } else if (starts_with(arg, "--no-max-parents")) {
                revs->max_parents = -1;
        } else if (!strcmp(arg, "--boundary")) {
                revs->boundary = 1;
@@ -1778,7 +1799,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->verify_objects = 1;
        } else if (!strcmp(arg, "--unpacked")) {
                revs->unpacked = 1;
-       } else if (!prefixcmp(arg, "--unpacked=")) {
+       } else if (starts_with(arg, "--unpacked=")) {
                die("--unpacked=<packfile> no longer supported.");
        } else if (!strcmp(arg, "-r")) {
                revs->diff = 1;
@@ -1803,7 +1824,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->verbose_header = 1;
                revs->pretty_given = 1;
                get_commit_format(arg+8, revs);
-       } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) {
+       } else if (starts_with(arg, "--pretty=") || starts_with(arg, "--format=")) {
                /*
                 * Detached form ("--pretty X" as opposed to "--pretty=X")
                 * not allowed, since the argument is optional.
@@ -1817,12 +1838,20 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->notes_opt.use_default_notes = 1;
        } else if (!strcmp(arg, "--show-signature")) {
                revs->show_signature = 1;
-       } else if (!prefixcmp(arg, "--show-notes=") ||
-                  !prefixcmp(arg, "--notes=")) {
+       } else if (!strcmp(arg, "--show-linear-break") ||
+                  starts_with(arg, "--show-linear-break=")) {
+               if (starts_with(arg, "--show-linear-break="))
+                       revs->break_bar = xstrdup(arg + 20);
+               else
+                       revs->break_bar = "                    ..........";
+               revs->track_linear = 1;
+               revs->track_first_time = 1;
+       } else if (starts_with(arg, "--show-notes=") ||
+                  starts_with(arg, "--notes=")) {
                struct strbuf buf = STRBUF_INIT;
                revs->show_notes = 1;
                revs->show_notes_given = 1;
-               if (!prefixcmp(arg, "--show-notes")) {
+               if (starts_with(arg, "--show-notes")) {
                        if (revs->notes_opt.use_default_notes < 0)
                                revs->notes_opt.use_default_notes = 1;
                        strbuf_addstr(&buf, arg+13);
@@ -1865,7 +1894,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->abbrev = 0;
        } else if (!strcmp(arg, "--abbrev")) {
                revs->abbrev = DEFAULT_ABBREV;
-       } else if (!prefixcmp(arg, "--abbrev=")) {
+       } else if (starts_with(arg, "--abbrev=")) {
                revs->abbrev = strtoul(arg + 9, NULL, 10);
                if (revs->abbrev < MINIMUM_ABBREV)
                        revs->abbrev = MINIMUM_ABBREV;
@@ -1940,6 +1969,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                        unkv[(*unkc)++] = arg;
                return opts;
        }
+       if (revs->graph && revs->track_linear)
+               die("--show-linear-break and --graph are incompatible");
 
        return 1;
 }
@@ -1989,51 +2020,51 @@ static int handle_revision_pseudo_opt(const char *submodule,
        if (!strcmp(arg, "--all")) {
                handle_refs(submodule, revs, *flags, for_each_ref_submodule);
                handle_refs(submodule, revs, *flags, head_ref_submodule);
-               clear_ref_exclusion(revs);
+               clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--branches")) {
                handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
-               clear_ref_exclusion(revs);
+               clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--bisect")) {
                handle_refs(submodule, revs, *flags, for_each_bad_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);
-               clear_ref_exclusion(revs);
+               clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--remotes")) {
                handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule);
-               clear_ref_exclusion(revs);
+               clear_ref_exclusion(&revs->ref_excludes);
        } else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref(handle_one_ref, optarg, &cb);
-               clear_ref_exclusion(revs);
+               clear_ref_exclusion(&revs->ref_excludes);
                return argcount;
        } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
-               add_ref_exclusion(revs, optarg);
+               add_ref_exclusion(&revs->ref_excludes, optarg);
                return argcount;
-       } else if (!prefixcmp(arg, "--branches=")) {
+       } else if (starts_with(arg, "--branches=")) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
-               clear_ref_exclusion(revs);
-       } else if (!prefixcmp(arg, "--tags=")) {
+               clear_ref_exclusion(&revs->ref_excludes);
+       } else if (starts_with(arg, "--tags=")) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
-               clear_ref_exclusion(revs);
-       } else if (!prefixcmp(arg, "--remotes=")) {
+               clear_ref_exclusion(&revs->ref_excludes);
+       } else if (starts_with(arg, "--remotes=")) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
-               clear_ref_exclusion(revs);
+               clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--reflog")) {
                handle_reflog(revs, *flags);
        } else if (!strcmp(arg, "--not")) {
                *flags ^= UNINTERESTING | BOTTOM;
        } else if (!strcmp(arg, "--no-walk")) {
                revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
-       } else if (!prefixcmp(arg, "--no-walk=")) {
+       } else if (starts_with(arg, "--no-walk=")) {
                /*
                 * Detached form ("--no-walk X" as opposed to "--no-walk=X")
                 * not allowed, since the argument is optional.
@@ -2165,10 +2196,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                 *      call init_pathspec() to set revs->prune_data here.
                 * }
                 */
-               ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
+               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)
@@ -2201,12 +2232,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                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;
@@ -2810,7 +2842,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
        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);
 }
@@ -2867,12 +2899,41 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
        if (action == commit_show &&
            !revs->show_all &&
            revs->prune && revs->dense && want_ancestry(revs)) {
+               /*
+                * --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;
 }
 
+static void track_linear(struct rev_info *revs, struct commit *commit)
+{
+       if (revs->track_first_time) {
+               revs->linear = 1;
+               revs->track_first_time = 0;
+       } else {
+               struct commit_list *p;
+               for (p = revs->previous_parents; p; p = p->next)
+                       if (p->item == NULL || /* first commit */
+                           !hashcmp(p->item->object.sha1, commit->object.sha1))
+                               break;
+               revs->linear = p != NULL;
+       }
+       if (revs->reverse) {
+               if (revs->linear)
+                       commit->object.flags |= TRACK_LINEAR;
+       }
+       free_commit_list(revs->previous_parents);
+       revs->previous_parents = copy_commit_list(commit->parents);
+}
+
 static struct commit *get_revision_1(struct rev_info *revs)
 {
        if (!revs->commits)
@@ -2886,6 +2947,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
                free(entry);
 
                if (revs->reflog_info) {
+                       save_parents(revs, commit);
                        fake_reflog_parent(revs->reflog_info, commit);
                        commit->object.flags &= ~(ADDED | SEEN | SHOWN);
                }
@@ -2899,9 +2961,11 @@ static struct commit *get_revision_1(struct rev_info *revs)
                        if (revs->max_age != -1 &&
                            (commit->date < revs->max_age))
                                continue;
-                       if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0)
-                               die("Failed to traverse parents of commit %s",
-                                   sha1_to_hex(commit->object.sha1));
+                       if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) {
+                               if (!revs->ignore_missing_links)
+                                       die("Failed to traverse parents of commit %s",
+                                               sha1_to_hex(commit->object.sha1));
+                       }
                }
 
                switch (simplify_commit(revs, commit)) {
@@ -2911,6 +2975,8 @@ static struct commit *get_revision_1(struct rev_info *revs)
                        die("Failed to simplify parents of commit %s",
                            sha1_to_hex(commit->object.sha1));
                default:
+                       if (revs->track_linear)
+                               track_linear(revs, commit);
                        return commit;
                }
        } while (revs->commits);
@@ -3008,7 +3074,7 @@ static struct commit *get_revision_internal(struct rev_info *revs)
        if (revs->max_count) {
                c = get_revision_1(revs);
                if (c) {
-                       while (0 < revs->skip_count) {
+                       while (revs->skip_count > 0) {
                                revs->skip_count--;
                                c = get_revision_1(revs);
                                if (!c)
@@ -3023,9 +3089,8 @@ static struct commit *get_revision_internal(struct rev_info *revs)
        if (c)
                c->object.flags |= SHOWN;
 
-       if (!revs->boundary) {
+       if (!revs->boundary)
                return c;
-       }
 
        if (!c) {
                /*
@@ -3071,20 +3136,30 @@ struct commit *get_revision(struct rev_info *revs)
 
        if (revs->reverse) {
                reversed = NULL;
-               while ((c = get_revision_internal(revs))) {
+               while ((c = get_revision_internal(revs)))
                        commit_list_insert(c, &reversed);
-               }
                revs->commits = reversed;
                revs->reverse = 0;
                revs->reverse_output_stage = 1;
        }
 
-       if (revs->reverse_output_stage)
-               return pop_commit(&revs->commits);
+       if (revs->reverse_output_stage) {
+               c = pop_commit(&revs->commits);
+               if (revs->track_linear)
+                       revs->linear = !!(c && c->object.flags & TRACK_LINEAR);
+               return c;
+       }
 
        c = get_revision_internal(revs);
        if (c && revs->graph)
                graph_update(revs->graph, c);
+       if (!c) {
+               free_saved_parents(revs);
+               if (revs->previous_parents) {
+                       free_commit_list(revs->previous_parents);
+                       revs->previous_parents = NULL;
+               }
+       }
        return c;
 }
 
@@ -3116,3 +3191,54 @@ void put_revision_mark(const struct rev_info *revs, const struct commit *commit)
        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);
+}