commit-graph: merge commit-graph chains
[gitweb.git] / commit-graph.c
index 6f7961d0716f2cb4ecf4ede97c66ad3a4f647b4c..fb3100921cdcd4675231b81f71f6d1db209096d7 100644 (file)
@@ -300,12 +300,18 @@ static struct commit_graph *load_commit_graph_one(const char *graph_file)
 
        struct stat st;
        int fd;
+       struct commit_graph *g;
        int open_ok = open_commit_graph(graph_file, &fd, &st);
 
        if (!open_ok)
                return NULL;
 
-       return load_commit_graph_one_fd_st(fd, &st);
+       g = load_commit_graph_one_fd_st(fd, &st);
+
+       if (g)
+               g->filename = xstrdup(graph_file);
+
+       return g;
 }
 
 static struct commit_graph *load_commit_graph_v1(struct repository *r, const char *obj_dir)
@@ -730,8 +736,19 @@ struct write_commit_graph_context {
        struct progress *progress;
        int progress_done;
        uint64_t progress_cnt;
+
+       char *base_graph_name;
+       int num_commit_graphs_before;
+       int num_commit_graphs_after;
+       char **commit_graph_filenames_before;
+       char **commit_graph_filenames_after;
+       char **commit_graph_hash_after;
+       uint32_t new_num_commits_in_base;
+       struct commit_graph *new_base_graph;
+
        unsigned append:1,
-                report_progress:1;
+                report_progress:1,
+                split:1;
 };
 
 static void write_graph_chunk_fanout(struct hashfile *f,
@@ -801,6 +818,16 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
                                              ctx->commits.nr,
                                              commit_to_sha1);
 
+                       if (edge_value >= 0)
+                               edge_value += ctx->new_num_commits_in_base;
+                       else {
+                               uint32_t pos;
+                               if (find_commit_in_graph(parent->item,
+                                                        ctx->new_base_graph,
+                                                        &pos))
+                                       edge_value = pos;
+                       }
+
                        if (edge_value < 0)
                                BUG("missing parent %s for commit %s",
                                    oid_to_hex(&parent->item->object.oid),
@@ -821,6 +848,17 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
                                              ctx->commits.list,
                                              ctx->commits.nr,
                                              commit_to_sha1);
+
+                       if (edge_value >= 0)
+                               edge_value += ctx->new_num_commits_in_base;
+                       else {
+                               uint32_t pos;
+                               if (find_commit_in_graph(parent->item,
+                                                        ctx->new_base_graph,
+                                                        &pos))
+                                       edge_value = pos;
+                       }
+
                        if (edge_value < 0)
                                BUG("missing parent %s for commit %s",
                                    oid_to_hex(&parent->item->object.oid),
@@ -878,6 +916,16 @@ static void write_graph_chunk_extra_edges(struct hashfile *f,
                                                  ctx->commits.nr,
                                                  commit_to_sha1);
 
+                       if (edge_value >= 0)
+                               edge_value += ctx->new_num_commits_in_base;
+                       else {
+                               uint32_t pos;
+                               if (find_commit_in_graph(parent->item,
+                                                        ctx->new_base_graph,
+                                                        &pos))
+                                       edge_value = pos;
+                       }
+
                        if (edge_value < 0)
                                BUG("missing parent %s for commit %s",
                                    oid_to_hex(&parent->item->object.oid),
@@ -969,7 +1017,13 @@ static void close_reachable(struct write_commit_graph_context *ctx)
                display_progress(ctx->progress, i + 1);
                commit = lookup_commit(ctx->r, &ctx->oids.list[i]);
 
-               if (commit && !parse_commit_no_graph(commit))
+               if (!commit)
+                       continue;
+               if (ctx->split) {
+                       if (!parse_commit(commit) &&
+                           commit->graph_pos == COMMIT_NOT_FROM_GRAPH)
+                               add_missing_parents(ctx, commit);
+               } else if (!parse_commit_no_graph(commit))
                        add_missing_parents(ctx, commit);
        }
        stop_progress(&ctx->progress);
@@ -1165,8 +1219,16 @@ static uint32_t count_distinct_commits(struct write_commit_graph_context *ctx)
 
        for (i = 1; i < ctx->oids.nr; i++) {
                display_progress(ctx->progress, i + 1);
-               if (!oideq(&ctx->oids.list[i - 1], &ctx->oids.list[i]))
+               if (!oideq(&ctx->oids.list[i - 1], &ctx->oids.list[i])) {
+                       if (ctx->split) {
+                               struct commit *c = lookup_commit(ctx->r, &ctx->oids.list[i]);
+
+                               if (!c || c->graph_pos != COMMIT_NOT_FROM_GRAPH)
+                                       continue;
+                       }
+
                        count_distinct++;
+               }
        }
        stop_progress(&ctx->progress);
 
@@ -1189,7 +1251,13 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
                if (i > 0 && oideq(&ctx->oids.list[i - 1], &ctx->oids.list[i]))
                        continue;
 
+               ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc);
                ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.list[i]);
