Merge branch 'ds/commit-graph-with-grafts'
authorJunio C Hamano <gitster@pobox.com>
Tue, 16 Oct 2018 07:15:59 +0000 (16:15 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 16 Oct 2018 07:15:59 +0000 (16:15 +0900)
The recently introduced commit-graph auxiliary data is incompatible
with mechanisms such as replace & grafts that "breaks" immutable
nature of the object reference relationship. Disable optimizations
based on its use (and updating existing commit-graph) when these
incompatible features are in use in the repository.

* ds/commit-graph-with-grafts:
commit-graph: close_commit_graph before shallow walk
commit-graph: not compatible with uninitialized repo
commit-graph: not compatible with grafts
commit-graph: not compatible with replace objects
test-repository: properly init repo
commit-graph: update design document
refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
refs.c: migrate internal ref iteration to pass thru repository argument

1  2 
builtin/commit-graph.c
builtin/replace.c
commit-graph.c
commit-graph.h
commit.c
commit.h
refs.c
refs.h
replace-object.c
t/t5318-commit-graph.sh
upload-pack.c
diff --combined builtin/commit-graph.c
index bc0fa9ba525a2e2a67610c2bbbaba37e00676cb8,da737df321528088d8aa20e3e87e803336b1f550..22b974f4b434913cf3ebe9e4073d2cb847f8a468
@@@ -120,6 -120,8 +120,8 @@@ static int graph_read(int argc, const c
        return 0;
  }
  
+ extern int read_replace_refs;
  static int graph_write(int argc, const char **argv)
  {
        struct string_list *pack_indexes = NULL;
        if (!opts.obj_dir)
                opts.obj_dir = get_object_directory();
  
+       read_replace_refs = 0;
        if (opts.reachable) {
 -              write_commit_graph_reachable(opts.obj_dir, opts.append);
 +              write_commit_graph_reachable(opts.obj_dir, opts.append, 1);
                return 0;
        }
  
        write_commit_graph(opts.obj_dir,
                           pack_indexes,
                           commit_hex,
 -                         opts.append);
 +                         opts.append,
 +                         1);
  
        string_list_clear(&lines, 0);
        return 0;
diff --combined builtin/replace.c
index 8e67e09819e1e9445dde80b23f7d9e104799deec,b5861a0ee91f82f6e7bc7641bec140e0ee3ed06c..30a661ea0c71f058cdb8bc7626334cb03747fa7c
@@@ -39,7 -39,8 +39,8 @@@ struct show_data 
        enum replace_format format;
  };
  
