packaging: Add contrib installation
[platform/upstream/git.git] / revision.c
index 0741229..7ec9b63 100644 (file)
 #include "bisect.h"
 #include "packfile.h"
 #include "worktree.h"
-#include "argv-array.h"
+#include "strvec.h"
 #include "commit-reach.h"
 #include "commit-graph.h"
 #include "prio-queue.h"
 #include "hashmap.h"
+#include "utf8.h"
+#include "bloom.h"
+#include "json-writer.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -36,6 +39,8 @@ static const char *term_good;
 
 implement_shared_commit_slab(revision_sources, char *);
 
+static inline int want_ancestry(const struct rev_info *revs);
+
 void show_object_with_name(FILE *out, struct object *obj, const char *name)
 {
        const char *p;
@@ -107,30 +112,29 @@ struct path_and_oids_entry {
 };
 
 static int path_and_oids_cmp(const void *hashmap_cmp_fn_data,
-                            const struct path_and_oids_entry *e1,
-                            const struct path_and_oids_entry *e2,
+                            const struct hashmap_entry *eptr,
+                            const struct hashmap_entry *entry_or_key,
                             const void *keydata)
 {
-       return strcmp(e1->path, e2->path);
-}
+       const struct path_and_oids_entry *e1, *e2;
 
-static void paths_and_oids_init(struct hashmap *map)
-{
-       hashmap_init(map, (hashmap_cmp_fn) path_and_oids_cmp, NULL, 0);
+       e1 = container_of(eptr, const struct path_and_oids_entry, ent);
+       e2 = container_of(entry_or_key, const struct path_and_oids_entry, ent);
+
+       return strcmp(e1->path, e2->path);
 }
 
 static void paths_and_oids_clear(struct hashmap *map)
 {
        struct hashmap_iter iter;
        struct path_and_oids_entry *entry;
-       hashmap_iter_init(map, &iter);
 
-       while ((entry = (struct path_and_oids_entry *)hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(map, &iter, entry, ent /* member name */) {
                oidset_clear(&entry->trees);
                free(entry->path);
        }
 
-       hashmap_free(map, 1);
+       hashmap_clear_and_free(map, struct path_and_oids_entry, ent);
 }
 
 static void paths_and_oids_insert(struct hashmap *map,
@@ -141,18 +145,19 @@ static void paths_and_oids_insert(struct hashmap *map,
        struct path_and_oids_entry key;
        struct path_and_oids_entry *entry;
 
-       hashmap_entry_init(&key, hash);
+       hashmap_entry_init(&key.ent, hash);
 
        /* use a shallow copy for the lookup */
        key.path = (char *)path;
        oidset_init(&key.trees, 0);
 
-       if (!(entry = (struct path_and_oids_entry *)hashmap_get(map, &key, NULL))) {
+       entry = hashmap_get_entry(map, &key, ent, NULL);
+       if (!entry) {
                entry = xcalloc(1, sizeof(struct path_and_oids_entry));
-               hashmap_entry_init(entry, hash);
+               hashmap_entry_init(&entry->ent, hash);
                entry->path = xstrdup(key.path);
                oidset_init(&entry->trees, 16);
-               hashmap_put(map, entry);
+               hashmap_put(map, &entry->ent);
        }
 
        oidset_insert(&entry->trees, oid);
@@ -203,7 +208,7 @@ void mark_trees_uninteresting_sparse(struct repository *r,
                                     struct oidset *trees)
 {
        unsigned has_interesting = 0, has_uninteresting = 0;
-       struct hashmap map;
+       struct hashmap map = HASHMAP_INIT(path_and_oids_cmp, NULL);
        struct hashmap_iter map_iter;
        struct path_and_oids_entry *entry;
        struct object_id *oid;
@@ -227,16 +232,13 @@ void mark_trees_uninteresting_sparse(struct repository *r,
        if (!has_uninteresting || !has_interesting)
                return;
 
-       paths_and_oids_init(&map);
-
        oidset_iter_init(trees, &iter);
        while ((oid = oidset_iter_next(&iter))) {
                struct tree *tree = lookup_tree(r, oid);
                add_children_by_path(r, tree, &map);
        }
 
-       hashmap_iter_init(&map, &map_iter);
-       while ((entry = hashmap_iter_next(&map_iter)))
+       hashmap_for_each_entry(&map, &map_iter, entry, ent /* member name */)
                mark_trees_uninteresting_sparse(r, &entry->trees);
 
        paths_and_oids_clear(&map);
@@ -306,13 +308,14 @@ static void add_pending_object_with_path(struct rev_info *revs,
                                         const char *name, unsigned mode,
                                         const char *path)
 {
+       struct interpret_branch_name_options options = { 0 };
        if (!obj)
                return;
        if (revs->no_walk && (obj->flags & UNINTERESTING))
                revs->no_walk = 0;
        if (revs->reflog_info && obj->type == OBJ_COMMIT) {
                struct strbuf buf = STRBUF_INIT;
-               int len = interpret_branch_name(name, 0, &buf, 0);
+               int len = interpret_branch_name(name, 0, &buf, &options);
 
                if (0 < len && name[len] && buf.len)
                        strbuf_addstr(&buf, name + len);
@@ -404,9 +407,7 @@ static struct commit *handle_commit(struct rev_info *revs,
                struct tag *tag = (struct tag *) object;
                if (revs->tag_objects && !(flags & UNINTERESTING))
                        add_pending_object(revs, object, tag->tag);
-               if (!tag->tagged)
-                       die("bad tag");
-               object = parse_object(revs->repo, &tag->tagged->oid);
+               object = parse_object(revs->repo, get_tagged_oid(tag));
                if (!object) {
                        if (revs->ignore_missing_links || (flags & UNINTERESTING))
                                return NULL;
@@ -432,7 +433,7 @@ static struct commit *handle_commit(struct rev_info *revs,
        if (object->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)object;
 
-               if (parse_commit(commit) < 0)
+               if (repo_parse_commit(revs->repo, commit) < 0)
                        die("unable to parse commit %s", name);
                if (flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
@@ -621,11 +622,156 @@ static void file_change(struct diff_options *options,
        options->flags.has_changes = 1;
 }
 
+static int bloom_filter_atexit_registered;
+static unsigned int count_bloom_filter_maybe;
+static unsigned int count_bloom_filter_definitely_not;
+static unsigned int count_bloom_filter_false_positive;
+static unsigned int count_bloom_filter_not_present;
+
+static void trace2_bloom_filter_statistics_atexit(void)
+{
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       jw_object_intmax(&jw, "filter_not_present", count_bloom_filter_not_present);
+       jw_object_intmax(&jw, "maybe", count_bloom_filter_maybe);
+       jw_object_intmax(&jw, "definitely_not", count_bloom_filter_definitely_not);
+       jw_object_intmax(&jw, "false_positive", count_bloom_filter_false_positive);
+       jw_end(&jw);
+
+       trace2_data_json("bloom", the_repository, "statistics", &jw);
+
+       jw_release(&jw);
+}
+
+static int forbid_bloom_filters(struct pathspec *spec)
+{
+       if (spec->has_wildcard)
+               return 1;
+       if (spec->nr > 1)
+               return 1;
+       if (spec->magic & ~PATHSPEC_LITERAL)
+               return 1;
+       if (spec->nr && (spec->items[0].magic & ~PATHSPEC_LITERAL))
+               return 1;
+
+       return 0;
+}
+
+static void prepare_to_use_bloom_filter(struct rev_info *revs)
+{
+       struct pathspec_item *pi;
+       char *path_alloc = NULL;
+       const char *path, *p;
+       size_t len;
+       int path_component_nr = 1;
+
+       if (!revs->commits)
+               return;
+
+       if (forbid_bloom_filters(&revs->prune_data))
+               return;
+
+       repo_parse_commit(revs->repo, revs->commits->item);
+
+       revs->bloom_filter_settings = get_bloom_filter_settings(revs->repo);
+       if (!revs->bloom_filter_settings)
+               return;
+
+       if (!revs->pruning.pathspec.nr)
+               return;
+
+       pi = &revs->pruning.pathspec.items[0];
+
+       /* remove single trailing slash from path, if needed */
+       if (pi->len > 0 && pi->match[pi->len - 1] == '/') {
+               path_alloc = xmemdupz(pi->match, pi->len - 1);
+               path = path_alloc;
+       } else
+               path = pi->match;
+
+       len = strlen(path);
+       if (!len) {
+               revs->bloom_filter_settings = NULL;
+               free(path_alloc);
+               return;
+       }
+
+       p = path;
+       while (*p) {
+               /*
+                * At this point, the path is normalized to use Unix-style
+                * path separators. This is required due to how the
+                * changed-path Bloom filters store the paths.
+                */
+               if (*p == '/')
+                       path_component_nr++;
+               p++;
+       }
+
+       revs->bloom_keys_nr = path_component_nr;
+       ALLOC_ARRAY(revs->bloom_keys, revs->bloom_keys_nr);
+
+       fill_bloom_key(path, len, &revs->bloom_keys[0],
+                      revs->bloom_filter_settings);
+       path_component_nr = 1;
+
+       p = path + len - 1;
+       while (p > path) {
+               if (*p == '/')
+                       fill_bloom_key(path, p - path,
+                                      &revs->bloom_keys[path_component_nr++],
+                                      revs->bloom_filter_settings);
+               p--;
+       }
+
+       if (trace2_is_enabled() && !bloom_filter_atexit_registered) {
+               atexit(trace2_bloom_filter_statistics_atexit);
+               bloom_filter_atexit_registered = 1;
+       }
+
+       free(path_alloc);
+}
+
+static int check_maybe_different_in_bloom_filter(struct rev_info *revs,
+                                                struct commit *commit)
+{
+       struct bloom_filter *filter;
+       int result = 1, j;
+
+       if (!revs->repo->objects->commit_graph)
+               return -1;
+
+       if (commit_graph_generation(commit) == GENERATION_NUMBER_INFINITY)
+               return -1;
+
+       filter = get_bloom_filter(revs->repo, commit);
+
+       if (!filter) {
+               count_bloom_filter_not_present++;
+               return -1;
+       }
+
+       for (j = 0; result && j < revs->bloom_keys_nr; j++) {
+               result = bloom_filter_contains(filter,
+                                              &revs->bloom_keys[j],
+                                              revs->bloom_filter_settings);
+       }
+
+       if (result)
+               count_bloom_filter_maybe++;
+       else
+               count_bloom_filter_definitely_not++;
+
+       return result;
+}
+
 static int rev_compare_tree(struct rev_info *revs,
-                           struct commit *parent, struct commit *commit)
+                           struct commit *parent, struct commit *commit, int nth_parent)
 {
        struct tree *t1 = get_commit_tree(parent);
        struct tree *t2 = get_commit_tree(commit);
+       int bloom_ret = 1;
 
        if (!t1)
                return REV_TREE_NEW;
@@ -650,17 +796,26 @@ static int rev_compare_tree(struct rev_info *revs,
                        return REV_TREE_SAME;
        }
 
+       if (revs->bloom_keys_nr && !nth_parent) {
+               bloom_ret = check_maybe_different_in_bloom_filter(revs, commit);
+
+               if (bloom_ret == 0)
+                       return REV_TREE_SAME;
+       }
+
        tree_difference = REV_TREE_SAME;
        revs->pruning.flags.has_changes = 0;
-       if (diff_tree_oid(&t1->object.oid, &t2->object.oid, "",
-                          &revs->pruning) < 0)
-               return REV_TREE_DIFFERENT;
+       diff_tree_oid(&t1->object.oid, &t2->object.oid, "", &revs->pruning);
+
+       if (!nth_parent)
+               if (bloom_ret == 1 && tree_difference == REV_TREE_SAME)
+                       count_bloom_filter_false_positive++;
+
        return tree_difference;
 }
 
 static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
 {
-       int retval;
        struct tree *t1 = get_commit_tree(commit);
 
        if (!t1)
@@ -668,9 +823,9 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
 
        tree_difference = REV_TREE_SAME;
        revs->pruning.flags.has_changes = 0;
-       retval = diff_tree_oid(NULL, &t1->object.oid, "", &revs->pruning);
+       diff_tree_oid(NULL, &t1->object.oid, "", &revs->pruning);
 
-       return retval >= 0 && (tree_difference == REV_TREE_SAME);
+       return tree_difference == REV_TREE_SAME;
 }
 
 struct treesame_state {
@@ -848,11 +1003,11 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                                        ts->treesame[0] = 1;
                        }
                }
-               if (parse_commit(p) < 0)
+               if (repo_parse_commit(revs->repo, p) < 0)
                        die("cannot simplify commit %s (because of %s)",
                            oid_to_hex(&commit->object.oid),
                            oid_to_hex(&p->object.oid));
-               switch (rev_compare_tree(revs, p, commit)) {
+               switch (rev_compare_tree(revs, p, commit, nth_parent)) {
                case REV_TREE_SAME:
                        if (!revs->simplify_history || !relevant_commit(p)) {
                                /* Even if a merge with an uninteresting
@@ -867,7 +1022,19 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                        }
                        parent->next = NULL;
                        commit->parents = parent;
-                       commit->object.flags |= TREESAME;
+
+                       /*
+                        * A merge commit is a "diversion" if it is not
+                        * TREESAME to its first parent but is TREESAME
+                        * to a later parent. In the simplified history,
+                        * we "divert" the history walk to the later
+                        * parent. These commits are shown when "show_pulls"
+                        * is enabled, so do not mark the object as
+                        * TREESAME here.
+                        */
+                       if (!revs->show_pulls || !nth_parent)
+                               commit->object.flags |= TREESAME;
+
                        return;
 
                case REV_TREE_NEW:
@@ -881,7 +1048,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                                 * IOW, we pretend this parent is a
                                 * "root" commit.
                                 */
-                               if (parse_commit(p) < 0)
+                               if (repo_parse_commit(revs->repo, p) < 0)
                                        die("cannot simplify commit %s (invalid %s)",
                                            oid_to_hex(&commit->object.oid),
                                            oid_to_hex(&p->object.oid));
@@ -894,6 +1061,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                                relevant_change = 1;
                        else
                                irrelevant_change = 1;
+
+                       if (!nth_parent)
+                               commit->object.flags |= PULL_MERGE;
+
                        continue;
                }
                die("bad tree compare for commit %s", oid_to_hex(&commit->object.oid));
@@ -945,7 +1116,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                        parent = parent->next;
                        if (p)
                                p->object.flags |= UNINTERESTING;
-                       if (parse_commit_gently(p, 1) < 0)
+                       if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
                                continue;
                        if (p->parents)
                                mark_parents_uninteresting(p);
@@ -976,7 +1147,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                struct commit *p = parent->item;
                int gently = revs->ignore_missing_links ||
                             revs->exclude_promisor_objects;
-               if (parse_commit_gently(p, gently) < 0) {
+               if (repo_parse_commit_gently(revs->repo, p, gently) < 0) {
                        if (revs->exclude_promisor_objects &&
                            is_promisor_object(&p->object.oid)) {
                                if (revs->first_parent_only)
@@ -1070,12 +1241,14 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
                /*
                 * Have we seen the same patch id?
                 */
-               id = has_commit_patch_id(commit, &ids);
+               id = patch_id_iter_first(commit, &ids);
                if (!id)
                        continue;
 
                commit->object.flags |= cherry_flag;
-               id->commit->object.flags |= cherry_flag;
+               do {
+                       id->commit->object.flags |= cherry_flag;
+               } while ((id = patch_id_iter_next(id, &ids)));
        }
 
        free_patch_ids(&ids);
@@ -1250,7 +1423,8 @@ static int limit_list(struct rev_info *revs)
                                continue;
                        break;
                }
-               if (revs->min_age != -1 && (commit->date > revs->min_age))
+               if (revs->min_age != -1 && (commit->date > revs->min_age) &&
+                   !revs->line_level_traverse)
                        continue;
                date = commit->date;
                p = &commit_list_insert(commit, p)->next;
@@ -1449,7 +1623,7 @@ static void add_other_reflogs_to_pending(struct all_refs_cb *cb)
 {
        struct worktree **worktrees, **p;
 
-       worktrees = get_worktrees(0);
+       worktrees = get_worktrees();
        for (p = worktrees; *p; p++) {
                struct worktree *wt = *p;
 
@@ -1537,7 +1711,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags)
        if (revs->single_worktree)
                return;
 
-       worktrees = get_worktrees(0);
+       worktrees = get_worktrees();
        for (p = worktrees; *p; p++) {
                struct worktree *wt = *p;
                struct index_state istate = { NULL };
@@ -1634,7 +1808,7 @@ void repo_init_revisions(struct repository *r,
 
        revs->repo = r;
        revs->abbrev = DEFAULT_ABBREV;
-       revs->ignore_merges = 1;
+       revs->ignore_merges = -1;
        revs->simplify_history = 1;
        revs->pruning.repo = r;
        revs->pruning.flags.recursive = 1;
@@ -1655,7 +1829,6 @@ void repo_init_revisions(struct repository *r,
        revs->commit_format = CMIT_FMT_DEFAULT;
        revs->expand_tabs_in_log_default = 8;
 
-       init_grep_defaults(revs->repo);
        grep_init(&revs->grep_filter, revs->repo, prefix);
        revs->grep_filter.status_only = 1;
 
@@ -1665,7 +1838,7 @@ void repo_init_revisions(struct repository *r,
                revs->diffopt.prefix_length = strlen(prefix);
        }
 
-       revs->notes_opt.use_default_notes = -1;
+       init_display_notes(&revs->notes_opt);
 }
 
 static void add_pending_commit_list(struct rev_info *revs,
@@ -1836,7 +2009,7 @@ static int handle_dotdot(const char *arg,
        return ret;
 }
 
-int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
+static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
 {
        struct object_context oc;
        char *mark;
@@ -1911,15 +2084,23 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
        return 0;
 }
 
+int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt)
+{
+       int ret = handle_revision_arg_1(arg, revs, flags, revarg_opt);
+       if (!ret)
+               revs->rev_input_given = 1;
+       return ret;
+}
+
 static void read_pathspec_from_stdin(struct strbuf *sb,
-                                    struct argv_array *prune)
+                                    struct strvec *prune)
 {
        while (strbuf_getline(sb, stdin) != EOF)
-               argv_array_push(prune, sb->buf);
+               strvec_push(prune, sb->buf);
 }
 
 static void read_revisions_from_stdin(struct rev_info *revs,
-                                     struct argv_array *prune)
+                                     struct strvec *prune)
 {
        struct strbuf sb;
        int seen_dashdash = 0;
@@ -2063,7 +2244,6 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->simplify_by_decoration = 1;
                revs->limited = 1;
                revs->prune = 1;
-               load_ref_decorations(NULL, DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
                revs->sort_order = REV_SORT_BY_COMMIT_DATE;
                revs->topo_order = 1;
@@ -2155,7 +2335,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--unpacked")) {
                revs->unpacked = 1;
        } else if (starts_with(arg, "--unpacked=")) {
-               die("--unpacked=<packfile> no longer supported.");
+               die(_("--unpacked=<packfile> no longer supported"));
        } else if (!strcmp(arg, "-r")) {
                revs->diff = 1;
                revs->diffopt.flags.recursive = 1;
@@ -2164,7 +2344,22 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->diffopt.flags.recursive = 1;
                revs->diffopt.flags.tree_in_recursive = 1;
        } else if (!strcmp(arg, "-m")) {
+               /*
+                * To "diff-index", "-m" means "match missing", and to the "log"
+                * family of commands, it means "show full diff for merges". Set
+                * both fields appropriately.
+                */
                revs->ignore_merges = 0;
+               revs->match_missing = 1;
+       } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) {
+               if (!strcmp(optarg, "off")) {
+                       revs->ignore_merges = 1;
+               } else {
+                       die(_("unknown value for --diff-merges: %s"), optarg);
+               }
+               return argcount;
+       } else if (!strcmp(arg, "--no-diff-merges")) {
+               revs->ignore_merges = 1;
        } else if (!strcmp(arg, "-c")) {
                revs->diff = 1;
                revs->dense_combined_merges = 0;
@@ -2201,9 +2396,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                        die("'%s': not a non-negative integer", arg);
                revs->expand_tabs_in_log = val;
        } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) {
-               revs->show_notes = 1;
+               enable_default_display_notes(&revs->notes_opt, &revs->show_notes);
                revs->show_notes_given = 1;
-               revs->notes_opt.use_default_notes = 1;
        } else if (!strcmp(arg, "--show-signature")) {
                revs->show_signature = 1;
        } else if (!strcmp(arg, "--no-show-signature")) {
@@ -2218,25 +2412,14 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->track_first_time = 1;
        } else if (skip_prefix(arg, "--show-notes=", &optarg) ||
                   skip_prefix(arg, "--notes=", &optarg)) {
-               struct strbuf buf = STRBUF_INIT;
-               revs->show_notes = 1;
-               revs->show_notes_given = 1;
                if (starts_with(arg, "--show-notes=") &&
                    revs->notes_opt.use_default_notes < 0)
                        revs->notes_opt.use_default_notes = 1;
-               strbuf_addstr(&buf, optarg);
-               expand_notes_ref(&buf);
-               string_list_append(&revs->notes_opt.extra_notes_refs,
-                                  strbuf_detach(&buf, NULL));
+               enable_ref_display_notes(&revs->notes_opt, &revs->show_notes, optarg);
+               revs->show_notes_given = 1;
        } else if (!strcmp(arg, "--no-notes")) {
-               revs->show_notes = 0;
+               disable_display_notes(&revs->notes_opt, &revs->show_notes);
                revs->show_notes_given = 1;
-               revs->notes_opt.use_default_notes = -1;
-               /* we have been strdup'ing ourselves, so trick
-                * string_list into free()ing strings */
-               revs->notes_opt.extra_notes_refs.strdup_strings = 1;
-               string_list_clear(&revs->notes_opt.extra_notes_refs, 0);
-               revs->notes_opt.extra_notes_refs.strdup_strings = 0;
        } else if (!strcmp(arg, "--standard-notes")) {
                revs->show_notes_given = 1;
                revs->notes_opt.use_default_notes = 1;
@@ -2251,6 +2434,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->topo_order = 1;
                revs->rewrite_parents = 1;
                revs->graph = graph_init(revs);
+       } else if (!strcmp(arg, "--encode-email-headers")) {
+               revs->encode_email_headers = 1;
+       } else if (!strcmp(arg, "--no-encode-email-headers")) {
+               revs->encode_email_headers = 0;
        } else if (!strcmp(arg, "--root")) {
                revs->show_root_diff = 1;
        } else if (!strcmp(arg, "--no-commit-id")) {
@@ -2275,6 +2462,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--full-diff")) {
                revs->diff = 1;
                revs->full_diff = 1;
+       } else if (!strcmp(arg, "--show-pulls")) {
+               revs->show_pulls = 1;
        } else if (!strcmp(arg, "--full-history")) {
                revs->simplify_history = 0;
        } else if (!strcmp(arg, "--relative-date")) {
@@ -2385,8 +2574,8 @@ static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void
 }
 
 static int handle_revision_pseudo_opt(const char *submodule,
-                               struct rev_info *revs,
-                               int argc, const char **argv, int *flags)
+                                     struct rev_info *revs,
+                                     const char **argv, int *flags)
 {
        const char *arg = argv[0];
        const char *optarg;
@@ -2520,9 +2709,10 @@ static void NORETURN diagnose_missing_default(const char *def)
  */
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
 {
-       int i, flags, left, seen_dashdash, got_rev_arg = 0, revarg_opt;
-       struct argv_array prune_data = ARGV_ARRAY_INIT;
+       int i, flags, left, seen_dashdash, revarg_opt;
+       struct strvec prune_data = STRVEC_INIT;
        const char *submodule = NULL;
+       int seen_end_of_options = 0;
 
        if (opt)
                submodule = opt->submodule;
@@ -2539,7 +2729,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                        argv[i] = NULL;
                        argc = i;
                        if (argv[i + 1])
-                               argv_array_pushv(&prune_data, argv + i + 1);
+                               strvec_pushv(&prune_data, argv + i + 1);
                        seen_dashdash = 1;
                        break;
                }
@@ -2552,11 +2742,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                revarg_opt |= REVARG_CANNOT_BE_FILENAME;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               if (*arg == '-') {
+               if (!seen_end_of_options && *arg == '-') {
                        int opts;
 
                        opts = handle_revision_pseudo_opt(submodule,
-                                               revs, argc - i, argv + i,
+                                               revs, argv + i,
                                                &flags);
                        if (opts > 0) {
                                i += opts - 1;
@@ -2574,6 +2764,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                                continue;
                        }
 
+                       if (!strcmp(arg, "--end-of-options")) {
+                               seen_end_of_options = 1;
+                               continue;
+                       }
+
                        opts = handle_revision_opt(revs, argc - i, argv + i,
                                                   &left, argv, opt);
                        if (opts > 0) {
@@ -2600,14 +2795,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                        for (j = i; j < argc; j++)
                                verify_filename(revs->prefix, argv[j], j == i);
 
-                       argv_array_pushv(&prune_data, argv + i);
+                       strvec_pushv(&prune_data, argv + i);
                        break;
                }
-               else
-                       got_rev_arg = 1;
        }
 
-       if (prune_data.argc) {
+       if (prune_data.nr) {
                /*
                 * If we need to introduce the magic "a lone ':' means no
                 * pathspec whatsoever", here is the place to do so.
@@ -2623,9 +2816,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                 * }
                 */
                parse_pathspec(&revs->prune_data, 0, 0,
-                              revs->prefix, prune_data.argv);
+                              revs->prefix, prune_data.v);
        }
-       argv_array_clear(&prune_data);
+       strvec_clear(&prune_data);
 
        if (revs->def == NULL)
                revs->def = opt ? opt->def : NULL;
@@ -2633,7 +2826,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                opt->tweak(revs, opt);
        if (revs->show_merge)
                prepare_show_merge(revs);
-       if (revs->def && !revs->pending.nr && !revs->rev_input_given && !got_rev_arg) {
+       if (revs->def && !revs->pending.nr && !revs->rev_input_given) {
                struct object_id oid;
                struct object *object;
                struct object_context oc;
@@ -2656,6 +2849,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->diffopt.objfind)
                revs->simplify_history = 0;
 
+       if (revs->line_level_traverse) {
+               if (want_ancestry(revs))
+                       revs->limited = 1;
+               revs->topo_order = 1;
+       }
+
        if (revs->topo_order && !generation_numbers_enabled(the_repository))
                revs->limited = 1;
 
@@ -2668,22 +2867,21 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                        copy_pathspec(&revs->diffopt.pathspec,
                                      &revs->prune_data);
        }
-       if (revs->combine_merges)
+       if (revs->combine_merges && revs->ignore_merges < 0)
                revs->ignore_merges = 0;
+       if (revs->ignore_merges < 0)
+               revs->ignore_merges = 1;
        if (revs->combined_all_paths && !revs->combine_merges)
                die("--combined-all-paths makes no sense without -c or --cc");
 
        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,
                                 &revs->grep_filter);
+       if (!is_encoding_utf8(get_log_output_encoding()))
+               revs->grep_filter.ignore_locale = 1;
        compile_grep_patterns(&revs->grep_filter);
 
        if (revs->reverse && revs->reflog_info)
@@ -2706,9 +2904,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
                die("cannot use --grep-reflog without --walk-reflogs");
 
-       if (revs->first_parent_only && revs->bisect)
-               die(_("--first-parent is incompatible with --bisect"));
-
        if (revs->line_level_traverse &&
            (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))
                die(_("-L does not yet support diff formats besides -p and -s"));
@@ -3021,7 +3216,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
        if (!cnt ||
            (commit->object.flags & UNINTERESTING) ||
            !(commit->object.flags & TREESAME) ||
-           (parent = one_relevant_parent(revs, commit->parents)) == NULL)
+           (parent = one_relevant_parent(revs, commit->parents)) == NULL ||
+           (revs->show_pulls && (commit->object.flags & PULL_MERGE)))
                st->simplified = commit;
        else {
                pst = locate_simplify_state(revs, parent);
@@ -3088,7 +3284,7 @@ static void set_children(struct rev_info *revs)
 
 void reset_revision_walk(void)
 {
-       clear_object_flags(SEEN | ADDED | SHOWN);
+       clear_object_flags(SEEN | ADDED | SHOWN | TOPO_WALK_EXPLORED | TOPO_WALK_INDEGREE);
 }
 
 static int mark_uninteresting(const struct object_id *oid,
@@ -3132,7 +3328,7 @@ static void explore_walk_step(struct rev_info *revs)
        if (!c)
                return;
 
-       if (parse_commit_gently(c, 1) < 0)
+       if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
                return;
 
        if (revs->sort_order == REV_SORT_BY_AUTHOR_DATE)
@@ -3157,7 +3353,7 @@ static void explore_to_depth(struct rev_info *revs,
        struct topo_walk_info *info = revs->topo_walk_info;
        struct commit *c;
        while ((c = prio_queue_peek(&info->explore_queue)) &&
-              c->generation >= gen_cutoff)
+              commit_graph_generation(c) >= gen_cutoff)
                explore_walk_step(revs);
 }
 
@@ -3170,10 +3366,10 @@ static void indegree_walk_step(struct rev_info *revs)
        if (!c)
                return;
 
-       if (parse_commit_gently(c, 1) < 0)
+       if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
                return;
 
-       explore_to_depth(revs, c->generation);
+       explore_to_depth(revs, commit_graph_generation(c));
 
        for (p = c->parents; p; p = p->next) {
                struct commit *parent = p->item;
@@ -3197,14 +3393,30 @@ static void compute_indegrees_to_depth(struct rev_info *revs,
        struct topo_walk_info *info = revs->topo_walk_info;
        struct commit *c;
        while ((c = prio_queue_peek(&info->indegree_queue)) &&
-              c->generation >= gen_cutoff)
+              commit_graph_generation(c) >= gen_cutoff)
                indegree_walk_step(revs);
 }
 
+static void reset_topo_walk(struct rev_info *revs)
+{
+       struct topo_walk_info *info = revs->topo_walk_info;
+
+       clear_prio_queue(&info->explore_queue);
+       clear_prio_queue(&info->indegree_queue);
+       clear_prio_queue(&info->topo_queue);
+       clear_indegree_slab(&info->indegree);
+       clear_author_date_slab(&info->author_date);
+
+       FREE_AND_NULL(revs->topo_walk_info);
+}
+
 static void init_topo_walk(struct rev_info *revs)
 {
        struct topo_walk_info *info;
        struct commit_list *list;
+       if (revs->topo_walk_info)
+               reset_topo_walk(revs);
+
        revs->topo_walk_info = xmalloc(sizeof(struct topo_walk_info));
        info = revs->topo_walk_info;
        memset(info, 0, sizeof(struct topo_walk_info));
@@ -3234,15 +3446,17 @@ static void init_topo_walk(struct rev_info *revs)
        info->min_generation = GENERATION_NUMBER_INFINITY;
        for (list = revs->commits; list; list = list->next) {
                struct commit *c = list->item;
+               uint32_t generation;
 
-               if (parse_commit_gently(c, 1))
+               if (repo_parse_commit_gently(revs->repo, c, 1))
                        continue;
 
                test_flag_and_insert(&info->explore_queue, c, TOPO_WALK_EXPLORED);
                test_flag_and_insert(&info->indegree_queue, c, TOPO_WALK_INDEGREE);
 
-               if (c->generation < info->min_generation)
-                       info->min_generation = c->generation;
+               generation = commit_graph_generation(c);
+               if (generation < info->min_generation)
+                       info->min_generation = generation;
 
                *(indegree_slab_at(&info->indegree, c)) = 1;
 
@@ -3293,15 +3507,17 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit)
        for (p = commit->parents; p; p = p->next) {
                struct commit *parent = p->item;
                int *pi;
+               uint32_t generation;
 
                if (parent->object.flags & UNINTERESTING)
                        continue;
 
-               if (parse_commit_gently(parent, 1) < 0)
+               if (repo_parse_commit_gently(revs->repo, parent, 1) < 0)
                        continue;
 
-               if (parent->generation < info->min_generation) {
-                       info->min_generation = parent->generation;
+               generation = commit_graph_generation(parent);
+               if (generation < info->min_generation) {
+                       info->min_generation = generation;
                        compute_indegrees_to_depth(revs, info->min_generation);
                }
 
@@ -3348,6 +3564,8 @@ int prepare_revision_walk(struct rev_info *revs)
                                       FOR_EACH_OBJECT_PROMISOR_ONLY);
        }
 
+       if (!revs->reflog_info)
+               prepare_to_use_bloom_filter(revs);
        if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
                commit_list_sort_by_date(&revs->commits);
        if (revs->no_walk)
@@ -3359,12 +3577,20 @@ int prepare_revision_walk(struct rev_info *revs)
                        sort_in_topological_order(&revs->commits, revs->sort_order);
        } else if (revs->topo_order)
                init_topo_walk(revs);
-       if (revs->line_level_traverse)
+       if (revs->line_level_traverse && want_ancestry(revs))
+               /*
+                * At the moment we can only do line-level log with parent
+                * rewriting by performing this expensive pre-filtering step.
+                * If parent rewriting is not requested, then we rather
+                * perform the line-level log filtering during the regular
+                * history traversal.
+                */
                line_log_filter(revs);
        if (revs->simplify_merges)
                simplify_merges(revs);
        if (revs->children.name)
                set_children(revs);
+
        return 0;
 }
 
@@ -3569,6 +3795,22 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
                return commit_ignore;
        if (commit->object.flags & UNINTERESTING)
                return commit_ignore;
+       if (revs->line_level_traverse && !want_ancestry(revs)) {
+               /*
+                * In case of line-level log with parent rewriting
+                * prepare_revision_walk() already took care of all line-level
+                * log filtering, and there is nothing left to do here.
+                *
+                * If parent rewriting was not requested, then this is the
+                * place to perform the line-level log filtering.  Notably,
+                * this check, though expensive, must come before the other,
+                * cheaper filtering conditions, because the tracked line
+                * ranges must be adjusted even when the commit will end up
+                * being ignored based on other conditions.
+                */
+               if (!line_log_process_ranges_arbitrary_commit(revs, commit))
+                       return commit_ignore;
+       }
        if (revs->min_age != -1 &&
            comparison_date(revs, commit) > revs->min_age)
                        return commit_ignore;
@@ -3588,6 +3830,10 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
                        /* drop merges unless we want parenthood */
                        if (!want_ancestry(revs))
                                return commit_ignore;
+
+                       if (revs->show_pulls && (commit->object.flags & PULL_MERGE))
+                               return commit_show;
+
                        /*
                         * If we want ancestry, then need to keep any merges
                         * between relevant commits to tie together topology.
@@ -3934,7 +4180,7 @@ struct commit *get_revision(struct rev_info *revs)
        return c;
 }
 
-char *get_revision_mark(const struct rev_info *revs, const struct commit *commit)
+const char *get_revision_mark(const struct rev_info *revs, const struct commit *commit)
 {
        if (commit->object.flags & BOUNDARY)
                return "-";
@@ -3956,7 +4202,7 @@ char *get_revision_mark(const struct rev_info *revs, const struct commit *commit
 
 void put_revision_mark(const struct rev_info *revs, const struct commit *commit)
 {
-       char *mark = get_revision_mark(revs, commit);
+       const char *mark = get_revision_mark(revs, commit);
        if (!strlen(mark))
                return;
        fputs(mark, stdout);