+
+               if (ctx->split &&
+                   ctx->commits.list[ctx->commits.nr]->graph_pos != COMMIT_NOT_FROM_GRAPH)
+                       continue;
+
                parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]);
 
                for (parent = ctx->commits.list[ctx->commits.nr]->parents;
@@ -1204,18 +1272,56 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
        stop_progress(&ctx->progress);
 }
 
+static int write_graph_chunk_base_1(struct hashfile *f,
+                                   struct commit_graph *g)
+{
+       int num = 0;
+
+       if (!g)
+               return 0;
+
+       num = write_graph_chunk_base_1(f, g->base_graph);
+       hashwrite(f, g->oid.hash, the_hash_algo->rawsz);
+       return num + 1;
+}
+
+static int write_graph_chunk_base(struct hashfile *f,
+                                 struct write_commit_graph_context *ctx)
+{
+       int num = write_graph_chunk_base_1(f, ctx->new_base_graph);
+
+       if (num != ctx->num_commit_graphs_after - 1) {
+               error(_("failed to write correct number of base graph ids"));
+               return -1;
+       }
+
+       return 0;
+}
+
 static int write_commit_graph_file(struct write_commit_graph_context *ctx)
 {
        uint32_t i;
+       int fd;
        struct hashfile *f;
        struct lock_file lk = LOCK_INIT;
-       uint32_t chunk_ids[5];
-       uint64_t chunk_offsets[5];
+       uint32_t chunk_ids[6];
+       uint64_t chunk_offsets[6];
        const unsigned hashsz = the_hash_algo->rawsz;
        struct strbuf progress_title = STRBUF_INIT;
-       int num_chunks = ctx->num_extra_edges ? 4 : 3;
+       int num_chunks = 3;
+       struct object_id file_hash;
+
+       if (ctx->split) {
+               struct strbuf tmp_file = STRBUF_INIT;
+
+               strbuf_addf(&tmp_file,
+                           "%s/info/commit-graphs/tmp_graph_XXXXXX",
+                           ctx->obj_dir);
+               ctx->graph_name = strbuf_detach(&tmp_file, NULL);
+       } else {
+               ctx->graph_name = get_commit_graph_filename(ctx->obj_dir);
+       }
 
-       ctx->graph_name = get_commit_graph_filename(ctx->obj_dir);
        if (safe_create_leading_directories(ctx->graph_name)) {
                UNLEAK(ctx->graph_name);
                error(_("unable to create leading directories of %s"),
@@ -1223,30 +1329,61 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
                return -1;
        }
 
-       hold_lock_file_for_update(&lk, ctx->graph_name, LOCK_DIE_ON_ERROR);
-       f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+       if (ctx->split) {
+               char *lock_name = get_chain_filename(ctx->obj_dir);
 
-       hashwrite_be32(f, GRAPH_SIGNATURE);
+               hold_lock_file_for_update(&lk, lock_name, LOCK_DIE_ON_ERROR);
 
-       hashwrite_u8(f, GRAPH_VERSION);
-       hashwrite_u8(f, oid_version());
-       hashwrite_u8(f, num_chunks);
-       hashwrite_u8(f, 0); /* unused padding byte */
+               fd = git_mkstemp_mode(ctx->graph_name, 0444);
+               if (fd < 0) {
+                       error(_("unable to create '%s'"), ctx->graph_name);
+                       return -1;
+               }
+
+               f = hashfd(fd, ctx->graph_name);
+       } else {
+               hold_lock_file_for_update(&lk, ctx->graph_name, LOCK_DIE_ON_ERROR);
+               fd = lk.tempfile->fd;
+               f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+       }
 
        chunk_ids[0] = GRAPH_CHUNKID_OIDFANOUT;
        chunk_ids[1] = GRAPH_CHUNKID_OIDLOOKUP;
        chunk_ids[2] = GRAPH_CHUNKID_DATA;
-       if (ctx->num_extra_edges)
-               chunk_ids[3] = GRAPH_CHUNKID_EXTRAEDGES;
-       else
-               chunk_ids[3] = 0;
-       chunk_ids[4] = 0;
+       if (ctx->num_extra_edges) {
+               chunk_ids[num_chunks] = GRAPH_CHUNKID_EXTRAEDGES;
+               num_chunks++;
+       }
+       if (ctx->num_commit_graphs_after > 1) {
+               chunk_ids[num_chunks] = GRAPH_CHUNKID_BASE;
+               num_chunks++;
+       }
+
+       chunk_ids[num_chunks] = 0;
 
        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] + hashsz * ctx->commits.nr;
        chunk_offsets[3] = chunk_offsets[2] + (hashsz + 16) * ctx->commits.nr;
-       chunk_offsets[4] = chunk_offsets[3] + 4 * ctx->num_extra_edges;
+
+       num_chunks = 3;
+       if (ctx->num_extra_edges) {
+               chunk_offsets[num_chunks + 1] = chunk_offsets[num_chunks] +
+                                               4 * ctx->num_extra_edges;
+               num_chunks++;
+       }
+       if (ctx->num_commit_graphs_after > 1) {
+               chunk_offsets[num_chunks + 1] = chunk_offsets[num_chunks] +
+                                               hashsz * (ctx->num_commit_graphs_after - 1);
+               num_chunks++;
+       }
+
+       hashwrite_be32(f, GRAPH_SIGNATURE);
+
+       hashwrite_u8(f, GRAPH_VERSION);
+       hashwrite_u8(f, oid_version());
+       hashwrite_u8(f, num_chunks);
+       hashwrite_u8(f, ctx->num_commit_graphs_after - 1);
 
        for (i = 0; i <= num_chunks; i++) {
                uint32_t chunk_write[3];
@@ -1272,16 +1409,215 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
        write_graph_chunk_data(f, hashsz, ctx);
        if (ctx->num_extra_edges)
                write_graph_chunk_extra_edges(f, ctx);
+       if (ctx->num_commit_graphs_after > 1 &&
+           write_graph_chunk_base(f, ctx)) {
+               return -1;
+       }
        stop_progress(&ctx->progress);
        strbuf_release(&progress_title);
 
+       if (ctx->split && ctx->base_graph_name && ctx->num_commit_graphs_after > 1) {
+               char *new_base_hash = xstrdup(oid_to_hex(&ctx->new_base_graph->oid));
+               char *new_base_name = get_split_graph_filename(ctx->obj_dir, new_base_hash);
+
+               free(ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 2]);
+               free(ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 2]);
+               ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 2] = new_base_name;
+               ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 2] = new_base_hash;
+       }
+
        close_commit_graph(ctx->r->objects);
