Merge branch 'cc/test-ref-store-typofix'
[gitweb.git] / commit-graph.c
index 0d6ba6da2d76fa3a5d43dae74fc1bb7b652c6df0..18bd2b6df79e51105a17a1d2784608ba0370de36 100644 (file)
 #define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */
 #define GRAPH_CHUNKID_LARGEEDGES 0x45444745 /* "EDGE" */
 
-#define GRAPH_DATA_WIDTH 36
+#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
 
 #define GRAPH_VERSION_1 0x1
 #define GRAPH_VERSION GRAPH_VERSION_1
 
-#define GRAPH_OID_VERSION_SHA1 1
-#define GRAPH_OID_LEN_SHA1 GIT_SHA1_RAWSZ
-#define GRAPH_OID_VERSION GRAPH_OID_VERSION_SHA1
-#define GRAPH_OID_LEN GRAPH_OID_LEN_SHA1
-
 #define GRAPH_OCTOPUS_EDGES_NEEDED 0x80000000
 #define GRAPH_EDGE_LAST_MASK 0x7fffffff
 #define GRAPH_PARENT_NONE 0x70000000
 #define GRAPH_FANOUT_SIZE (4 * 256)
 #define GRAPH_CHUNKLOOKUP_WIDTH 12
 #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \
-                       + GRAPH_FANOUT_SIZE + GRAPH_OID_LEN)
+                       + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)
 
 char *get_commit_graph_filename(const char *obj_dir)
 {
        return xstrfmt("%s/info/commit-graph", obj_dir);
 }
 
+static uint8_t oid_version(void)
+{
+       return 1;
+}
+
 static struct commit_graph *alloc_commit_graph(void)
 {
        struct commit_graph *g = xcalloc(1, sizeof(*g));
@@ -83,16 +83,10 @@ static int commit_graph_compatible(struct repository *r)
 struct commit_graph *load_commit_graph_one(const char *graph_file)
 {
        void *graph_map;
-       const unsigned char *data, *chunk_lookup;
        size_t graph_size;
        struct stat st;
-       uint32_t i;
-       struct commit_graph *graph;
+       struct commit_graph *ret;
        int fd = git_open(graph_file);
-       uint64_t last_chunk_offset;
-       uint32_t last_chunk_id;
-       uint32_t graph_signature;
-       unsigned char graph_version, hash_version;
 
        if (fd < 0)
                return NULL;
@@ -107,32 +101,60 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
                die(_("graph file %s is too small"), graph_file);
        }
        graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       ret = parse_commit_graph(graph_map, fd, graph_size);
+
+       if (!ret) {
+               munmap(graph_map, graph_size);
+               close(fd);
+               exit(1);
+       }
+
+       return ret;
+}
+
+struct commit_graph *parse_commit_graph(void *graph_map, int fd,
+                                       size_t graph_size)
+{
+       const unsigned char *data, *chunk_lookup;
+       uint32_t i;
+       struct commit_graph *graph;
+       uint64_t last_chunk_offset;
+       uint32_t last_chunk_id;
+       uint32_t graph_signature;
+       unsigned char graph_version, hash_version;
+
+       if (!graph_map)
+               return NULL;
+
+       if (graph_size < GRAPH_MIN_SIZE)
+               return NULL;
+
        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"),
                      graph_signature, GRAPH_SIGNATURE);
-               goto cleanup_fail;
+               return NULL;
        }
 
        graph_version = *(unsigned char*)(data + 4);
        if (graph_version != GRAPH_VERSION) {
                error(_("graph version %X does not match version %X"),
                      graph_version, GRAPH_VERSION);
-               goto cleanup_fail;
+               return NULL;
        }
 
        hash_version = *(unsigned char*)(data + 5);
-       if (hash_version != GRAPH_OID_VERSION) {
+       if (hash_version != oid_version()) {
                error(_("hash version %X does not match version %X"),
-                     hash_version, GRAPH_OID_VERSION);
-               goto cleanup_fail;
+                     hash_version, oid_version());
+               return NULL;
        }
 
        graph = alloc_commit_graph();
 
-       graph->hash_len = GRAPH_OID_LEN;
+       graph->hash_len = the_hash_algo->rawsz;
        graph->num_chunks = *(unsigned char*)(data + 6);
        graph->graph_fd = fd;
        graph->data = graph_map;