- static int show_reference(const char *refname, const struct object_id *oid,
+ static int show_reference(struct repository *r, const char *refname,
+                         const struct object_id *oid,
                          int flag, void *cb_data)
  {
        struct show_data *data = cb_data;
                        enum object_type obj_type, repl_type;
  
                        if (get_oid(refname, &object))
 -                              return error("Failed to resolve '%s' as a valid ref.", refname);
 +                              return error(_("failed to resolve '%s' as a valid ref"), refname);
  
-                       obj_type = oid_object_info(the_repository, &object,
-                                                  NULL);
-                       repl_type = oid_object_info(the_repository, oid, NULL);
+                       obj_type = oid_object_info(r, &object, NULL);
+                       repl_type = oid_object_info(r, oid, NULL);
  
                        printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
                               oid_to_hex(oid), type_name(repl_type));
@@@ -83,8 -83,8 +83,8 @@@ static int list_replace_refs(const cha
        else if (!strcmp(format, "long"))
                data.format = REPLACE_FORMAT_LONG;
        else
 -              return error("invalid replace format '%s'\n"
 -                           "valid formats are 'short', 'medium' and 'long'\n",
 +              return error(_("invalid replace format '%s'\n"
 +                             "valid formats are 'short', 'medium' and 'long'"),
                             format);
  
        for_each_replace_ref(the_repository, show_reference, (void *)&data);
@@@ -108,7 -108,7 +108,7 @@@ static int for_each_replace_name(const 
  
        for (p = argv; *p; p++) {
                if (get_oid(*p, &oid)) {
 -                      error("Failed to resolve '%s' as a valid ref.", *p);
 +                      error("failed to resolve '%s' as a valid ref", *p);
                        had_error = 1;
                        continue;
                }
                full_hex = ref.buf + base_len;
  
                if (read_ref(ref.buf, &oid)) {
 -                      error("replace ref '%s' not found.", full_hex);
 +                      error(_("replace ref '%s' not found"), full_hex);
                        had_error = 1;
                        continue;
                }
@@@ -134,7 -134,7 +134,7 @@@ static int delete_replace_ref(const cha
  {
        if (delete_ref(NULL, ref, oid, 0))
                return 1;
 -      printf("Deleted replace ref '%s'\n", name);
 +      printf_ln(_("Deleted replace ref '%s'"), name);
        return 0;
  }
  
@@@ -146,12 -146,12 +146,12 @@@ static int check_ref_valid(struct objec
        strbuf_reset(ref);
        strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object));
        if (check_refname_format(ref->buf, 0))
 -              return error("'%s' is not a valid ref name.", ref->buf);
 +              return error(_("'%s' is not a valid ref name"), ref->buf);
  
        if (read_ref(ref->buf, prev))
                oidclr(prev);
        else if (!force)
 -              return error("replace ref '%s' already exists", ref->buf);
 +              return error(_("replace ref '%s' already exists"), ref->buf);
        return 0;
  }
  
@@@ -171,10 -171,10 +171,10 @@@ static int replace_object_oid(const cha
        obj_type = oid_object_info(the_repository, object, NULL);
        repl_type = oid_object_info(the_repository, repl, NULL);
        if (!force && obj_type != repl_type)
 -              return error("Objects must be of the same type.\n"
 -                           "'%s' points to a replaced object of type '%s'\n"
 -                           "while '%s' points to a replacement object of "
 -                           "type '%s'.",
 +              return error(_("Objects must be of the same type.\n"
 +                             "'%s' points to a replaced object of type '%s'\n"
 +                             "while '%s' points to a replacement object of "
 +                             "type '%s'."),
                             object_ref, type_name(obj_type),
                             replace_ref, type_name(repl_type));
  
@@@ -200,10 -200,10 +200,10 @@@ static int replace_object(const char *o
        struct object_id object, repl;
  
        if (get_oid(object_ref, &object))
 -              return error("Failed to resolve '%s' as a valid ref.",
 +              return error(_("failed to resolve '%s' as a valid ref"),
                             object_ref);
        if (get_oid(replace_ref, &repl))
 -              return error("Failed to resolve '%s' as a valid ref.",
 +              return error(_("failed to resolve '%s' as a valid ref"),
                             replace_ref);
  
        return replace_object_oid(object_ref, &object, replace_ref, &repl, force);
@@@ -222,7 -222,7 +222,7 @@@ static int export_object(const struct o
  
        fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
 -              return error_errno("unable to open %s for writing", filename);
 +              return error_errno(_("unable to open %s for writing"), filename);
  
        argv_array_push(&cmd.args, "--no-replace-objects");
        argv_array_push(&cmd.args, "cat-file");
        cmd.out = fd;
  
        if (run_command(&cmd))
 -              return error("cat-file reported failure");
 +              return error(_("cat-file reported failure"));
        return 0;
  }
  
@@@ -251,7 -251,7 +251,7 @@@ static int import_object(struct object_
  
        fd = open(filename, O_RDONLY);
        if (fd < 0)
 -              return error_errno("unable to open %s for reading", filename);
 +              return error_errno(_("unable to open %s for reading"), filename);
  
        if (!raw && type == OBJ_TREE) {
                const char *argv[] = { "mktree", NULL };
  
                if (start_command(&cmd)) {
                        close(fd);
 -                      return error("unable to spawn mktree");
 +                      return error(_("unable to spawn mktree"));
                }
  
                if (strbuf_read(&result, cmd.out, 41) < 0) {
 -                      error_errno("unable to read from mktree");
 +                      error_errno(_("unable to read from mktree"));
                        close(fd);
                        close(cmd.out);
                        return -1;
  
                if (finish_command(&cmd)) {
                        strbuf_release(&result);
 -                      return error("mktree reported failure");
 +                      return error(_("mktree reported failure"));
                }
                if (get_oid_hex(result.buf, oid) < 0) {
                        strbuf_release(&result);
 -                      return error("mktree did not return an object name");
 +                      return error(_("mktree did not return an object name"));
                }
  
                strbuf_release(&result);
                int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
  
                if (fstat(fd, &st) < 0) {
 -                      error_errno("unable to fstat %s", filename);
 +                      error_errno(_("unable to fstat %s"), filename);
                        close(fd);
                        return -1;
                }
                if (index_fd(oid, fd, &st, type, NULL, flags) < 0)
 -                      return error("unable to write object to database");
 +                      return error(_("unable to write object to database"));
                /* index_fd close()s fd for us */
        }
  
@@@ -315,11 -315,11 +315,11 @@@ static int edit_and_replace(const char 
        struct strbuf ref = STRBUF_INIT;
  
        if (get_oid(object_ref, &old_oid) < 0)
 -              return error("Not a valid object name: '%s'", object_ref);
 +              return error(_("not a valid object name: '%s'"), object_ref);
  
        type = oid_object_info(the_repository, &old_oid, NULL);
        if (type < 0)
 -              return error("unable to get object type for %s",
 +              return error(_("unable to get object type for %s"),
                             oid_to_hex(&old_oid));
  
        if (check_ref_valid(&old_oid, &prev, &ref, force)) {
        }
        if (launch_editor(tmpfile, NULL, NULL) < 0) {
                free(tmpfile);
 -              return error("editing object file failed");
 +              return error(_("editing object file failed"));
        }
        if (import_object(&new_oid, type, raw, tmpfile)) {
                free(tmpfile);
        }
        free(tmpfile);
  
 -      if (!oidcmp(&old_oid, &new_oid))
 -              return error("new object is the same as the old one: '%s'", oid_to_hex(&old_oid));
 +      if (oideq(&old_oid, &new_oid))
 +              return error(_("new object is the same as the old one: '%s'"), oid_to_hex(&old_oid));
  
        return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force);
  }
@@@ -368,7 -368,7 +368,7 @@@ static int replace_parents(struct strbu
                struct object_id oid;
                if (get_oid(argv[i], &oid) < 0) {
                        strbuf_release(&new_parents);
 -                      return error(_("Not a valid object name: '%s'"),
 +                      return error(_("not a valid object name: '%s'"),
                                     argv[i]);
                }
                if (!lookup_commit_reference(the_repository, &oid)) {
@@@ -412,9 -412,9 +412,9 @@@ static int check_one_mergetag(struct co
        for (i = 1; i < mergetag_data->argc; i++) {
                struct object_id oid;
                if (get_oid(mergetag_data->argv[i], &oid) < 0)
 -                      return error(_("Not a valid object name: '%s'"),
 +                      return error(_("not a valid object name: '%s'"),
                                     mergetag_data->argv[i]);
 -              if (!oidcmp(&tag->tagged->oid, &oid))
 +              if (oideq(&tag->tagged->oid, &oid))
                        return 0; /* found */
        }
  
@@@ -442,7 -442,7 +442,7 @@@ static int create_graft(int argc, cons
        unsigned long size;
  
        if (get_oid(old_ref, &old_oid) < 0)
 -              return error(_("Not a valid object name: '%s'"), old_ref);
 +              return error(_("not a valid object name: '%s'"), old_ref);
        commit = lookup_commit_reference(the_repository, &old_oid);
        if (!commit)
                return error(_("could not parse %s"), old_ref);
        }
  
        if (remove_signature(&buf)) {
 -              warning(_("the original commit '%s' has a gpg signature."), old_ref);
 +              warning(_("the original commit '%s' has a gpg signature"), old_ref);
                warning(_("the signature will be removed in the replacement commit!"));
        }
  
  
        strbuf_release(&buf);
  
 -      if (!oidcmp(&old_oid, &new_oid)) {
 +      if (oideq(&old_oid, &new_oid)) {
                if (gentle) {
 -                      warning("graft for '%s' unnecessary", oid_to_hex(&old_oid));
 +                      warning(_("graft for '%s' unnecessary"), oid_to_hex(&old_oid));
                        return 0;
                }
 -              return error("new commit is the same as the old one: '%s'", oid_to_hex(&old_oid));
 +              return error(_("new commit is the same as the old one: '%s'"), oid_to_hex(&old_oid));
        }
  
        return replace_object_oid(old_ref, &old_oid, "replacement", &new_oid, force);
@@@ -553,7 -553,7 +553,7 @@@ int cmd_replace(int argc, const char **
                cmdmode = argc ? MODE_REPLACE : MODE_LIST;
  
        if (format && cmdmode != MODE_LIST)
 -              usage_msg_opt("--format cannot be used when not listing",
 +              usage_msg_opt(_("--format cannot be used when not listing"),
                              git_replace_usage, options);
  
        if (force &&
            cmdmode != MODE_EDIT &&
            cmdmode != MODE_GRAFT &&
            cmdmode != MODE_CONVERT_GRAFT_FILE)
 -              usage_msg_opt("-f only makes sense when writing a replacement",
 +              usage_msg_opt(_("-f only makes sense when writing a replacement"),
                              git_replace_usage, options);
  
        if (raw && cmdmode != MODE_EDIT)
 -              usage_msg_opt("--raw only makes sense with --edit",
 +              usage_msg_opt(_("--raw only makes sense with --edit"),
                              git_replace_usage, options);
  
        switch (cmdmode) {
        case MODE_DELETE:
                if (argc < 1)
 -                      usage_msg_opt("-d needs at least one argument",
 +                      usage_msg_opt(_("-d needs at least one argument"),
                                      git_replace_usage, options);
                return for_each_replace_name(argv, delete_replace_ref);
  
        case MODE_REPLACE:
                if (argc != 2)
 -                      usage_msg_opt("bad number of arguments",
 +                      usage_msg_opt(_("bad number of arguments"),
                                      git_replace_usage, options);
                return replace_object(argv[0], argv[1], force);
  
        case MODE_EDIT:
                if (argc != 1)
 -                      usage_msg_opt("-e needs exactly one argument",
 +                      usage_msg_opt(_("-e needs exactly one argument"),
                                      git_replace_usage, options);
                return edit_and_replace(argv[0], force, raw);
  
        case MODE_GRAFT:
                if (argc < 1)
 -                      usage_msg_opt("-g needs at least one argument",
 +                      usage_msg_opt(_("-g needs at least one argument"),
                                      git_replace_usage, options);
                return create_graft(argc, argv, force, 0);
  
        case MODE_CONVERT_GRAFT_FILE:
                if (argc != 0)
 -                      usage_msg_opt("--convert-graft-file takes no argument",
 +                      usage_msg_opt(_("--convert-graft-file takes no argument"),
                                      git_replace_usage, options);
                return !!convert_graft_file(force);
  
        case MODE_LIST:
                if (argc > 1)
 -                      usage_msg_opt("only one pattern can be given with -l",
 +                      usage_msg_opt(_("only one pattern can be given with -l"),
                                      git_replace_usage, options);
                return list_replace_refs(argv[0], format);
  
diff --combined commit-graph.c
index 5908bd4e3429fe9bc187db82fb2d14625f1bbd1c,4bd1a4abbfd35f7d0e44e1e632261ea722e1525e..a6867586039b507e7802a469a1a3bed82e44bddb
@@@ -13,7 -13,8 +13,9 @@@
  #include "commit-graph.h"
  #include "object-store.h"
  #include "alloc.h"
+ #include "hashmap.h"
+ #include "replace-object.h"
 +#include "progress.h"
  
  #define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
  #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
@@@ -57,6 -58,28 +59,28 @@@ static struct commit_graph *alloc_commi
        return g;
  }
  
+ extern int read_replace_refs;
+ static int commit_graph_compatible(struct repository *r)
+ {
+       if (!r->gitdir)
+               return 0;
+       if (read_replace_refs) {
+               prepare_replace_object(r);
+               if (hashmap_get_size(&r->objects->replace_map->map))
+                       return 0;
+       }
+       prepare_commit_graft(r);
+       if (r->parsed_objects && r->parsed_objects->grafts_nr)
+               return 0;
+       if (is_repository_shallow(r))
+               return 0;
+       return 1;
+ }
  struct commit_graph *load_commit_graph_one(const char *graph_file)
  {
        void *graph_map;
  
        if (graph_size < GRAPH_MIN_SIZE) {
                close(fd);
 -              die("graph file %s is too small", graph_file);
 +              die(_("graph file %s is too small"), graph_file);
        }
        graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
        data = (const unsigned char *)graph_map;
  
        graph_signature = get_be32(data);
        if (graph_signature != GRAPH_SIGNATURE) {
 -              error("graph signature %X does not match signature %X",
 +              error(_("graph signature %X does not match signature %X"),
                      graph_signature, GRAPH_SIGNATURE);
                goto cleanup_fail;
        }
  
        graph_version = *(unsigned char*)(data + 4);
        if (graph_version != GRAPH_VERSION) {
 -              error("graph version %X does not match version %X",
 +              error(_("graph version %X does not match version %X"),
                      graph_version, GRAPH_VERSION);
                goto cleanup_fail;
        }
  
        hash_version = *(unsigned char*)(data + 5);
        if (hash_version != GRAPH_OID_VERSION) {
 -              error("hash version %X does not match version %X",
 +              error(_("hash version %X does not match version %X"),
                      hash_version, GRAPH_OID_VERSION);
                goto cleanup_fail;
        }
                chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH;
  
                if (chunk_offset > graph_size - GIT_MAX_RAWSZ) {
 -                      error("improper chunk offset %08x%08x", (uint32_t)(chunk_offset >> 32),
 +                      error(_("improper chunk offset %08x%08x"), (uint32_t)(chunk_offset >> 32),
                              (uint32_t)chunk_offset);
                        goto cleanup_fail;
                }
                }
  
                if (chunk_repeated) {
 -                      error("chunk id %08x appears multiple times", chunk_id);
 +                      error(_("chunk id %08x appears multiple times"), chunk_id);
                        goto cleanup_fail;
                }
  
@@@ -214,9 -237,8 +238,9 @@@ static int prepare_commit_graph(struct 
                return !!r->objects->commit_graph;
        r->objects->commit_graph_attempted = 1;
  
 -      if (repo_config_get_bool(r, "core.commitgraph", &config_value) ||
 -          !config_value)
 +      if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
 +          (repo_config_get_bool(r, "core.commitgraph", &config_value) ||
 +          !config_value))
                /*
                 * This repository is not configured to use commit graphs, so
                 * do not load one. (But report commit_graph_attempted anyway
                 */
                return 0;
  
+       if (!commit_graph_compatible(r))
+               return 0;
        obj_dir = r->objects->objectdir;
        prepare_commit_graph_one(r, obj_dir);
        prepare_alt_odb(r);
        return !!r->objects->commit_graph;
  }
  
- static void close_commit_graph(void)
 +int generation_numbers_enabled(struct repository *r)
 +{
 +      uint32_t first_generation;
 +      struct commit_graph *g;
 +      if (!prepare_commit_graph(r))
 +             return 0;
 +
 +      g = r->objects->commit_graph;
 +
 +      if (!g->num_commits)
 +              return 0;
 +
 +      first_generation = get_be32(g->chunk_commit_data +
 +                                  g->hash_len + 8) >> 2;
 +
 +      return !!first_generation;
 +}
 +
+ void close_commit_graph(struct repository *r)
  {
-       free_commit_graph(the_repository->objects->commit_graph);
-       the_repository->objects->commit_graph = NULL;
+       free_commit_graph(r->objects->commit_graph);
+       r->objects->commit_graph = NULL;
  }
  
  static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
@@@ -278,7 -285,7 +305,7 @@@ static struct commit_list **insert_pare
        hashcpy(oid.hash, g->chunk_oid_lookup + g->hash_len * pos);
        c = lookup_commit(the_repository, &oid);
        if (!c)
 -              die("could not find commit %s", oid_to_hex(&oid));
 +              die(_("could not find commit %s"), oid_to_hex(&oid));
        c->graph_pos = pos;
        return &commit_list_insert(c, pptr)->next;
  }
@@@ -568,8 -575,6 +595,8 @@@ struct packed_oid_list 
        struct object_id *list;
        int nr;
        int alloc;
 +      struct progress *progress;
 +      int progress_done;
  };
  
  static int add_packed_commits(const struct object_id *oid,
        off_t offset = nth_packed_object_offset(pack, pos);
        struct object_info oi = OBJECT_INFO_INIT;
  
 +      if (list->progress)
 +              display_progress(list->progress, ++list->progress_done);
 +
        oi.typep = &type;
        if (packed_object_info(the_repository, pack, offset, &oi) < 0)
 -              die("unable to get type of object %s", oid_to_hex(oid));
 +              die(_("unable to get type of object %s"), oid_to_hex(oid));
  
        if (type != OBJ_COMMIT)
                return 0;
@@@ -612,18 -614,12 +639,18 @@@ static void add_missing_parents(struct 
        }
  }
  
 -static void close_reachable(struct packed_oid_list *oids)
 +static void close_reachable(struct packed_oid_list *oids, int report_progress)
  {
        int i;
        struct commit *commit;
 +      struct progress *progress = NULL;
 +      int j = 0;
  
 +      if (report_progress)
 +              progress = start_delayed_progress(
 +                      _("Annotating commits in commit graph"), 0);
        for (i = 0; i < oids->nr; i++) {
 +              display_progress(progress, ++j);
                commit = lookup_commit(the_repository, &oids->list[i]);
                if (commit)
                        commit->object.flags |= UNINTERESTING;
         * closure.
         */
        for (i = 0; i < oids->nr; i++) {
 +              display_progress(progress, ++j);
                commit = lookup_commit(the_repository, &oids->list[i]);
  
                if (commit && !parse_commit(commit))
        }
  
        for (i = 0; i < oids->nr; i++) {
 +              display_progress(progress, ++j);
                commit = lookup_commit(the_repository, &oids->list[i]);
  
                if (commit)
                        commit->object.flags &= ~UNINTERESTING;
        }
 +      stop_progress(&progress);
  }
  
 -static void compute_generation_numbers(struct packed_commit_list* commits)
 +static void compute_generation_numbers(struct packed_commit_list* commits,
 +                                     int report_progress)
  {
        int i;
        struct commit_list *list = NULL;
 +      struct progress *progress = NULL;
  
 +      if (report_progress)
 +              progress = start_progress(
 +                      _("Computing commit graph generation numbers"),
 +                      commits->nr);
        for (i = 0; i < commits->nr; i++) {
 +              display_progress(progress, i + 1);
                if (commits->list[i]->generation != GENERATION_NUMBER_INFINITY &&
                    commits->list[i]->generation != GENERATION_NUMBER_ZERO)
                        continue;
                        }
                }
        }
 +      stop_progress(&progress);
  }
  
  static int add_ref_to_list(const char *refname,
        return 0;
  }
  
 -void write_commit_graph_reachable(const char *obj_dir, int append)
 +void write_commit_graph_reachable(const char *obj_dir, int append,
 +                                int report_progress)
  {
        struct string_list list;
  
        string_list_init(&list, 1);
        for_each_ref(add_ref_to_list, &list);
 -      write_commit_graph(obj_dir, NULL, &list, append);
 +      write_commit_graph(obj_dir, NULL, &list, append, report_progress);
  }
  
  void write_commit_graph(const char *obj_dir,
                        struct string_list *pack_indexes,
                        struct string_list *commit_hex,
 -                      int append)
 +                      int append, int report_progress)
  {
        struct packed_oid_list oids;
        struct packed_commit_list commits;
        int num_chunks;
        int num_extra_edges;
        struct commit_list *parent;
 +      struct progress *progress = NULL;
  
+       if (!commit_graph_compatible(the_repository))
+               return;
        oids.nr = 0;
        oids.alloc = approximate_object_count() / 4;
 +      oids.progress = NULL;
 +      oids.progress_done = 0;
  
        if (append) {
                prepare_commit_graph_one(the_repository, obj_dir);
                int dirlen;
                strbuf_addf(&packname, "%s/pack/", obj_dir);
                dirlen = packname.len;
 +              if (report_progress) {
 +                      oids.progress = start_delayed_progress(
 +                              _("Finding commits for commit graph"), 0);
 +                      oids.progress_done = 0;
 +              }
                for (i = 0; i < pack_indexes->nr; i++) {
                        struct packed_git *p;
                        strbuf_setlen(&packname, dirlen);
                        strbuf_addstr(&packname, pack_indexes->items[i].string);
                        p = add_packed_git(packname.buf, packname.len, 1);
                        if (!p)
 -                              die("error adding pack %s", packname.buf);
 +                              die(_("error adding pack %s"), packname.buf);
                        if (open_pack_index(p))
 -                              die("error opening index for %s", packname.buf);
 -                      for_each_object_in_pack(p, add_packed_commits, &oids);
 +                              die(_("error opening index for %s"), packname.buf);
 +                      for_each_object_in_pack(p, add_packed_commits, &oids, 0);
                        close_pack(p);
                }
 +              stop_progress(&oids.progress);
                strbuf_release(&packname);
        }
  
        if (commit_hex) {
 +              if (report_progress)
 +                      progress = start_delayed_progress(
 +                              _("Finding commits for commit graph"),
 +                              commit_hex->nr);
                for (i = 0; i < commit_hex->nr; i++) {
                        const char *end;
                        struct object_id oid;
                        struct commit *result;
  
 +                      display_progress(progress, i + 1);
                        if (commit_hex->items[i].string &&
                            parse_oid_hex(commit_hex->items[i].string, &oid, &end))
                                continue;
                                oids.nr++;
                        }
                }
 +              stop_progress(&progress);
        }
  
 -      if (!pack_indexes && !commit_hex)
 +      if (!pack_indexes && !commit_hex) {
 +              if (report_progress)
 +                      oids.progress = start_delayed_progress(
 +                              _("Finding commits for commit graph"), 0);
                for_each_packed_object(add_packed_commits, &oids, 0);
 +              stop_progress(&oids.progress);
 +      }
  
 -      close_reachable(&oids);
 +      close_reachable(&oids, report_progress);
  
        QSORT(oids.list, oids.nr, commit_compare);
  
        count_distinct = 1;
        for (i = 1; i < oids.nr; i++) {
 -              if (oidcmp(&oids.list[i-1], &oids.list[i]))
 +              if (!oideq(&oids.list[i - 1], &oids.list[i]))
                        count_distinct++;
        }
  
        num_extra_edges = 0;
        for (i = 0; i < oids.nr; i++) {
                int num_parents = 0;
 -              if (i > 0 && !oidcmp(&oids.list[i-1], &oids.list[i]))
 +              if (i > 0 && oideq(&oids.list[i - 1], &oids.list[i]))
                        continue;
  
                commits.list[commits.nr] = lookup_commit(the_repository, &oids.list[i]);
        if (commits.nr >= GRAPH_PARENT_MISSING)
                die(_("too many commits to write graph"));
  
 -      compute_generation_numbers(&commits);
 +      compute_generation_numbers(&commits, report_progress);
  
        graph_name = get_commit_graph_filename(obj_dir);
        if (safe_create_leading_directories(graph_name))
        write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
        write_graph_chunk_large_edges(f, commits.list, commits.nr);
  
-       close_commit_graph();
+       close_commit_graph(the_repository);
        finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
        commit_lock_file(&lk);
  
@@@ -941,7 -908,6 +971,7 @@@ int verify_commit_graph(struct reposito
        int generation_zero = 0;
        struct hashfile *f;
        int devnull;
 +      struct progress *progress = NULL;
  
        if (!g) {
                graph_report("no commit-graph file loaded");
        f = hashfd(devnull, NULL);
        hashwrite(f, g->data, g->data_len - g->hash_len);
        finalize_hashfile(f, checksum.hash, CSUM_CLOSE);
 -      if (hashcmp(checksum.hash, g->data + g->data_len - g->hash_len)) {
 +      if (!hasheq(checksum.hash, g->data + g->data_len - g->hash_len)) {
                graph_report(_("the commit-graph file has incorrect checksum and is likely corrupt"));
                verify_commit_graph_error = VERIFY_COMMIT_GRAPH_ERROR_HASH;
        }
        if (verify_commit_graph_error & ~VERIFY_COMMIT_GRAPH_ERROR_HASH)
                return verify_commit_graph_error;
  
 +      progress = start_progress(_("Verifying commits in commit graph"),
 +                                g->num_commits);
        for (i = 0; i < g->num_commits; i++) {
                struct commit *graph_commit, *odb_commit;
                struct commit_list *graph_parents, *odb_parents;
                uint32_t max_generation = 0;
  
 +              display_progress(progress, i + 1);
                hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
  
                graph_commit = lookup_commit(r, &cur_oid);
                        continue;
                }
  
 -              if (oidcmp(&get_commit_tree_in_graph_one(g, graph_commit)->object.oid,
 +              if (!oideq(&get_commit_tree_in_graph_one(g, graph_commit)->object.oid,
                           get_commit_tree_oid(odb_commit)))
                        graph_report("root tree OID for commit %s in commit-graph is %s != %s",
                                     oid_to_hex(&cur_oid),
                                break;
                        }
  
 -                      if (oidcmp(&graph_parents->item->object.oid, &odb_parents->item->object.oid))
 +                      if (!oideq(&graph_parents->item->object.oid, &odb_parents->item->object.oid))
                                graph_report("commit-graph parent for %s is %s != %s",
                                             oid_to_hex(&cur_oid),
                                             oid_to_hex(&graph_parents->item->object.oid),
                                     graph_commit->date,
                                     odb_commit->date);
        }
 +      stop_progress(&progress);
  
        return verify_commit_graph_error;
  }
diff --combined commit-graph.h
index 5678a8f4cad2ddec9c7c50a31f0a54abc026f951,13d736cdde28987e23378b2465c2c4b8f525e38c..9db40b4d3aadb75b4dcea5027d07d373bdf169e1
@@@ -4,9 -4,6 +4,9 @@@
  #include "git-compat-util.h"
  #include "repository.h"
  #include "string-list.h"
 +#include "cache.h"
 +
 +#define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH"
  
  struct commit;
  
@@@ -54,21 -51,15 +54,22 @@@ struct commit_graph 
  
  struct commit_graph *load_commit_graph_one(const char *graph_file);
  
 -void write_commit_graph_reachable(const char *obj_dir, int append);
 +/*
 + * Return 1 if and only if the repository has a commit-graph
 + * file and generation numbers are computed in that file.
 + */
 +int generation_numbers_enabled(struct repository *r);
 +
 +void write_commit_graph_reachable(const char *obj_dir, int append,
 +                                int report_progress);
  void write_commit_graph(const char *obj_dir,
                        struct string_list *pack_indexes,
                        struct string_list *commit_hex,
 -                      int append);
 +                      int append, int report_progress);
  
  int verify_commit_graph(struct repository *r, struct commit_graph *g);
  
+ void close_commit_graph(struct repository *);
  void free_commit_graph(struct commit_graph *);
  
  #endif
diff --combined commit.c
index d0f199e12286983c3f9f23b6567dc78a2ceeaf79,ef9a2cbb23d1af449b568242d40b0b69d456b823..dc8a39d52a1c31f979068a34bbdeeca14f3a7547
+++ b/commit.c
@@@ -46,7 -46,7 +46,7 @@@ struct commit *lookup_commit_or_die(con
        struct commit *c = lookup_commit_reference(the_repository, oid);
        if (!c)
                die(_("could not parse %s"), ref_name);
 -      if (oidcmp(oid, &c->object.oid)) {
 +      if (!oideq(oid, &c->object.oid)) {
                warning(_("%s %s is not a commit!"),
                        ref_name, oid_to_hex(oid));
        }
@@@ -209,7 -209,7 +209,7 @@@ static int read_graft_file(struct repos
        return 0;
  }
  
static void prepare_commit_graft(struct repository *r)
+ void prepare_commit_graft(struct repository *r)
  {
        char *graft_file;
  
@@@ -656,7 -656,7 +656,7 @@@ struct commit *pop_commit(struct commit
  define_commit_slab(indegree_slab, int);
  
  /* record author-date for each commit object */
 -define_commit_slab(author_date_slab, unsigned long);
 +define_commit_slab(author_date_slab, timestamp_t);
  
  static void record_author_date(struct author_date_slab *author_date,
                               struct commit *commit)
@@@ -843,6 -843,364 +843,6 @@@ void sort_in_topological_order(struct c
                clear_author_date_slab(&author_date);
  }
  
 -/* merge-base stuff */
 -
 -/* Remember to update object flag allocation in object.h */
 -#define PARENT1               (1u<<16)
 -#define PARENT2               (1u<<17)
 -#define STALE         (1u<<18)
 -#define RESULT                (1u<<19)
 -
 -static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
 -
 -static int queue_has_nonstale(struct prio_queue *queue)
 -{
 -      int i;
 -      for (i = 0; i < queue->nr; i++) {
 -              struct commit *commit = queue->array[i].data;
 -              if (!(commit->object.flags & STALE))
 -                      return 1;
 -      }
 -      return 0;
 -}
 -
 -/* all input commits in one and twos[] must have been parsed! */
 -static struct commit_list *paint_down_to_common(struct commit *one, int n,
 -                                              struct commit **twos,
 -                                              int min_generation)
 -{
 -      struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
 -      struct commit_list *result = NULL;
 -      int i;
 -      uint32_t last_gen = GENERATION_NUMBER_INFINITY;
 -
 -      one->object.flags |= PARENT1;
 -      if (!n) {
 -              commit_list_append(one, &result);
 -              return result;
 -      }
 -      prio_queue_put(&queue, one);
 -
 -      for (i = 0; i < n; i++) {
 -              twos[i]->object.flags |= PARENT2;
 -              prio_queue_put(&queue, twos[i]);
 -      }
 -
 -      while (queue_has_nonstale(&queue)) {
 -              struct commit *commit = prio_queue_get(&queue);
 -              struct commit_list *parents;
 -              int flags;
 -
 -              if (commit->generation > last_gen)
 -                      BUG("bad generation skip %8x > %8x at %s",
 -                          commit->generation, last_gen,
 -                          oid_to_hex(&commit->object.oid));
 -              last_gen = commit->generation;
 -
 -              if (commit->generation < min_generation)
 -                      break;
 -
 -              flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
 -              if (flags == (PARENT1 | PARENT2)) {
 -                      if (!(commit->object.flags & RESULT)) {
 -                              commit->object.flags |= RESULT;
 -                              commit_list_insert_by_date(commit, &result);
 -                      }
 -                      /* Mark parents of a found merge stale */
 -                      flags |= STALE;
 -              }
 -              parents = commit->parents;
 -              while (parents) {
 -                      struct commit *p = parents->item;
 -                      parents = parents->next;
 -                      if ((p->object.flags & flags) == flags)
 -                              continue;
 -                      if (parse_commit(p))
 -                              return NULL;
 -                      p->object.flags |= flags;
 -                      prio_queue_put(&queue, p);
 -              }
 -      }
 -
 -      clear_prio_queue(&queue);
 -      return result;
 -}
 -
 -static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
 -{
 -      struct commit_list *list = NULL;
 -      struct commit_list *result = NULL;
 -      int i;
 -
 -      for (i = 0; i < n; i++) {
 -              if (one == twos[i])
 -                      /*
 -                       * We do not mark this even with RESULT so we do not
 -                       * have to clean it up.
 -                       */
 -                      return commit_list_insert(one, &result);
 -      }
 -
 -      if (parse_commit(one))
 -              return NULL;
 -      for (i = 0; i < n; i++) {
 -              if (parse_commit(twos[i]))
 -                      return NULL;
 -      }
 -
 -      list = paint_down_to_common(one, n, twos, 0);
 -
 -      while (list) {
 -              struct commit *commit = pop_commit(&list);
 -              if (!(commit->object.flags & STALE))
 -                      commit_list_insert_by_date(commit, &result);
 -      }
 -      return result;
 -}
 -
 -struct commit_list *get_octopus_merge_bases(struct commit_list *in)
 -{
 -      struct commit_list *i, *j, *k, *ret = NULL;
 -
 -      if (!in)
 -              return ret;
 -
 -      commit_list_insert(in->item, &ret);
 -
 -      for (i = in->next; i; i = i->next) {
 -              struct commit_list *new_commits = NULL, *end = NULL;
 -
 -              for (j = ret; j; j = j->next) {
 -                      struct commit_list *bases;
 -                      bases = get_merge_bases(i->item, j->item);
 -                      if (!new_commits)
 -                              new_commits = bases;
 -                      else
 -                              end->next = bases;
 -                      for (k = bases; k; k = k->next)
 -                              end = k;
 -              }
 -              ret = new_commits;
 -      }
 -      return ret;
 -}
 -
 -static int remove_redundant(struct commit **array, int cnt)
 -{
 -      /*
 -       * Some commit in the array may be an ancestor of
 -       * another commit.  Move such commit to the end of
 -       * the array, and return the number of commits that
 -       * are independent from each other.
 -       */
 -      struct commit **work;
 -      unsigned char *redundant;
 -      int *filled_index;
 -      int i, j, filled;
 -
 -      work = xcalloc(cnt, sizeof(*work));
 -      redundant = xcalloc(cnt, 1);
 -      ALLOC_ARRAY(filled_index, cnt - 1);
 -
 -      for (i = 0; i < cnt; i++)
 -              parse_commit(array[i]);
 -      for (i = 0; i < cnt; i++) {
 -              struct commit_list *common;
 -              uint32_t min_generation = array[i]->generation;
 -
 -              if (redundant[i])
 -                      continue;
 -              for (j = filled = 0; j < cnt; j++) {
 -                      if (i == j || redundant[j])
 -                              continue;
 -                      filled_index[filled] = j;
 -                      work[filled++] = array[j];
 -
 -                      if (array[j]->generation < min_generation)
 -                              min_generation = array[j]->generation;
 -              }
 -              common = paint_down_to_common(array[i], filled, work,
 -                                            min_generation);
 -              if (array[i]->object.flags & PARENT2)
 -                      redundant[i] = 1;
 -              for (j = 0; j < filled; j++)
 -                      if (work[j]->object.flags & PARENT1)
 -                              redundant[filled_index[j]] = 1;
 -              clear_commit_marks(array[i], all_flags);
 -              clear_commit_marks_many(filled, work, all_flags);
 -              free_commit_list(common);
 -      }
 -
 -      /* Now collect the result */
 -      COPY_ARRAY(work, array, cnt);
 -      for (i = filled = 0; i < cnt; i++)
 -              if (!redundant[i])
 -                      array[filled++] = work[i];
 -      for (j = filled, i = 0; i < cnt; i++)
 -              if (redundant[i])
 -                      array[j++] = work[i];
 -      free(work);
 -      free(redundant);
 -      free(filled_index);
 -      return filled;
 -}
 -
 -static struct commit_list *get_merge_bases_many_0(struct commit *one,
 -                                                int n,
 -                                                struct commit **twos,
 -                                                int cleanup)
 -{
 -      struct commit_list *list;
 -      struct commit **rslt;
 -      struct commit_list *result;
 -      int cnt, i;
 -
 -      result = merge_bases_many(one, n, twos);
 -      for (i = 0; i < n; i++) {
 -              if (one == twos[i])
 -                      return result;
 -      }
 -      if (!result || !result->next) {
 -              if (cleanup) {
 -                      clear_commit_marks(one, all_flags);
 -                      clear_commit_marks_many(n, twos, all_flags);
 -              }
 -              return result;
 -      }
 -
 -      /* There are more than one */
 -      cnt = commit_list_count(result);
 -      rslt = xcalloc(cnt, sizeof(*rslt));
 -      for (list = result, i = 0; list; list = list->next)
 -              rslt[i++] = list->item;
 -      free_commit_list(result);
 -
 -      clear_commit_marks(one, all_flags);
 -      clear_commit_marks_many(n, twos, all_flags);
 -
 -      cnt = remove_redundant(rslt, cnt);
 -      result = NULL;
 -      for (i = 0; i < cnt; i++)
 -              commit_list_insert_by_date(rslt[i], &result);
 -      free(rslt);
 -      return result;
 -}
 -
 -struct commit_list *get_merge_bases_many(struct commit *one,
 -                                       int n,
 -                                       struct commit **twos)
 -{
 -      return get_merge_bases_many_0(one, n, twos, 1);
 -}
 -
 -struct commit_list *get_merge_bases_many_dirty(struct commit *one,
 -                                             int n,
 -                                             struct commit **twos)
 -{
 -      return get_merge_bases_many_0(one, n, twos, 0);
 -}
 -
 -struct commit_list *get_merge_bases(struct commit *one, struct commit *two)
 -{
 -      return get_merge_bases_many_0(one, 1, &two, 1);
 -}
 -
 -/*
 - * Is "commit" a descendant of one of the elements on the "with_commit" list?
 - */
 -int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
 -{
 -      if (!with_commit)
 -              return 1;
 -      while (with_commit) {
 -              struct commit *other;
 -
 -              other = with_commit->item;
 -              with_commit = with_commit->next;
 -              if (in_merge_bases(other, commit))
 -                      return 1;
 -      }
 -      return 0;
 -}
 -
 -/*
 - * Is "commit" an ancestor of one of the "references"?
 - */
 -int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
 -{
 -      struct commit_list *bases;
 -      int ret = 0, i;
 -      uint32_t min_generation = GENERATION_NUMBER_INFINITY;
 -
 -      if (parse_commit(commit))
 -              return ret;
 -      for (i = 0; i < nr_reference; i++) {
 -              if (parse_commit(reference[i]))
 -                      return ret;
 -              if (reference[i]->generation < min_generation)
 -                      min_generation = reference[i]->generation;
 -      }
 -
 -      if (commit->generation > min_generation)
 -              return ret;
 -
 -      bases = paint_down_to_common(commit, nr_reference, reference, commit->generation);
 -      if (commit->object.flags & PARENT2)
 -              ret = 1;
 -      clear_commit_marks(commit, all_flags);
 -      clear_commit_marks_many(nr_reference, reference, all_flags);
 -      free_commit_list(bases);
 -      return ret;
 -}
 -
 -/*
 - * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
 - */
 -int in_merge_bases(struct commit *commit, struct commit *reference)
 -{
 -      return in_merge_bases_many(commit, 1, &reference);
 -}
 -
 -struct commit_list *reduce_heads(struct commit_list *heads)
 -{
 -      struct commit_list *p;
 -      struct commit_list *result = NULL, **tail = &result;
 -      struct commit **array;
 -      int num_head, i;
 -
 -      if (!heads)
 -              return NULL;
 -
 -      /* Uniquify */
 -      for (p = heads; p; p = p->next)
 -              p->item->object.flags &= ~STALE;
 -      for (p = heads, num_head = 0; p; p = p->next) {
 -              if (p->item->object.flags & STALE)
 -                      continue;
 -              p->item->object.flags |= STALE;
 -              num_head++;
 -      }
 -      array = xcalloc(num_head, sizeof(*array));
 -      for (p = heads, i = 0; p; p = p->next) {
 -              if (p->item->object.flags & STALE) {
 -                      array[i++] = p->item;
 -                      p->item->object.flags &= ~STALE;
 -              }
 -      }
 -      num_head = remove_redundant(array, num_head);
 -      for (i = 0; i < num_head; i++)
 -              tail = &commit_list_insert(array[i], tail)->next;
 -      free(array);
 -      return result;
 -}
 -
 -void reduce_heads_replace(struct commit_list **heads)
 -{
 -      struct commit_list *result = reduce_heads(*heads);
 -      free_commit_list(*heads);
 -      *heads = result;
 -}
 -
  static const char gpg_sig_header[] = "gpgsig";
  static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
  
@@@ -1426,10 -1784,10 +1426,10 @@@ const char *find_commit_header(const ch
   * Returns the number of bytes from the tail to ignore, to be fed as
   * the second parameter to append_signoff().
   */
 -int ignore_non_trailer(const char *buf, size_t len)
 +size_t ignore_non_trailer(const char *buf, size_t len)
  {
 -      int boc = 0;
 -      int bol = 0;
 +      size_t boc = 0;
 +      size_t bol = 0;
        int in_old_conflicts_block = 0;
        size_t cutoff = wt_status_locate_end(buf, len);
  
diff --combined commit.h
index 2b1a73438873f20e2c9e570121a2e024d7762fbe,5459e279fe02aec791ce3c8761c64b8e0deb3c34..1d260d62f57a24864986252892faa89c17572210
+++ b/commit.h
@@@ -202,8 -202,16 +202,9 @@@ typedef int (*each_commit_graft_fn)(con
  
  struct commit_graft *read_graft_line(struct strbuf *line);
  int register_commit_graft(struct repository *r, struct commit_graft *, int);
+ void prepare_commit_graft(struct repository *r);
  struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
  
 -extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2);
 -extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos);
 -extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 -
 -/* To be used only when object flags after this call no longer matter */
 -extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos);
 -
  /* largest positive number a signed 32-bit integer can contain */
  #define INFINITE_DEPTH 0x7fffffff
  
@@@ -251,10 -259,32 +252,10 @@@ extern int delayed_reachability_test(st
  extern void prune_shallow(int show_only);
  extern struct trace_key trace_shallow;
  
 -int is_descendant_of(struct commit *, struct commit_list *);
 -int in_merge_bases(struct commit *, struct commit *);
 -int in_merge_bases_many(struct commit *, int, struct commit **);
 -
  extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
  extern int run_add_interactive(const char *revision, const char *patch_mode,
                               const struct pathspec *pathspec);
  
 -/*
 - * Takes a list of commits and returns a new list where those
 - * have been removed that can be reached from other commits in
 - * the list. It is useful for, e.g., reducing the commits
 - * randomly thrown at the git-merge command and removing
 - * redundant commits that the user shouldn't have given to it.
 - *
 - * This function destroys the STALE bit of the commit objects'
 - * flags.
 - */
 -extern struct commit_list *reduce_heads(struct commit_list *heads);
 -
 -/*
 - * Like `reduce_heads()`, except it replaces the list. Use this
 - * instead of `foo = reduce_heads(foo);` to avoid memory leaks.
 - */
 -extern void reduce_heads_replace(struct commit_list **heads);
 -
  struct commit_extra_header {
        struct commit_extra_header *next;
        char *key;
@@@ -293,7 -323,7 +294,7 @@@ extern const char *find_commit_header(c
                                      size_t *out_len);
  
  /* Find the end of the log message, the right place for a new trailer. */
 -extern int ignore_non_trailer(const char *buf, size_t len);
 +extern size_t ignore_non_trailer(const char *buf, size_t len);
  
  typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
                                 void *cb_data);
diff --combined refs.c
index a7a75b4cc0b9b93ec3a56340a711900c76b506ce,c5a5f727e8aa354d881e5528f471f395f4a6ed12..bbcac921b6d78fd598380608e7195675aae1d4f6
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -189,7 -189,7 +189,7 @@@ int ref_resolves_to_object(const char *
        if (flags & REF_ISBROKEN)
                return 0;
        if (!has_sha1_file(oid->hash)) {
 -              error("%s does not point to a valid object!", refname);
 +              error(_("%s does not point to a valid object!"), refname);
                return 0;
        }
        return 1;
@@@ -490,24 -490,16 +490,24 @@@ static const char *ref_rev_parse_rules[
        NULL
  };
  
 +#define NUM_REV_PARSE_RULES (ARRAY_SIZE(ref_rev_parse_rules) - 1)
 +
 +/*
 + * Is it possible that the caller meant full_name with abbrev_name?
 + * If so return a non-zero value to signal "yes"; the magnitude of
 + * the returned value gives the precedence used for disambiguation.
 + *
 + * If abbrev_name cannot mean full_name, return 0.
 + */
  int refname_match(const char *abbrev_name, const char *full_name)
  {
        const char **p;
        const int abbrev_name_len = strlen(abbrev_name);
 +      const int num_rules = NUM_REV_PARSE_RULES;
  
 -      for (p = ref_rev_parse_rules; *p; p++) {
 -              if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
 -                      return 1;
 -              }
 -      }
 +      for (p = ref_rev_parse_rules; *p; p++)
 +              if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name)))
 +                      return &ref_rev_parse_rules[num_rules] - p;
  
        return 0;
  }
@@@ -576,9 -568,9 +576,9 @@@ int expand_ref(const char *str, int len
                        if (!warn_ambiguous_refs)
                                break;
                } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) {
 -                      warning("ignoring dangling symref %s.", fullref.buf);
 +                      warning(_("ignoring dangling symref %s"), fullref.buf);
                } else if ((flag & REF_ISBROKEN) && strchr(fullref.buf, '/')) {
 -                      warning("ignoring broken ref %s.", fullref.buf);
 +                      warning(_("ignoring broken ref %s"), fullref.buf);
                }
        }
        strbuf_release(&fullref);
@@@ -682,7 -674,7 +682,7 @@@ static int write_pseudoref(const char *
        fd = hold_lock_file_for_update_timeout(&lock, filename, 0,
                                               get_files_ref_lock_timeout_ms());
        if (fd < 0) {
 -              strbuf_addf(err, "could not open '%s' for writing: %s",
 +              strbuf_addf(err, _("could not open '%s' for writing: %s"),
                            filename, strerror(errno));
                goto done;
        }
  
                if (read_ref(pseudoref, &actual_old_oid)) {
                        if (!is_null_oid(old_oid)) {
 -                              strbuf_addf(err, "could not read ref '%s'",
 +                              strbuf_addf(err, _("could not read ref '%s'"),
                                            pseudoref);
                                rollback_lock_file(&lock);
                                goto done;
                        }
                } else if (is_null_oid(old_oid)) {
 -                      strbuf_addf(err, "ref '%s' already exists",
 +                      strbuf_addf(err, _("ref '%s' already exists"),
                                    pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
 -              } else if (oidcmp(&actual_old_oid, old_oid)) {
 -                      strbuf_addf(err, "unexpected object ID when writing '%s'",
 +              } else if (!oideq(&actual_old_oid, old_oid)) {
 +                      strbuf_addf(err, _("unexpected object ID when writing '%s'"),
                                    pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
        }
  
        if (write_in_full(fd, buf.buf, buf.len) < 0) {
 -              strbuf_addf(err, "could not write to '%s'", filename);
 +              strbuf_addf(err, _("could not write to '%s'"), filename);
                rollback_lock_file(&lock);
                goto done;
        }
@@@ -743,9 -735,9 +743,9 @@@ static int delete_pseudoref(const char 
                        return -1;
                }
                if (read_ref(pseudoref, &actual_old_oid))
 -                      die("could not read ref '%s'", pseudoref);
 -              if (oidcmp(&actual_old_oid, old_oid)) {
 -                      error("unexpected object ID when deleting '%s'",
 +                      die(_("could not read ref '%s'"), pseudoref);
 +              if (!oideq(&actual_old_oid, old_oid)) {
 +                      error(_("unexpected object ID when deleting '%s'"),
                              pseudoref);
                        rollback_lock_file(&lock);
                        return -1;
@@@ -875,14 -867,14 +875,14 @@@ static int read_ref_at_ent(struct objec
                 */
                if (!is_null_oid(&cb->ooid)) {
                        oidcpy(cb->oid, noid);
 -                      if (oidcmp(&cb->ooid, noid))
 -                              warning("Log for ref %s has gap after %s.",
 +                      if (!oideq(&cb->ooid, noid))
 +                              warning(_("log for ref %s has gap after %s"),
                                        cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
                }
                else if (cb->date == cb->at_time)
                        oidcpy(cb->oid, noid);
 -              else if (oidcmp(noid, cb->oid))
 -                      warning("Log for ref %s unexpectedly ended on %s.",
 +              else if (!oideq(noid, cb->oid))
 +                      warning(_("log for ref %s unexpectedly ended on %s"),
                                cb->refname, show_date(cb->date, cb->tz,
                                                       DATE_MODE(RFC2822)));
                oidcpy(&cb->ooid, ooid);
@@@ -940,7 -932,7 +940,7 @@@ int read_ref_at(const char *refname, un
                if (flags & GET_OID_QUIETLY)
                        exit(128);
                else
 -                      die("Log for %s is empty.", refname);
 +                      die(_("log for %s is empty"), refname);
        }
        if (cb.found_it)
                return 0;
@@@ -1032,7 -1024,7 +1032,7 @@@ int ref_transaction_update(struct ref_t
        if ((new_oid && !is_null_oid(new_oid)) ?
            check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
            !refname_is_safe(refname)) {
 -              strbuf_addf(err, "refusing to update ref with bad name '%s'",
 +              strbuf_addf(err, _("refusing to update ref with bad name '%s'"),
                            refname);
                return -1;
        }
@@@ -1108,7 -1100,7 +1108,7 @@@ int refs_update_ref(struct ref_store *r
                }
        }
        if (ret) {
 -              const char *str = "update_ref failed for ref '%s': %s";
 +              const char *str = _("update_ref failed for ref '%s': %s");
  
                switch (onerr) {
                case UPDATE_REFS_MSG_ON_ERR:
@@@ -1394,17 -1386,50 +1394,50 @@@ struct ref_iterator *refs_ref_iterator_
   * non-zero value, stop the iteration and return that value;
   * otherwise, return 0.
   */
+ static int do_for_each_repo_ref(struct repository *r, const char *prefix,
+                               each_repo_ref_fn fn, int trim, int flags,
+                               void *cb_data)
+ {
+       struct ref_iterator *iter;
+       struct ref_store *refs = get_main_ref_store(r);
+       if (!refs)
+               return 0;
+       iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
+       return do_for_each_repo_ref_iterator(r, iter, fn, cb_data);
+ }
+ struct do_for_each_ref_help {
+       each_ref_fn *fn;
+       void *cb_data;
+ };
+ static int do_for_each_ref_helper(struct repository *r,
+                                 const char *refname,
+                                 const struct object_id *oid,
+                                 int flags,
+                                 void *cb_data)
+ {
+       struct do_for_each_ref_help *hp = cb_data;
+       return hp->fn(refname, oid, flags, hp->cb_data);
+ }
  static int do_for_each_ref(struct ref_store *refs, const char *prefix,
                           each_ref_fn fn, int trim, int flags, void *cb_data)
  {
        struct ref_iterator *iter;
+       struct do_for_each_ref_help hp = { fn, cb_data };
  
        if (!refs)
                return 0;
  
        iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
  
-       return do_for_each_ref_iterator(iter, fn, cb_data);
+       return do_for_each_repo_ref_iterator(the_repository, iter,
+                                       do_for_each_ref_helper, &hp);
  }
  
  int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
@@@ -1449,12 -1474,11 +1482,11 @@@ int refs_for_each_fullref_in(struct ref
        return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
  }
  
- int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data)
+ int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
  {
-       return do_for_each_ref(get_main_ref_store(r),
-                              git_replace_ref_base, fn,
-                              strlen(git_replace_ref_base),
-                              DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+       return do_for_each_repo_ref(r, git_replace_ref_base, fn,
+                                   strlen(git_replace_ref_base),
+                                   DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
  }
  
  int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
@@@ -1850,7 -1874,7 +1882,7 @@@ int ref_update_reject_duplicates(struc
  
                if (!cmp) {
                        strbuf_addf(err,
 -                                  "multiple updates for ref '%s' not allowed.",
 +                                  _("multiple updates for ref '%s' not allowed"),
                                    refnames->items[i].string);
                        return 1;
                } else if (cmp > 0) {
@@@ -1978,13 -2002,13 +2010,13 @@@ int refs_verify_refname_available(struc
                        continue;
  
                if (!refs_read_raw_ref(refs, dirname.buf, &oid, &referent, &type)) {
 -                      strbuf_addf(err, "'%s' exists; cannot create '%s'",
 +                      strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
                                    dirname.buf, refname);
                        goto cleanup;
                }
  
                if (extras && string_list_has_string(extras, dirname.buf)) {
 -                      strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
 +                      strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
                                    refname, dirname.buf);
                        goto cleanup;
                }
                    string_list_has_string(skip, iter->refname))
                        continue;
  
 -              strbuf_addf(err, "'%s' exists; cannot create '%s'",
 +              strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
                            iter->refname, refname);
                ref_iterator_abort(iter);
                goto cleanup;
  
        extra_refname = find_descendant_ref(dirname.buf, extras, skip);
        if (extra_refname)
 -              strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
 +              strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
                            refname, extra_refname);
        else
                ret = 0;
@@@ -2033,10 -2057,12 +2065,12 @@@ cleanup
  int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
  {
        struct ref_iterator *iter;
+       struct do_for_each_ref_help hp = { fn, cb_data };
  
        iter = refs->be->reflog_iterator_begin(refs);
  
-       return do_for_each_ref_iterator(iter, fn, cb_data);
+       return do_for_each_repo_ref_iterator(the_repository, iter,
+                                            do_for_each_ref_helper, &hp);
  }
  
  int for_each_reflog(each_ref_fn fn, void *cb_data)
diff --combined refs.h
index bd52c1bbae3a68fe8ca8f9e6cae7cc54bdbf9852,a0a18223a146f0f676bb4165955333309c372a92..6cc0397679fd55bfdfc72b7a7f4cbf3ee5d028a0
--- 1/refs.h
--- 2/refs.h
+++ b/refs.h
@@@ -3,10 -3,8 +3,10 @@@
  
  struct object_id;
  struct ref_store;
 +struct repository;
  struct strbuf;
  struct string_list;
 +struct string_list_item;
  struct worktree;
  
  /*
@@@ -276,6 -274,16 +276,16 @@@ struct ref_transaction
  typedef int each_ref_fn(const char *refname,
                        const struct object_id *oid, int flags, void *cb_data);
  
+ /*
+  * The same as each_ref_fn, but also with a repository argument that
+  * contains the repository associated with the callback.
+  */
+ typedef int each_repo_ref_fn(struct repository *r,
+                            const char *refname,
+                            const struct object_id *oid,
+                            int flags,
+                            void *cb_data);
  /*
   * The following functions invoke the specified callback function for
   * each reference indicated.  If the function ever returns a nonzero
@@@ -309,7 -317,7 +319,7 @@@ int for_each_fullref_in(const char *pre
  int for_each_tag_ref(each_ref_fn fn, void *cb_data);
  int for_each_branch_ref(each_ref_fn fn, void *cb_data);
  int for_each_remote_ref(each_ref_fn fn, void *cb_data);
- int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data);
+ int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
  int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
  int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
                         const char *prefix, void *cb_data);
diff --combined replace-object.c
index 4ec77ce41848311a912256046bd2bf8dc9ee63c0,9821f1477edab21d98bfeb89edd301f6d6257753..e295e87943102c2a1ad903aea1a634d34bf603a0
@@@ -6,7 -6,8 +6,8 @@@
  #include "repository.h"
  #include "commit.h"
  
- static int register_replace_ref(const char *refname,
+ static int register_replace_ref(struct repository *r,
+                               const char *refname,
                                const struct object_id *oid,
                                int flag, void *cb_data)
  {
@@@ -17,7 -18,7 +18,7 @@@
  
        if (get_oid_hex(hash, &repl_obj->original.oid)) {
                free(repl_obj);
 -              warning("bad replace ref name: %s", refname);
 +              warning(_("bad replace ref name: %s"), refname);
                return 0;
        }
  
        oidcpy(&repl_obj->replacement, oid);
  
        /* Register new object */
-       if (oidmap_put(the_repository->objects->replace_map, repl_obj))
+       if (oidmap_put(r->objects->replace_map, repl_obj))
 -              die("duplicate replace ref: %s", refname);
 +              die(_("duplicate replace ref: %s"), refname);
  
        return 0;
  }
  
static void prepare_replace_object(struct repository *r)
+ void prepare_replace_object(struct repository *r)
  {
        if (r->objects->replace_map)
                return;
@@@ -69,5 -70,5 +70,5 @@@ const struct object_id *do_lookup_repla
                        return cur;
                cur = &repl_obj->replacement;
        }
 -      die("replace depth too high for object %s", oid_to_hex(oid));
 +      die(_("replace depth too high for object %s"), oid_to_hex(oid));
  }
diff --combined t/t5318-commit-graph.sh
index 75fe09521f370e2da5a69ef0b80f9535122d06da,6aee861f7807f81a069e532908f91e7e7dd224d2..db8dcafca3c764f17ca323be91af65d991ccb73a
@@@ -8,8 -8,7 +8,8 @@@ test_expect_success 'setup full repo' 
        cd "$TRASH_DIRECTORY/full" &&
        git init &&
        git config core.commitGraph true &&
 -      objdir=".git/objects"
 +      objdir=".git/objects" &&
 +      test_oid_init
  '
  
  test_expect_success 'verify graph with no graph file' '
@@@ -135,7 -134,7 +135,7 @@@ test_expect_success 'Add one more commi
        git branch commits/8 &&
        ls $objdir/pack | grep idx >existing-idx &&
        git repack &&
 -      ls $objdir/pack| grep idx | grep -v --file=existing-idx >new-idx
 +      ls $objdir/pack| grep idx | grep -v -existing-idx >new-idx
  '
  
  # Current graph structure:
@@@ -255,11 -254,71 +255,71 @@@ test_expect_success 'check that gc comp
        git config gc.writeCommitGraph true &&
        git gc &&
        cp $objdir/info/commit-graph commit-graph-after-gc &&
 -      ! test_cmp commit-graph-before-gc commit-graph-after-gc &&
 +      ! test_cmp_bin commit-graph-before-gc commit-graph-after-gc &&
        git commit-graph write --reachable &&
 -      test_cmp commit-graph-after-gc $objdir/info/commit-graph
 +      test_cmp_bin commit-graph-after-gc $objdir/info/commit-graph
  '
  
+ test_expect_success 'replace-objects invalidates commit-graph' '
+       cd "$TRASH_DIRECTORY" &&
+       test_when_finished rm -rf replace &&
+       git clone full replace &&
+       (
+               cd replace &&
+               git commit-graph write --reachable &&
+               test_path_is_file .git/objects/info/commit-graph &&
+               git replace HEAD~1 HEAD~2 &&
+               git -c core.commitGraph=false log >expect &&
+               git -c core.commitGraph=true log >actual &&
+               test_cmp expect actual &&
+               git commit-graph write --reachable &&
+               git -c core.commitGraph=false --no-replace-objects log >expect &&
+               git -c core.commitGraph=true --no-replace-objects log >actual &&
+               test_cmp expect actual &&
+               rm -rf .git/objects/info/commit-graph &&
+               git commit-graph write --reachable &&
+               test_path_is_file .git/objects/info/commit-graph
+       )
+ '
+ test_expect_success 'commit grafts invalidate commit-graph' '
+       cd "$TRASH_DIRECTORY" &&
+       test_when_finished rm -rf graft &&
+       git clone full graft &&
+       (
+               cd graft &&
+               git commit-graph write --reachable &&
+               test_path_is_file .git/objects/info/commit-graph &&
+               H1=$(git rev-parse --verify HEAD~1) &&
+               H3=$(git rev-parse --verify HEAD~3) &&
+               echo "$H1 $H3" >.git/info/grafts &&
+               git -c core.commitGraph=false log >expect &&
+               git -c core.commitGraph=true log >actual &&
+               test_cmp expect actual &&
+               git commit-graph write --reachable &&
+               git -c core.commitGraph=false --no-replace-objects log >expect &&
+               git -c core.commitGraph=true --no-replace-objects log >actual &&
+               test_cmp expect actual &&
+               rm -rf .git/objects/info/commit-graph &&
+               git commit-graph write --reachable &&
+               test_path_is_missing .git/objects/info/commit-graph
+       )
+ '
+ test_expect_success 'replace-objects invalidates commit-graph' '
+       cd "$TRASH_DIRECTORY" &&
+       test_when_finished rm -rf shallow &&
+       git clone --depth 2 "file://$TRASH_DIRECTORY/full" shallow &&
+       (
+               cd shallow &&
+               git commit-graph write --reachable &&
+               test_path_is_missing .git/objects/info/commit-graph &&
+               git fetch origin --unshallow &&
+               git commit-graph write --reachable &&
+               test_path_is_file .git/objects/info/commit-graph
+       )
+ '
  # the verify tests below expect the commit-graph to contain
  # exactly the commits reachable from the commits/8 branch.
  # If the file changes the set of commits in the list, then the
@@@ -274,7 -333,7 +334,7 @@@ test_expect_success 'git commit-graph v
  
  NUM_COMMITS=9
  NUM_OCTOPUS_EDGES=2
 -HASH_LEN=20
 +HASH_LEN="$(test_oid rawsz)"
  GRAPH_BYTE_VERSION=4
  GRAPH_BYTE_HASH=5
  GRAPH_BYTE_CHUNK_COUNT=6
@@@ -445,27 -504,25 +505,27 @@@ test_expect_success 'setup non-the_repo
  test_expect_success 'parse_commit_in_graph works for non-the_repository' '
        test-tool repository parse_commit_in_graph \
                repo/.git repo "$(git -C repo rev-parse two)" >actual &&
 -      echo $(git -C repo log --pretty="%ct" -1) \
 -              $(git -C repo rev-parse one) >expect &&
 +      {
 +              git -C repo log --pretty=format:"%ct " -1 &&
 +              git -C repo rev-parse one
 +      } >expect &&
        test_cmp expect actual &&
  
        test-tool repository parse_commit_in_graph \
                repo/.git repo "$(git -C repo rev-parse one)" >actual &&
 -      echo $(git -C repo log --pretty="%ct" -1 one) >expect &&
 +      git -C repo log --pretty="%ct" -1 one >expect &&
        test_cmp expect actual
  '
  
  test_expect_success 'get_commit_tree_in_graph works for non-the_repository' '
        test-tool repository get_commit_tree_in_graph \
                repo/.git repo "$(git -C repo rev-parse two)" >actual &&
 -      echo $(git -C repo rev-parse two^{tree}) >expect &&
 +      git -C repo rev-parse two^{tree} >expect &&
        test_cmp expect actual &&
  
        test-tool repository get_commit_tree_in_graph \
                repo/.git repo "$(git -C repo rev-parse one)" >actual &&
 -      echo $(git -C repo rev-parse one^{tree}) >expect &&
 +      git -C repo rev-parse one^{tree} >expect &&
        test_cmp expect actual
  '
  
diff --combined upload-pack.c
index 62a1000f4401f4314f072b61046d7a19d9d04bae,2ae9d9bb475214070529e22cc3f186afebbc6c7c..540778d1ddb85b494a92bce75993c4d13d47ef82
  #include "quote.h"
  #include "upload-pack.h"
  #include "serve.h"
+ #include "commit-graph.h"
 +#include "commit-reach.h"
  
  /* Remember to update object flag allocation in object.h */
  #define THEY_HAVE     (1u << 11)
  #define OUR_REF               (1u << 12)
  #define WANTED                (1u << 13)
  #define COMMON_KNOWN  (1u << 14)
 -#define REACHABLE     (1u << 15)
  
  #define SHALLOW               (1u << 16)
  #define NOT_SHALLOW   (1u << 17)
@@@ -337,16 -338,64 +338,16 @@@ static int got_oid(const char *hex, str
        return 0;
  }
  
 -static int reachable(struct commit *want)
 -{
 -      struct prio_queue work = { compare_commits_by_commit_date };
 -
 -      prio_queue_put(&work, want);
 -      while (work.nr) {
 -              struct commit_list *list;
 -              struct commit *commit = prio_queue_get(&work);
 -
 -              if (commit->object.flags & THEY_HAVE) {
 -                      want->object.flags |= COMMON_KNOWN;
 -                      break;
 -              }
 -              if (!commit->object.parsed)
 -                      parse_object(the_repository, &commit->object.oid);
 -              if (commit->object.flags & REACHABLE)
 -                      continue;
 -              commit->object.flags |= REACHABLE;
 -              if (commit->date < oldest_have)
 -                      continue;
 -              for (list = commit->parents; list; list = list->next) {
 -                      struct commit *parent = list->item;
 -                      if (!(parent->object.flags & REACHABLE))
 -                              prio_queue_put(&work, parent);
 -              }
 -      }
 -      want->object.flags |= REACHABLE;
 -      clear_commit_marks(want, REACHABLE);
 -      clear_prio_queue(&work);
 -      return (want->object.flags & COMMON_KNOWN);
 -}
 -
  static int ok_to_give_up(void)
  {
 -      int i;
 +      uint32_t min_generation = GENERATION_NUMBER_ZERO;
  
        if (!have_obj.nr)
                return 0;
  
 -      for (i = 0; i < want_obj.nr; i++) {
 -              struct object *want = want_obj.objects[i].item;
 -
 -              if (want->flags & COMMON_KNOWN)
 -                      continue;
 -              want = deref_tag(the_repository, want, "a want line", 0);
 -              if (!want || want->type != OBJ_COMMIT) {
 -                      /* no way to tell if this is reachable by
 -                       * looking at the ancestry chain alone, so
 -                       * leave a note to ourselves not to worry about
 -                       * this object anymore.
 -                       */
 -                      want_obj.objects[i].item->flags |= COMMON_KNOWN;
 -                      continue;
 -              }
 -              if (!reachable((struct commit *)want))
 -                      return 0;
 -      }
 -      return 1;
 +      return can_all_from_reach_with_flag(&want_obj, THEY_HAVE,
 +                                          COMMON_KNOWN, oldest_have,
 +                                          min_generation);
  }
  
  static int get_common_commits(void)
@@@ -692,6 -741,7 +693,7 @@@ static void deepen_by_rev_list(int ac, 
  {
        struct commit_list *result;
  
+       close_commit_graph(the_repository);
        result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
        send_shallow(result);
        free_commit_list(result);