-       finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
+       finalize_hashfile(f, file_hash.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
+
+       if (ctx->split) {
+               FILE *chainf = fdopen_lock_file(&lk, "w");
+               char *final_graph_name;
+               int result;
+
+               close(fd);
+
+               if (!chainf) {
+                       error(_("unable to open commit-graph chain file"));
+                       return -1;
+               }
+
+               if (ctx->base_graph_name) {
+                       const char *dest = ctx->commit_graph_filenames_after[
+                                               ctx->num_commit_graphs_after - 2];
+
+                       if (strcmp(ctx->base_graph_name, dest)) {
+                               result = rename(ctx->base_graph_name, dest);
+
+                               if (result) {
+                                       error(_("failed to rename base commit-graph file"));
+                                       return -1;
+                               }
+                       }
+               } else {
+                       char *graph_name = get_commit_graph_filename(ctx->obj_dir);
+                       unlink(graph_name);
+               }
+
+               ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(oid_to_hex(&file_hash));
+               final_graph_name = get_split_graph_filename(ctx->obj_dir,
+                                       ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]);
+               ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1] = final_graph_name;
+
+               result = rename(ctx->graph_name, final_graph_name);
+
+               for (i = 0; i < ctx->num_commit_graphs_after; i++)
+                       fprintf(lk.tempfile->fp, "%s\n", ctx->commit_graph_hash_after[i]);
+
+               if (result) {
+                       error(_("failed to rename temporary commit-graph file"));
+                       return -1;
+               }
+       }
+
        commit_lock_file(&lk);
 
        return 0;
 }
 