@@ -142,16 +164,27 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
        last_chunk_offset = 8;
        chunk_lookup = data + 8;
        for (i = 0; i < graph->num_chunks; i++) {
-               uint32_t chunk_id = get_be32(chunk_lookup + 0);
-               uint64_t chunk_offset = get_be64(chunk_lookup + 4);
+               uint32_t chunk_id;
+               uint64_t chunk_offset;
                int chunk_repeated = 0;
 
+               if (data + graph_size - chunk_lookup <
+                   GRAPH_CHUNKLOOKUP_WIDTH) {
+                       error(_("chunk lookup table entry missing; graph file may be incomplete"));
+                       free(graph);
+                       return NULL;
+               }
+
+               chunk_id = get_be32(chunk_lookup + 0);
+               chunk_offset = get_be64(chunk_lookup + 4);
+
                chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH;
 
-               if (chunk_offset > graph_size - GIT_MAX_RAWSZ) {
+               if (chunk_offset > graph_size - the_hash_algo->rawsz) {
                        error(_("improper chunk offset %08x%08x"), (uint32_t)(chunk_offset >> 32),
                              (uint32_t)chunk_offset);
-                       goto cleanup_fail;
+                       free(graph);
+                       return NULL;
                }
 
                switch (chunk_id) {
@@ -186,7 +219,8 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
 
                if (chunk_repeated) {
                        error(_("chunk id %08x appears multiple times"), chunk_id);
-                       goto cleanup_fail;
+                       free(graph);
+                       return NULL;
                }
 
                if (last_chunk_id == GRAPH_CHUNKID_OIDLOOKUP)
@@ -200,11 +234,6 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
        }
 
        return graph;
-
-cleanup_fail:
-       munmap(graph_map, graph_size);
-       close(fd);
-       exit(1);
 }
 
 static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)
@@ -288,7 +317,8 @@ static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t
                            g->chunk_oid_lookup, g->hash_len, pos);
 }
 
