Merge branch 'ds/commit-graph-with-grafts' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 21 Nov 2018 13:57:47 +0000 (22:57 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 21 Nov 2018 13:57:47 +0000 (22:57 +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/replace.c
commit-graph.c
commit-graph.h
commit.c
commit.h
refs.c
refs.h
replace-object.c
t/t5318-commit-graph.sh
diff --combined builtin/replace.c
index 4f05791f3e895f78ba511dd6571bd09abab9269c,b5861a0ee91f82f6e7bc7641bec140e0ee3ed06c..17868a92dcfccce335d69854f54f0ef5d3a9ca1b
@@@ -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));
 +              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,7 -412,7 +412,7 @@@ 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))
                        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!"));
        }
  
  
        if (!oidcmp(&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 8a1bec7b8aa420dd3d4ecadc95dee31029533c07,4bd1a4abbfd35f7d0e44e1e632261ea722e1525e..7cfa779dcbdcf9a3e2137cc54c1c9ef62c5f6895
@@@ -13,6 -13,8 +13,8 @@@
  #include "commit-graph.h"
  #include "object-store.h"
  #include "alloc.h"
+ #include "hashmap.h"
+ #include "replace-object.h"
  
  #define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
  #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
@@@ -56,6 -58,28 +58,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;
                }
  
@@@ -223,6 -247,9 +247,9 @@@ static int prepare_commit_graph(struct 
                 */
                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)
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)
@@@ -258,7 -285,7 +285,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;
  }
@@@ -562,7 -589,7 +589,7 @@@ static int add_packed_commits(const str
  
        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;
@@@ -693,6 -720,9 +720,9 @@@ void write_commit_graph(const char *obj
        int num_extra_edges;
        struct commit_list *parent;
  
+       if (!commit_graph_compatible(the_repository))
+               return;
        oids.nr = 0;
        oids.alloc = approximate_object_count() / 4;
  
                        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);
                }
                strbuf_release(&packname);
        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);
  
diff --combined commit-graph.h
index eea62f8c0ee53b56630a1a2b0c2c716b4cd63670,13d736cdde28987e23378b2465c2c4b8f525e38c..b62d1b08373633afd545f7d8734cbd0d61963bb6
@@@ -4,7 -4,6 +4,7 @@@
  #include "git-compat-util.h"
  #include "repository.h"
  #include "string-list.h"
 +#include "cache.h"
  
  struct commit;
  
@@@ -60,6 -59,7 +60,7 @@@ void write_commit_graph(const char *obj
  
  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 db679c2adfd766a385f5406e6413ad13ba1e27af,ef9a2cbb23d1af449b568242d40b0b69d456b823..fb4c99cb8908b53c2bbba43ef08de0e2e217533e
+++ b/commit.c
@@@ -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)
@@@ -874,9 -874,6 +874,9 @@@ static struct commit_list *paint_down_t
        int i;
        uint32_t last_gen = GENERATION_NUMBER_INFINITY;
  
 +      if (!min_generation)
 +              queue.compare = compare_commits_by_commit_date;
 +
        one->object.flags |= PARENT1;
        if (!n) {
                commit_list_append(one, &result);
                struct commit_list *parents;
                int flags;
  
 -              if (commit->generation > last_gen)
 +              if (min_generation && commit->generation > last_gen)
                        BUG("bad generation skip %8x > %8x at %s",
                            commit->generation, last_gen,
                            oid_to_hex(&commit->object.oid));
@@@ -1787,10 -1784,10 +1787,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 43f93b973dffe3bf8e56aa175e66304c72b0e30a,5459e279fe02aec791ce3c8761c64b8e0deb3c34..12b8b2d6543c758a6e96c0ba2ee287ef0d048e77
+++ b/commit.h
@@@ -202,6 -202,7 +202,7 @@@ 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);
@@@ -322,7 -323,7 +323,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 de81c7be7ca8d3ca033b34a61f33b0bff069932f,c5a5f727e8aa354d881e5528f471f395f4a6ed12..9a318c8456c3fe8d157cf6c7c6aca5b635d75230
--- 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'",
 +                      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);
 +                      die(_("could not read ref '%s'"), pseudoref);
                if (oidcmp(&actual_old_oid, old_oid)) {
 -                      error("unexpected object ID when deleting '%s'",
 +                      error(_("unexpected object ID when deleting '%s'"),
                              pseudoref);
                        rollback_lock_file(&lock);
                        return -1;
@@@ -876,13 -868,13 +876,13 @@@ 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.",
 +                              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.",
 +                      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 0c500f7ca2641a2752f5d4819bb11efcf5f588bf,6aee861f7807f81a069e532908f91e7e7dd224d2..2799969f3e2a15fc636ed042c96d8a846e01ebe8
@@@ -134,7 -134,7 +134,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:
@@@ -254,11 -254,71 +254,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
@@@ -444,27 -504,25 +504,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
  '