+static int split_strategy_max_commits = 64000;
+static float split_strategy_size_mult = 2.0f;
+
+static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
+{
+       struct commit_graph *g = ctx->r->objects->commit_graph;
+       uint32_t num_commits = ctx->commits.nr;
+       uint32_t i;
+
+       g = ctx->r->objects->commit_graph;
+       ctx->num_commit_graphs_after = ctx->num_commit_graphs_before + 1;
+
+       while (g && (g->num_commits <= split_strategy_size_mult * num_commits ||
+                    num_commits > split_strategy_max_commits)) {
+               num_commits += g->num_commits;
+               g = g->base_graph;
+
+               ctx->num_commit_graphs_after--;
+       }
+
+       ctx->new_base_graph = g;
+
+       ALLOC_ARRAY(ctx->commit_graph_filenames_after, ctx->num_commit_graphs_after);
+       ALLOC_ARRAY(ctx->commit_graph_hash_after, ctx->num_commit_graphs_after);
+
+       for (i = 0; i < ctx->num_commit_graphs_after &&
+                   i < ctx->num_commit_graphs_before; i++)
+               ctx->commit_graph_filenames_after[i] = xstrdup(ctx->commit_graph_filenames_before[i]);
+
+       i = ctx->num_commit_graphs_before - 1;
+       g = ctx->r->objects->commit_graph;
+
+       while (g) {
+               if (i < ctx->num_commit_graphs_after)
+                       ctx->commit_graph_hash_after[i] = xstrdup(oid_to_hex(&g->oid));
+
+               i--;
+               g = g->base_graph;
+       }
+}
+
+static void merge_commit_graph(struct write_commit_graph_context *ctx,
+                              struct commit_graph *g)
+{
+       uint32_t i;
+       uint32_t offset = g->num_commits_in_base;
+
+       ALLOC_GROW(ctx->commits.list, ctx->commits.nr + g->num_commits, ctx->commits.alloc);
+
+       for (i = 0; i < g->num_commits; i++) {
+               struct object_id oid;
+               struct commit *result;
+
+               display_progress(ctx->progress, i + 1);
+
+               load_oid_from_graph(g, i + offset, &oid);
+
+               /* only add commits if they still exist in the repo */
+               result = lookup_commit_reference_gently(ctx->r, &oid, 1);
+
+               if (result) {
+                       ctx->commits.list[ctx->commits.nr] = result;
+                       ctx->commits.nr++;
+               }
+       }
+}
+
+static int commit_compare(const void *_a, const void *_b)
+{
+       const struct commit *a = *(const struct commit **)_a;
+       const struct commit *b = *(const struct commit **)_b;
+       return oidcmp(&a->object.oid, &b->object.oid);
+}
+
+static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
+{
+       uint32_t i, num_parents;
+       struct commit_list *parent;
+
+       if (ctx->report_progress)
+               ctx->progress = start_delayed_progress(
+                                       _("Scanning merged commits"),
+                                       ctx->commits.nr);
+
+       QSORT(ctx->commits.list, ctx->commits.nr, commit_compare);
+
+       ctx->num_extra_edges = 0;
+       for (i = 0; i < ctx->commits.nr; i++) {
+               display_progress(ctx->progress, i);
+
+               if (i && oideq(&ctx->commits.list[i - 1]->object.oid,
+                         &ctx->commits.list[i]->object.oid)) {
+                       die(_("unexpected duplicate commit id %s"),
+                           oid_to_hex(&ctx->commits.list[i]->object.oid));
+               } else {
+                       num_parents = 0;
+                       for (parent = ctx->commits.list[i]->parents; parent; parent = parent->next)
+                               num_parents++;
+
+                       if (num_parents > 2)
+                               ctx->num_extra_edges += num_parents - 2;
+               }
+       }
+
+       stop_progress(&ctx->progress);
+}
+
+static void merge_commit_graphs(struct write_commit_graph_context *ctx)
+{
+       struct commit_graph *g = ctx->r->objects->commit_graph;
+       uint32_t current_graph_number = ctx->num_commit_graphs_before;
+       struct strbuf progress_title = STRBUF_INIT;
+
+       while (g && current_graph_number >= ctx->num_commit_graphs_after) {
+               current_graph_number--;
+
+               if (ctx->report_progress) {
+                       strbuf_addstr(&progress_title, _("Merging commit-graph"));
+                       ctx->progress = start_delayed_progress(progress_title.buf, 0);
+               }
+
+               merge_commit_graph(ctx, g);
+               stop_progress(&ctx->progress);
+               strbuf_release(&progress_title);
+
+               g = g->base_graph;
+       }
+
+       if (g) {
+               ctx->new_base_graph = g;
+               ctx->new_num_commits_in_base = g->num_commits + g->num_commits_in_base;
+       }
+
+       if (ctx->new_base_graph)
+               ctx->base_graph_name = xstrdup(ctx->new_base_graph->filename);
+
+       sort_and_scan_merged_commits(ctx);
+}
+
 int write_commit_graph(const char *obj_dir,
                       struct string_list *pack_indexes,
                       struct string_list *commit_hex,
@@ -1299,10 +1635,37 @@ int write_commit_graph(const char *obj_dir,
        ctx->obj_dir = obj_dir;
        ctx->append = flags & COMMIT_GRAPH_APPEND ? 1 : 0;
        ctx->report_progress = flags & COMMIT_GRAPH_PROGRESS ? 1 : 0;
+       ctx->split = flags & COMMIT_GRAPH_SPLIT ? 1 : 0;
+
+       if (ctx->split) {
+               struct commit_graph *g;
+               prepare_commit_graph(ctx->r);
+
+               g = ctx->r->objects->commit_graph;
+
+               while (g) {
+                       ctx->num_commit_graphs_before++;
+                       g = g->base_graph;
+               }
+
+               if (ctx->num_commit_graphs_before) {
+                       ALLOC_ARRAY(ctx->commit_graph_filenames_before, ctx->num_commit_graphs_before);
+                       i = ctx->num_commit_graphs_before;
+                       g = ctx->r->objects->commit_graph;
+
+                       while (g) {
+                               ctx->commit_graph_filenames_before[--i] = xstrdup(g->filename);
+                               g = g->base_graph;
+                       }
+               }
+       }
 
        ctx->approx_nr_objects = approximate_object_count();
        ctx->oids.alloc = ctx->approx_nr_objects / 32;
 
+       if (ctx->split && ctx->oids.alloc > split_strategy_max_commits)
+               ctx->oids.alloc = split_strategy_max_commits;
+
        if (ctx->append) {
                prepare_commit_graph_one(ctx->r, ctx->obj_dir);
                if (ctx->r->objects->commit_graph)
@@ -1353,6 +1716,16 @@ int write_commit_graph(const char *obj_dir,
                goto cleanup;
        }
 
+       if (!ctx->commits.nr)
+               goto cleanup;
+
+       if (ctx->split) {
+               split_graph_merge_strategy(ctx);
+
+               merge_commit_graphs(ctx);
+       } else
+               ctx->num_commit_graphs_after = 1;
+
        compute_generation_numbers(ctx);
 
        res = write_commit_graph_file(ctx);
@@ -1361,6 +1734,21 @@ int write_commit_graph(const char *obj_dir,
        free(ctx->graph_name);
        free(ctx->commits.list);
        free(ctx->oids.list);
+
+       if (ctx->commit_graph_filenames_after) {
+               for (i = 0; i < ctx->num_commit_graphs_after; i++) {
+                       free(ctx->commit_graph_filenames_after[i]);
+                       free(ctx->commit_graph_hash_after[i]);
+               }
+
+               for (i = 0; i < ctx->num_commit_graphs_before; i++)
+                       free(ctx->commit_graph_filenames_before[i]);
+
+               free(ctx->commit_graph_filenames_after);
+               free(ctx->commit_graph_filenames_before);
+               free(ctx->commit_graph_hash_after);
+       }
+
        free(ctx);
 
        return res;
@@ -1548,5 +1936,6 @@ void free_commit_graph(struct commit_graph *g)
                g->data = NULL;
                close(g->graph_fd);
        }
+       free(g->filename);
        free(g);
 }