-static struct commit_list **insert_parent_or_die(struct commit_graph *g,
+static struct commit_list **insert_parent_or_die(struct repository *r,
+                                                struct commit_graph *g,
                                                 uint64_t pos,
                                                 struct commit_list **pptr)
 {
@@ -299,7 +329,7 @@ static struct commit_list **insert_parent_or_die(struct commit_graph *g,
                die("invalid parent position %"PRIu64, pos);
 
        hashcpy(oid.hash, g->chunk_oid_lookup + g->hash_len * pos);
-       c = lookup_commit(the_repository, &oid);
+       c = lookup_commit(r, &oid);
        if (!c)
                die(_("could not find commit %s"), oid_to_hex(&oid));
        c->graph_pos = pos;
@@ -313,7 +343,9 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g,
        item->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
 }
 
-static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t pos)
+static int fill_commit_in_graph(struct repository *r,
+                               struct commit *item,
+                               struct commit_graph *g, uint32_t pos)
 {
        uint32_t edge_value;
        uint32_t *parent_data_ptr;
@@ -337,13 +369,13 @@ static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uin
        edge_value = get_be32(commit_data + g->hash_len);
        if (edge_value == GRAPH_PARENT_NONE)
                return 1;
-       pptr = insert_parent_or_die(g, edge_value, pptr);
+       pptr = insert_parent_or_die(r, g, edge_value, pptr);
 
        edge_value = get_be32(commit_data + g->hash_len + 4);
        if (edge_value == GRAPH_PARENT_NONE)
                return 1;
        if (!(edge_value & GRAPH_OCTOPUS_EDGES_NEEDED)) {
-               pptr = insert_parent_or_die(g, edge_value, pptr);
+               pptr = insert_parent_or_die(r, g, edge_value, pptr);
                return 1;
        }
 
@@ -351,7 +383,7 @@ static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uin
                          4 * (uint64_t)(edge_value & GRAPH_EDGE_LAST_MASK));
        do {
                edge_value = get_be32(parent_data_ptr);
-               pptr = insert_parent_or_die(g,
+               pptr = insert_parent_or_die(r, g,
                                            edge_value & GRAPH_EDGE_LAST_MASK,
                                            pptr);
                parent_data_ptr++;
@@ -370,7 +402,9 @@ static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uin
        }
 }
 
-static int parse_commit_in_graph_one(struct commit_graph *g, struct commit *item)
+static int parse_commit_in_graph_one(struct repository *r,
+                                    struct commit_graph *g,
+                                    struct commit *item)
 {
        uint32_t pos;
 
@@ -378,7 +412,7 @@ static int parse_commit_in_graph_one(struct commit_graph *g, struct commit *item
                return 1;
 
        if (find_commit_in_graph(item, g, &pos))
-               return fill_commit_in_graph(item, g, pos);
+               return fill_commit_in_graph(r, item, g, pos);
 
        return 0;
 }
@@ -387,7 +421,7 @@ int parse_commit_in_graph(struct repository *r, struct commit *item)
 {
        if (!prepare_commit_graph(r))
                return 0;
-       return parse_commit_in_graph_one(r->objects->commit_graph, item);
+       return parse_commit_in_graph_one(r, r->objects->commit_graph, item);
 }
 
 void load_commit_graph_info(struct repository *r, struct commit *item)
@@ -399,19 +433,22 @@ void load_commit_graph_info(struct repository *r, struct commit *item)
                fill_commit_graph_info(item, r->objects->commit_graph, pos);
 }
 
-static struct tree *load_tree_for_commit(struct commit_graph *g, struct commit *c)
+static struct tree *load_tree_for_commit(struct repository *r,
+                                        struct commit_graph *g,
+                                        struct commit *c)
 {
        struct object_id oid;
        const unsigned char *commit_data = g->chunk_commit_data +
                                           GRAPH_DATA_WIDTH * (c->graph_pos);
 
        hashcpy(oid.hash, commit_data);
-       c->maybe_tree = lookup_tree(the_repository, &oid);
+       c->maybe_tree = lookup_tree(r, &oid);
 
        return c->maybe_tree;
 }
 
-static struct tree *get_commit_tree_in_graph_one(struct commit_graph *g,
+static struct tree *get_commit_tree_in_graph_one(struct repository *r,
+                                                struct commit_graph *g,
                                                 const struct commit *c)
 {
        if (c->maybe_tree)
@@ -419,12 +456,12 @@ static struct tree *get_commit_tree_in_graph_one(struct commit_graph *g,
        if (c->graph_pos == COMMIT_NOT_FROM_GRAPH)
                BUG("get_commit_tree_in_graph_one called from non-commit-graph commit");
 
-       return load_tree_for_commit(g, (struct commit *)c);
+       return load_tree_for_commit(r, g, (struct commit *)c);
 }
 
 struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit *c)
 {
-       return get_commit_tree_in_graph_one(r->objects->commit_graph, c);
+       return get_commit_tree_in_graph_one(r, r->objects->commit_graph, c);
 }
 
 static void write_graph_chunk_fanout(struct hashfile *f,
@@ -773,6 +810,7 @@ void write_commit_graph(const char *obj_dir,
        int num_extra_edges;
        struct commit_list *parent;
        struct progress *progress = NULL;
+       const unsigned hashsz = the_hash_algo->rawsz;
 
        if (!commit_graph_compatible(the_repository))
                return;
@@ -918,7 +956,7 @@ void write_commit_graph(const char *obj_dir,
        hashwrite_be32(f, GRAPH_SIGNATURE);
 
        hashwrite_u8(f, GRAPH_VERSION);
-       hashwrite_u8(f, GRAPH_OID_VERSION);
+       hashwrite_u8(f, oid_version());
        hashwrite_u8(f, num_chunks);
        hashwrite_u8(f, 0); /* unused padding byte */
 
@@ -933,8 +971,8 @@ void write_commit_graph(const char *obj_dir,
 
        chunk_offsets[0] = 8 + (num_chunks + 1) * GRAPH_CHUNKLOOKUP_WIDTH;
        chunk_offsets[1] = chunk_offsets[0] + GRAPH_FANOUT_SIZE;
-       chunk_offsets[2] = chunk_offsets[1] + GRAPH_OID_LEN * commits.nr;
-       chunk_offsets[3] = chunk_offsets[2] + (GRAPH_OID_LEN + 16) * commits.nr;
+       chunk_offsets[2] = chunk_offsets[1] + hashsz * commits.nr;
+       chunk_offsets[3] = chunk_offsets[2] + (hashsz + 16) * commits.nr;
        chunk_offsets[4] = chunk_offsets[3] + 4 * num_extra_edges;
 
        for (i = 0; i <= num_chunks; i++) {
@@ -947,8 +985,8 @@ void write_commit_graph(const char *obj_dir,
        }
 
        write_graph_chunk_fanout(f, commits.list, commits.nr);
-       write_graph_chunk_oids(f, GRAPH_OID_LEN, commits.list, commits.nr);
-       write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
+       write_graph_chunk_oids(f, hashsz, commits.list, commits.nr);
+       write_graph_chunk_data(f, hashsz, commits.list, commits.nr);
        write_graph_chunk_large_edges(f, commits.list, commits.nr);
 
        close_commit_graph(the_repository);
@@ -1034,7 +1072,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g)
                }
 
                graph_commit = lookup_commit(r, &cur_oid);
-               if (!parse_commit_in_graph_one(g, graph_commit))
+               if (!parse_commit_in_graph_one(r, g, graph_commit))
                        graph_report("failed to parse %s from commit-graph",
                                     oid_to_hex(&cur_oid));
        }
@@ -1070,7 +1108,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g)
                        continue;
                }
 
-               if (!oideq(&get_commit_tree_in_graph_one(g, graph_commit)->object.oid,
+               if (!oideq(&get_commit_tree_in_graph_one(r, 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),