commit-graph write: don't die if the existing graph is corrupt
[gitweb.git] / commit-graph.c
index 47e9be0a3aad883c17221b11c972b8376a70a555..34ecaaf857fb802bf15ca7a1154aac01043bfa38 100644 (file)
@@ -80,25 +80,30 @@ static int commit_graph_compatible(struct repository *r)
        return 1;
 }
 
-struct commit_graph *load_commit_graph_one(const char *graph_file)
+int open_commit_graph(const char *graph_file, int *fd, struct stat *st)
+{
+       *fd = git_open(graph_file);
+       if (*fd < 0)
+               return 0;
+       if (fstat(*fd, st)) {
+               close(*fd);
+               return 0;
+       }
+       return 1;
+}
+
+struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st)
 {
        void *graph_map;
        size_t graph_size;
-       struct stat st;
        struct commit_graph *ret;
-       int fd = git_open(graph_file);
 
-       if (fd < 0)
-               return NULL;
-       if (fstat(fd, &st)) {
-               close(fd);
-               return NULL;
-       }
-       graph_size = xsize_t(st.st_size);
+       graph_size = xsize_t(st->st_size);
 
        if (graph_size < GRAPH_MIN_SIZE) {
                close(fd);
-               die(_("graph file %s is too small"), graph_file);
+               error(_("commit-graph file is too small"));
+               return NULL;
        }
        graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
        ret = parse_commit_graph(graph_map, fd, graph_size);
@@ -106,12 +111,41 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
        if (!ret) {
                munmap(graph_map, graph_size);
                close(fd);
-               exit(1);
        }
 
        return ret;
 }
 
+static int verify_commit_graph_lite(struct commit_graph *g)
+{
+       /*
+        * Basic validation shared between parse_commit_graph()
+        * which'll be called every time the graph is used, and the
+        * much more expensive verify_commit_graph() used by
+        * "commit-graph verify".
+        *
+        * There should only be very basic checks here to ensure that
+        * we don't e.g. segfault in fill_commit_in_graph(), but
+        * because this is a very hot codepath nothing that e.g. loops
+        * over g->num_commits, or runs a checksum on the commit-graph
+        * itself.
+        */
+       if (!g->chunk_oid_fanout) {
+               error("commit-graph is missing the OID Fanout chunk");
+               return 1;
+       }
+       if (!g->chunk_oid_lookup) {
+               error("commit-graph is missing the OID Lookup chunk");
+               return 1;
+       }
+       if (!g->chunk_commit_data) {
+               error("commit-graph is missing the Commit Data chunk");
+               return 1;
+       }
+
+       return 0;
+}
+
 struct commit_graph *parse_commit_graph(void *graph_map, int fd,
                                        size_t graph_size)
 {
@@ -233,9 +267,25 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
                last_chunk_offset = chunk_offset;
        }
 
+       if (verify_commit_graph_lite(graph))
+               return NULL;
+
        return graph;
 }
 
+static struct commit_graph *load_commit_graph_one(const char *graph_file)
+{
+
+       struct stat st;
+       int fd;
+       int open_ok = open_commit_graph(graph_file, &fd, &st);
+
+       if (!open_ok)
+               return NULL;
+
+       return load_commit_graph_one_fd_st(fd, &st);
+}
+
 static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)
 {
        char *graph_name;
@@ -261,6 +311,10 @@ static int prepare_commit_graph(struct repository *r)
        struct object_directory *odb;
        int config_value;
 
+       if (git_env_bool(GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD, 0))
+               die("dying as requested by the '%s' variable on commit-graph load!",
+                   GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD);
+
        if (r->objects->commit_graph_attempted)
                return !!r->objects->commit_graph;
        r->objects->commit_graph_attempted = 1;
@@ -525,7 +579,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
                uint32_t packedDate[2];
                display_progress(progress, ++*progress_cnt);
 
-               parse_commit(*list);
+               parse_commit_no_graph(*list);
                hashwrite(f, get_commit_tree_oid(*list)->hash, hash_len);
 
                parent = (*list)->parents;
@@ -722,7 +776,7 @@ static void close_reachable(struct packed_oid_list *oids, int report_progress)
                display_progress(progress, i + 1);
                commit = lookup_commit(the_repository, &oids->list[i]);
 
-               if (commit && !parse_commit(commit))
+               if (commit && !parse_commit_no_graph(commit))
                        add_missing_parents(oids, commit);
        }
        stop_progress(&progress);
@@ -971,7 +1025,7 @@ void write_commit_graph(const char *obj_dir,
                        continue;
 
                commits.list[commits.nr] = lookup_commit(the_repository, &oids.list[i]);
-               parse_commit(commits.list[commits.nr]);
+               parse_commit_no_graph(commits.list[commits.nr]);
 
                for (parent = commits.list[commits.nr]->parents;
                     parent; parent = parent->next)
@@ -1089,15 +1143,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g)
                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");
-
+       verify_commit_graph_error = verify_commit_graph_lite(g);
        if (verify_commit_graph_error)
                return verify_commit_graph_error;