commit-graph: verify corrupt OID fanout and lookup
[gitweb.git] / commit-graph.c
index b63a1fc85eaded844fcb1a2634067f9509b5937c..b34f62f2777534c20f79ac20ddaf67b3d233f14d 100644 (file)
 
 #define GRAPH_LAST_EDGE 0x80000000
 
+#define GRAPH_HEADER_SIZE 8
 #define GRAPH_FANOUT_SIZE (4 * 256)
 #define GRAPH_CHUNKLOOKUP_WIDTH 12
-#define GRAPH_MIN_SIZE (5 * GRAPH_CHUNKLOOKUP_WIDTH + GRAPH_FANOUT_SIZE + \
-                       GRAPH_OID_LEN + 8)
+#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \
+                       + GRAPH_FANOUT_SIZE + GRAPH_OID_LEN)
 
 char *get_commit_graph_filename(const char *obj_dir)
 {
@@ -313,7 +314,7 @@ static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uin
        }
 }
 
-int parse_commit_in_graph(struct commit *item)
+static int parse_commit_in_graph_one(struct commit_graph *g, struct commit *item)
 {
        uint32_t pos;
 
@@ -321,9 +322,21 @@ int parse_commit_in_graph(struct commit *item)
                return 0;
        if (item->object.parsed)
                return 1;
+
+       if (find_commit_in_graph(item, g, &pos))
+               return fill_commit_in_graph(item, g, pos);
+
+       return 0;
+}
+
+int parse_commit_in_graph(struct commit *item)
+{
+       if (!core_commit_graph)
+               return 0;
+
        prepare_commit_graph();
-       if (commit_graph && find_commit_in_graph(item, commit_graph, &pos))
-               return fill_commit_in_graph(item, commit_graph, pos);
+       if (commit_graph)
+               return parse_commit_in_graph_one(commit_graph, item);
        return 0;
 }
 
@@ -349,14 +362,20 @@ static struct tree *load_tree_for_commit(struct commit_graph *g, struct commit *
        return c->maybe_tree;
 }
 
-struct tree *get_commit_tree_in_graph(const struct commit *c)
+static struct tree *get_commit_tree_in_graph_one(struct commit_graph *g,
+                                                const struct commit *c)
 {
        if (c->maybe_tree)
                return c->maybe_tree;
        if (c->graph_pos == COMMIT_NOT_FROM_GRAPH)
-               BUG("get_commit_tree_in_graph called from non-commit-graph commit");
+               BUG("get_commit_tree_in_graph_one called from non-commit-graph commit");
 
-       return load_tree_for_commit(commit_graph, (struct commit *)c);
+       return load_tree_for_commit(g, (struct commit *)c);
+}
+
+struct tree *get_commit_tree_in_graph(const struct commit *c)
+{
+       return get_commit_tree_in_graph_one(commit_graph, c);
 }
 
 static void write_graph_chunk_fanout(struct hashfile *f,
@@ -808,3 +827,71 @@ void write_commit_graph(const char *obj_dir,
        oids.alloc = 0;
        oids.nr = 0;
 }
+
+static int verify_commit_graph_error;
+
+static void graph_report(const char *fmt, ...)
+{
+       va_list ap;
+
+       verify_commit_graph_error = 1;
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       fprintf(stderr, "\n");
+       va_end(ap);
+}
+
+int verify_commit_graph(struct repository *r, struct commit_graph *g)
+{
+       uint32_t i, cur_fanout_pos = 0;
+       struct object_id prev_oid, cur_oid;
+
+       if (!g) {
+               graph_report("no commit-graph file loaded");
+               return 1;
+       }
+
+       verify_commit_graph_error = 0;
+
+       if (!g->chunk_oid_fanout)
+               graph_report("commit-graph is missing the OID Fanout chunk");
+       if (!g->chunk_oid_lookup)
+               graph_report("commit-graph is missing the OID Lookup chunk");
+       if (!g->chunk_commit_data)
+               graph_report("commit-graph is missing the Commit Data chunk");
+
+       if (verify_commit_graph_error)
+               return verify_commit_graph_error;
+
+       for (i = 0; i < g->num_commits; i++) {
+               hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
+
+               if (i && oidcmp(&prev_oid, &cur_oid) >= 0)
+                       graph_report("commit-graph has incorrect OID order: %s then %s",
+                                    oid_to_hex(&prev_oid),
+                                    oid_to_hex(&cur_oid));
+
+               oidcpy(&prev_oid, &cur_oid);
+
+               while (cur_oid.hash[0] > cur_fanout_pos) {
+                       uint32_t fanout_value = get_be32(g->chunk_oid_fanout + cur_fanout_pos);
+
+                       if (i != fanout_value)
+                               graph_report("commit-graph has incorrect fanout value: fanout[%d] = %u != %u",
+                                            cur_fanout_pos, fanout_value, i);
+                       cur_fanout_pos++;
+               }
+       }
+
+       while (cur_fanout_pos < 256) {
+               uint32_t fanout_value = get_be32(g->chunk_oid_fanout + cur_fanout_pos);
+
+               if (g->num_commits != fanout_value)
+                       graph_report("commit-graph has incorrect fanout value: fanout[%d] = %u != %u",
+                                    cur_fanout_pos, fanout_value, i);
+
+               cur_fanout_pos++;
+       }
+
+       return verify_commit_graph_error;
+}