File Coverage

deps/libgit2/src/libgit2/commit_graph.c
Criterion Covered Total %
statement 42 656 6.4
branch 16 398 4.0
condition n/a
subroutine n/a
pod n/a
total 58 1054 5.5


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
4             * This file is part of libgit2, distributed under the GNU GPL v2 with
5             * a Linking Exception. For full terms see the included COPYING file.
6             */
7              
8             #include "commit_graph.h"
9              
10             #include "array.h"
11             #include "buf.h"
12             #include "filebuf.h"
13             #include "futils.h"
14             #include "hash.h"
15             #include "oidarray.h"
16             #include "oidmap.h"
17             #include "pack.h"
18             #include "repository.h"
19             #include "revwalk.h"
20              
21             #define GIT_COMMIT_GRAPH_MISSING_PARENT 0x70000000
22             #define GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX 0x3FFFFFFF
23             #define GIT_COMMIT_GRAPH_GENERATION_NUMBER_INFINITY 0xFFFFFFFF
24              
25             #define COMMIT_GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
26             #define COMMIT_GRAPH_VERSION 1
27             #define COMMIT_GRAPH_OBJECT_ID_VERSION 1
28             struct git_commit_graph_header {
29             uint32_t signature;
30             uint8_t version;
31             uint8_t object_id_version;
32             uint8_t chunks;
33             uint8_t base_graph_files;
34             };
35              
36             #define COMMIT_GRAPH_OID_FANOUT_ID 0x4f494446 /* "OIDF" */
37             #define COMMIT_GRAPH_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */
38             #define COMMIT_GRAPH_COMMIT_DATA_ID 0x43444154 /* "CDAT" */
39             #define COMMIT_GRAPH_EXTRA_EDGE_LIST_ID 0x45444745 /* "EDGE" */
40             #define COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID 0x42494458 /* "BIDX" */
41             #define COMMIT_GRAPH_BLOOM_FILTER_DATA_ID 0x42444154 /* "BDAT" */
42              
43             struct git_commit_graph_chunk {
44             off64_t offset;
45             size_t length;
46             };
47              
48             typedef git_array_t(size_t) parent_index_array_t;
49              
50             struct packed_commit {
51             size_t index;
52             git_oid sha1;
53             git_oid tree_oid;
54             uint32_t generation;
55             git_time_t commit_time;
56             git_array_oid_t parents;
57             parent_index_array_t parent_indices;
58             };
59              
60 0           static void packed_commit_free(struct packed_commit *p)
61             {
62 0 0         if (!p)
63 0           return;
64              
65 0           git_array_clear(p->parents);
66 0           git_array_clear(p->parent_indices);
67 0           git__free(p);
68             }
69              
70 0           static struct packed_commit *packed_commit_new(git_commit *commit)
71             {
72 0           unsigned int i, parentcount = git_commit_parentcount(commit);
73 0           struct packed_commit *p = git__calloc(1, sizeof(struct packed_commit));
74 0 0         if (!p)
75 0           goto cleanup;
76              
77 0           git_array_init_to_size(p->parents, parentcount);
78 0 0         if (parentcount && !p->parents.ptr)
    0          
79 0           goto cleanup;
80              
81 0 0         if (git_oid_cpy(&p->sha1, git_commit_id(commit)) < 0)
82 0           goto cleanup;
83 0 0         if (git_oid_cpy(&p->tree_oid, git_commit_tree_id(commit)) < 0)
84 0           goto cleanup;
85 0           p->commit_time = git_commit_time(commit);
86              
87 0 0         for (i = 0; i < parentcount; ++i) {
88 0 0         git_oid *parent_id = git_array_alloc(p->parents);
    0          
89 0 0         if (!parent_id)
90 0           goto cleanup;
91 0 0         if (git_oid_cpy(parent_id, git_commit_parent_id(commit, i)) < 0)
92 0           goto cleanup;
93             }
94              
95 0           return p;
96              
97             cleanup:
98 0           packed_commit_free(p);
99 0           return NULL;
100             }
101              
102             typedef int (*commit_graph_write_cb)(const char *buf, size_t size, void *cb_data);
103              
104 0           static int commit_graph_error(const char *message)
105             {
106 0           git_error_set(GIT_ERROR_ODB, "invalid commit-graph file - %s", message);
107 0           return -1;
108             }
109              
110 0           static int commit_graph_parse_oid_fanout(
111             git_commit_graph_file *file,
112             const unsigned char *data,
113             struct git_commit_graph_chunk *chunk_oid_fanout)
114             {
115             uint32_t i, nr;
116 0 0         if (chunk_oid_fanout->offset == 0)
117 0           return commit_graph_error("missing OID Fanout chunk");
118 0 0         if (chunk_oid_fanout->length == 0)
119 0           return commit_graph_error("empty OID Fanout chunk");
120 0 0         if (chunk_oid_fanout->length != 256 * 4)
121 0           return commit_graph_error("OID Fanout chunk has wrong length");
122              
123 0           file->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset);
124 0           nr = 0;
125 0 0         for (i = 0; i < 256; ++i) {
126 0           uint32_t n = ntohl(file->oid_fanout[i]);
127 0 0         if (n < nr)
128 0           return commit_graph_error("index is non-monotonic");
129 0           nr = n;
130             }
131 0           file->num_commits = nr;
132 0           return 0;
133             }
134              
135 0           static int commit_graph_parse_oid_lookup(
136             git_commit_graph_file *file,
137             const unsigned char *data,
138             struct git_commit_graph_chunk *chunk_oid_lookup)
139             {
140             uint32_t i;
141 0           unsigned char *oid, *prev_oid, zero_oid[GIT_OID_RAWSZ] = {0};
142              
143 0 0         if (chunk_oid_lookup->offset == 0)
144 0           return commit_graph_error("missing OID Lookup chunk");
145 0 0         if (chunk_oid_lookup->length == 0)
146 0           return commit_graph_error("empty OID Lookup chunk");
147 0 0         if (chunk_oid_lookup->length != file->num_commits * GIT_OID_RAWSZ)
148 0           return commit_graph_error("OID Lookup chunk has wrong length");
149              
150 0           file->oid_lookup = oid = (unsigned char *)(data + chunk_oid_lookup->offset);
151 0           prev_oid = zero_oid;
152 0 0         for (i = 0; i < file->num_commits; ++i, oid += GIT_OID_RAWSZ) {
153 0 0         if (git_oid_raw_cmp(prev_oid, oid) >= 0)
154 0           return commit_graph_error("OID Lookup index is non-monotonic");
155 0           prev_oid = oid;
156             }
157              
158 0           return 0;
159             }
160              
161 0           static int commit_graph_parse_commit_data(
162             git_commit_graph_file *file,
163             const unsigned char *data,
164             struct git_commit_graph_chunk *chunk_commit_data)
165             {
166 0 0         if (chunk_commit_data->offset == 0)
167 0           return commit_graph_error("missing Commit Data chunk");
168 0 0         if (chunk_commit_data->length == 0)
169 0           return commit_graph_error("empty Commit Data chunk");
170 0 0         if (chunk_commit_data->length != file->num_commits * (GIT_OID_RAWSZ + 16))
171 0           return commit_graph_error("Commit Data chunk has wrong length");
172              
173 0           file->commit_data = data + chunk_commit_data->offset;
174              
175 0           return 0;
176             }
177              
178 0           static int commit_graph_parse_extra_edge_list(
179             git_commit_graph_file *file,
180             const unsigned char *data,
181             struct git_commit_graph_chunk *chunk_extra_edge_list)
182             {
183 0 0         if (chunk_extra_edge_list->length == 0)
184 0           return 0;
185 0 0         if (chunk_extra_edge_list->length % 4 != 0)
186 0           return commit_graph_error("malformed Extra Edge List chunk");
187              
188 0           file->extra_edge_list = data + chunk_extra_edge_list->offset;
189 0           file->num_extra_edge_list = chunk_extra_edge_list->length / 4;
190              
191 0           return 0;
192             }
193              
194 0           int git_commit_graph_file_parse(
195             git_commit_graph_file *file,
196             const unsigned char *data,
197             size_t size)
198             {
199             struct git_commit_graph_header *hdr;
200             const unsigned char *chunk_hdr;
201             struct git_commit_graph_chunk *last_chunk;
202             uint32_t i;
203             off64_t last_chunk_offset, chunk_offset, trailer_offset;
204             unsigned char checksum[GIT_HASH_SHA1_SIZE];
205             size_t checksum_size;
206             int error;
207 0           struct git_commit_graph_chunk chunk_oid_fanout = {0}, chunk_oid_lookup = {0},
208 0           chunk_commit_data = {0}, chunk_extra_edge_list = {0},
209 0           chunk_unsupported = {0};
210              
211 0 0         GIT_ASSERT_ARG(file);
212              
213 0 0         if (size < sizeof(struct git_commit_graph_header) + GIT_OID_RAWSZ)
214 0           return commit_graph_error("commit-graph is too short");
215              
216 0           hdr = ((struct git_commit_graph_header *)data);
217              
218 0 0         if (hdr->signature != htonl(COMMIT_GRAPH_SIGNATURE) || hdr->version != COMMIT_GRAPH_VERSION
    0          
219 0 0         || hdr->object_id_version != COMMIT_GRAPH_OBJECT_ID_VERSION) {
220 0           return commit_graph_error("unsupported commit-graph version");
221             }
222 0 0         if (hdr->chunks == 0)
223 0           return commit_graph_error("no chunks in commit-graph");
224              
225             /*
226             * The very first chunk's offset should be after the header, all the chunk
227             * headers, and a special zero chunk.
228             */
229 0           last_chunk_offset = sizeof(struct git_commit_graph_header) + (1 + hdr->chunks) * 12;
230 0           trailer_offset = size - GIT_OID_RAWSZ;
231 0           checksum_size = GIT_HASH_SHA1_SIZE;
232              
233 0 0         if (trailer_offset < last_chunk_offset)
234 0           return commit_graph_error("wrong commit-graph size");
235 0           memcpy(file->checksum, (data + trailer_offset), checksum_size);
236              
237 0 0         if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0)
238 0           return commit_graph_error("could not calculate signature");
239 0 0         if (memcmp(checksum, file->checksum, checksum_size) != 0)
240 0           return commit_graph_error("index signature mismatch");
241              
242 0           chunk_hdr = data + sizeof(struct git_commit_graph_header);
243 0           last_chunk = NULL;
244 0 0         for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) {
245 0           chunk_offset = ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) << 32
246 0           | ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 8))));
247 0 0         if (chunk_offset < last_chunk_offset)
248 0           return commit_graph_error("chunks are non-monotonic");
249 0 0         if (chunk_offset >= trailer_offset)
250 0           return commit_graph_error("chunks extend beyond the trailer");
251 0 0         if (last_chunk != NULL)
252 0           last_chunk->length = (size_t)(chunk_offset - last_chunk_offset);
253 0           last_chunk_offset = chunk_offset;
254              
255 0           switch (ntohl(*((uint32_t *)(chunk_hdr + 0)))) {
256             case COMMIT_GRAPH_OID_FANOUT_ID:
257 0           chunk_oid_fanout.offset = last_chunk_offset;
258 0           last_chunk = &chunk_oid_fanout;
259 0           break;
260              
261             case COMMIT_GRAPH_OID_LOOKUP_ID:
262 0           chunk_oid_lookup.offset = last_chunk_offset;
263 0           last_chunk = &chunk_oid_lookup;
264 0           break;
265              
266             case COMMIT_GRAPH_COMMIT_DATA_ID:
267 0           chunk_commit_data.offset = last_chunk_offset;
268 0           last_chunk = &chunk_commit_data;
269 0           break;
270              
271             case COMMIT_GRAPH_EXTRA_EDGE_LIST_ID:
272 0           chunk_extra_edge_list.offset = last_chunk_offset;
273 0           last_chunk = &chunk_extra_edge_list;
274 0           break;
275              
276             case COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID:
277             case COMMIT_GRAPH_BLOOM_FILTER_DATA_ID:
278 0           chunk_unsupported.offset = last_chunk_offset;
279 0           last_chunk = &chunk_unsupported;
280 0           break;
281              
282             default:
283 0           return commit_graph_error("unrecognized chunk ID");
284             }
285             }
286 0           last_chunk->length = (size_t)(trailer_offset - last_chunk_offset);
287              
288 0           error = commit_graph_parse_oid_fanout(file, data, &chunk_oid_fanout);
289 0 0         if (error < 0)
290 0           return error;
291 0           error = commit_graph_parse_oid_lookup(file, data, &chunk_oid_lookup);
292 0 0         if (error < 0)
293 0           return error;
294 0           error = commit_graph_parse_commit_data(file, data, &chunk_commit_data);
295 0 0         if (error < 0)
296 0           return error;
297 0           error = commit_graph_parse_extra_edge_list(file, data, &chunk_extra_edge_list);
298 0 0         if (error < 0)
299 0           return error;
300              
301 0           return 0;
302             }
303              
304 35           int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file)
305             {
306 35           git_commit_graph *cgraph = NULL;
307 35           int error = 0;
308              
309 35 50         GIT_ASSERT_ARG(cgraph_out);
310 35 50         GIT_ASSERT_ARG(objects_dir);
311              
312 35           cgraph = git__calloc(1, sizeof(git_commit_graph));
313 35 50         GIT_ERROR_CHECK_ALLOC(cgraph);
314              
315 35           error = git_str_joinpath(&cgraph->filename, objects_dir, "info/commit-graph");
316 35 50         if (error < 0)
317 0           goto error;
318              
319 35 50         if (open_file) {
320 0           error = git_commit_graph_file_open(&cgraph->file, git_str_cstr(&cgraph->filename));
321 0 0         if (error < 0)
322 0           goto error;
323 0           cgraph->checked = 1;
324             }
325              
326 35           *cgraph_out = cgraph;
327 35           return 0;
328              
329             error:
330 0           git_commit_graph_free(cgraph);
331 0           return error;
332             }
333              
334 0           int git_commit_graph_open(git_commit_graph **cgraph_out, const char *objects_dir)
335             {
336 0           return git_commit_graph_new(cgraph_out, objects_dir, true);
337             }
338              
339 13           int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path)
340             {
341             git_commit_graph_file *file;
342 13           git_file fd = -1;
343             size_t cgraph_size;
344             struct stat st;
345             int error;
346              
347             /* TODO: properly open the file without access time using O_NOATIME */
348 13           fd = git_futils_open_ro(path);
349 13 50         if (fd < 0)
350 13           return fd;
351              
352 0 0         if (p_fstat(fd, &st) < 0) {
353 0           p_close(fd);
354 0           git_error_set(GIT_ERROR_ODB, "commit-graph file not found - '%s'", path);
355 0           return GIT_ENOTFOUND;
356             }
357              
358 0 0         if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) {
    0          
359 0           p_close(fd);
360 0           git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path);
361 0           return GIT_ENOTFOUND;
362             }
363 0           cgraph_size = (size_t)st.st_size;
364              
365 0           file = git__calloc(1, sizeof(git_commit_graph_file));
366 0 0         GIT_ERROR_CHECK_ALLOC(file);
367              
368 0           error = git_futils_mmap_ro(&file->graph_map, fd, 0, cgraph_size);
369 0           p_close(fd);
370 0 0         if (error < 0) {
371 0           git_commit_graph_file_free(file);
372 0           return error;
373             }
374              
375 0 0         if ((error = git_commit_graph_file_parse(file, file->graph_map.data, cgraph_size)) < 0) {
376 0           git_commit_graph_file_free(file);
377 0           return error;
378             }
379              
380 0           *file_out = file;
381 13           return 0;
382             }
383              
384 179           int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph)
385             {
386 179 100         if (!cgraph->checked) {
387 13           int error = 0;
388 13           git_commit_graph_file *result = NULL;
389              
390             /* We only check once, no matter the result. */
391 13           cgraph->checked = 1;
392              
393             /* Best effort */
394 13           error = git_commit_graph_file_open(&result, git_str_cstr(&cgraph->filename));
395              
396 13 50         if (error < 0)
397 13           return error;
398              
399 0           cgraph->file = result;
400             }
401 166 50         if (!cgraph->file)
402 166           return GIT_ENOTFOUND;
403              
404 0           *file_out = cgraph->file;
405 0           return 0;
406             }
407              
408 174           void git_commit_graph_refresh(git_commit_graph *cgraph)
409             {
410 174 100         if (!cgraph->checked)
411 166           return;
412              
413 8 50         if (cgraph->file
414 0 0         && git_commit_graph_file_needs_refresh(cgraph->file, git_str_cstr(&cgraph->filename))) {
415             /* We just free the commit graph. The next time it is requested, it will be
416             * re-loaded. */
417 0           git_commit_graph_file_free(cgraph->file);
418 0           cgraph->file = NULL;
419             }
420             /* Force a lazy re-check next time it is needed. */
421 8           cgraph->checked = 0;
422             }
423              
424 0           static int git_commit_graph_entry_get_byindex(
425             git_commit_graph_entry *e,
426             const git_commit_graph_file *file,
427             size_t pos)
428             {
429             const unsigned char *commit_data;
430              
431 0 0         GIT_ASSERT_ARG(e);
432 0 0         GIT_ASSERT_ARG(file);
433              
434 0 0         if (pos >= file->num_commits) {
435 0           git_error_set(GIT_ERROR_INVALID, "commit index %zu does not exist", pos);
436 0           return GIT_ENOTFOUND;
437             }
438              
439 0           commit_data = file->commit_data + pos * (GIT_OID_RAWSZ + 4 * sizeof(uint32_t));
440 0           git_oid_fromraw(&e->tree_oid, commit_data);
441 0           e->parent_indices[0] = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ)));
442 0           e->parent_indices[1] = ntohl(
443 0           *((uint32_t *)(commit_data + GIT_OID_RAWSZ + sizeof(uint32_t))));
444 0           e->parent_count = (e->parent_indices[0] != GIT_COMMIT_GRAPH_MISSING_PARENT)
445 0           + (e->parent_indices[1] != GIT_COMMIT_GRAPH_MISSING_PARENT);
446 0           e->generation = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 2 * sizeof(uint32_t))));
447 0           e->commit_time = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 3 * sizeof(uint32_t))));
448              
449 0           e->commit_time |= (e->generation & UINT64_C(0x3)) << UINT64_C(32);
450 0           e->generation >>= 2u;
451 0 0         if (e->parent_indices[1] & 0x80000000u) {
452 0           uint32_t extra_edge_list_pos = e->parent_indices[1] & 0x7fffffff;
453              
454             /* Make sure we're not being sent out of bounds */
455 0 0         if (extra_edge_list_pos >= file->num_extra_edge_list) {
456 0           git_error_set(GIT_ERROR_INVALID,
457             "commit %u does not exist",
458             extra_edge_list_pos);
459 0           return GIT_ENOTFOUND;
460             }
461              
462 0           e->extra_parents_index = extra_edge_list_pos;
463 0 0         while (extra_edge_list_pos < file->num_extra_edge_list
464 0 0         && (ntohl(*(
465 0           (uint32_t *)(file->extra_edge_list
466 0           + extra_edge_list_pos * sizeof(uint32_t))))
467             & 0x80000000u)
468 0           == 0) {
469 0           extra_edge_list_pos++;
470 0           e->parent_count++;
471             }
472             }
473              
474 0           git_oid_fromraw(&e->sha1, &file->oid_lookup[pos * GIT_OID_RAWSZ]);
475 0           return 0;
476             }
477              
478 0           bool git_commit_graph_file_needs_refresh(const git_commit_graph_file *file, const char *path)
479             {
480 0           git_file fd = -1;
481             struct stat st;
482             ssize_t bytes_read;
483             unsigned char checksum[GIT_HASH_SHA1_SIZE];
484 0           size_t checksum_size = GIT_HASH_SHA1_SIZE;
485              
486             /* TODO: properly open the file without access time using O_NOATIME */
487 0           fd = git_futils_open_ro(path);
488 0 0         if (fd < 0)
489 0           return true;
490              
491 0 0         if (p_fstat(fd, &st) < 0) {
492 0           p_close(fd);
493 0           return true;
494             }
495              
496 0 0         if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)
    0          
497 0 0         || (size_t)st.st_size != file->graph_map.len) {
498 0           p_close(fd);
499 0           return true;
500             }
501              
502 0           bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - checksum_size);
503 0           p_close(fd);
504 0 0         if (bytes_read != (ssize_t)checksum_size)
505 0           return true;
506              
507 0           return (memcmp(checksum, file->checksum, checksum_size) != 0);
508             }
509              
510 0           int git_commit_graph_entry_find(
511             git_commit_graph_entry *e,
512             const git_commit_graph_file *file,
513             const git_oid *short_oid,
514             size_t len)
515             {
516 0           int pos, found = 0;
517             uint32_t hi, lo;
518 0           const unsigned char *current = NULL;
519              
520 0 0         GIT_ASSERT_ARG(e);
521 0 0         GIT_ASSERT_ARG(file);
522 0 0         GIT_ASSERT_ARG(short_oid);
523              
524 0           hi = ntohl(file->oid_fanout[(int)short_oid->id[0]]);
525 0 0         lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(file->oid_fanout[(int)short_oid->id[0] - 1]));
526              
527 0           pos = git_pack__lookup_sha1(file->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id);
528              
529 0 0         if (pos >= 0) {
530             /* An object matching exactly the oid was found */
531 0           found = 1;
532 0           current = file->oid_lookup + (pos * GIT_OID_RAWSZ);
533             } else {
534             /* No object was found */
535             /* pos refers to the object with the "closest" oid to short_oid */
536 0           pos = -1 - pos;
537 0 0         if (pos < (int)file->num_commits) {
538 0           current = file->oid_lookup + (pos * GIT_OID_RAWSZ);
539              
540 0 0         if (!git_oid_raw_ncmp(short_oid->id, current, len))
541 0           found = 1;
542             }
543             }
544              
545 0 0         if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)file->num_commits) {
    0          
    0          
546             /* Check for ambiguousity */
547 0           const unsigned char *next = current + GIT_OID_RAWSZ;
548              
549 0 0         if (!git_oid_raw_ncmp(short_oid->id, next, len))
550 0           found = 2;
551             }
552              
553 0 0         if (!found)
554 0           return git_odb__error_notfound(
555             "failed to find offset for commit-graph index entry", short_oid, len);
556 0 0         if (found > 1)
557 0           return git_odb__error_ambiguous(
558             "found multiple offsets for commit-graph index entry");
559              
560 0           return git_commit_graph_entry_get_byindex(e, file, pos);
561             }
562              
563 0           int git_commit_graph_entry_parent(
564             git_commit_graph_entry *parent,
565             const git_commit_graph_file *file,
566             const git_commit_graph_entry *entry,
567             size_t n)
568             {
569 0 0         GIT_ASSERT_ARG(parent);
570 0 0         GIT_ASSERT_ARG(file);
571              
572 0 0         if (n >= entry->parent_count) {
573 0           git_error_set(GIT_ERROR_INVALID, "parent index %zu does not exist", n);
574 0           return GIT_ENOTFOUND;
575             }
576              
577 0 0         if (n == 0 || (n == 1 && entry->parent_count == 2))
    0          
    0          
578 0           return git_commit_graph_entry_get_byindex(parent, file, entry->parent_indices[n]);
579              
580 0           return git_commit_graph_entry_get_byindex(
581             parent,
582             file,
583 0           ntohl(
584 0           *(uint32_t *)(file->extra_edge_list
585 0           + (entry->extra_parents_index + n - 1)
586             * sizeof(uint32_t)))
587             & 0x7fffffff);
588             }
589              
590 0           int git_commit_graph_file_close(git_commit_graph_file *file)
591             {
592 0 0         GIT_ASSERT_ARG(file);
593              
594 0 0         if (file->graph_map.data)
595 0           git_futils_mmap_free(&file->graph_map);
596              
597 0           return 0;
598             }
599              
600 36           void git_commit_graph_free(git_commit_graph *cgraph)
601             {
602 36 100         if (!cgraph)
603 1           return;
604              
605 35           git_str_dispose(&cgraph->filename);
606 35           git_commit_graph_file_free(cgraph->file);
607 35           git__free(cgraph);
608             }
609              
610 35           void git_commit_graph_file_free(git_commit_graph_file *file)
611             {
612 35 50         if (!file)
613 35           return;
614              
615 0           git_commit_graph_file_close(file);
616 0           git__free(file);
617             }
618              
619 0           static int packed_commit__cmp(const void *a_, const void *b_)
620             {
621 0           const struct packed_commit *a = a_;
622 0           const struct packed_commit *b = b_;
623 0           return git_oid_cmp(&a->sha1, &b->sha1);
624             }
625              
626 0           int git_commit_graph_writer_new(git_commit_graph_writer **out, const char *objects_info_dir)
627             {
628 0           git_commit_graph_writer *w = git__calloc(1, sizeof(git_commit_graph_writer));
629 0 0         GIT_ERROR_CHECK_ALLOC(w);
630              
631 0 0         if (git_str_sets(&w->objects_info_dir, objects_info_dir) < 0) {
632 0           git__free(w);
633 0           return -1;
634             }
635              
636 0 0         if (git_vector_init(&w->commits, 0, packed_commit__cmp) < 0) {
637 0           git_str_dispose(&w->objects_info_dir);
638 0           git__free(w);
639 0           return -1;
640             }
641              
642 0           *out = w;
643 0           return 0;
644             }
645              
646 0           void git_commit_graph_writer_free(git_commit_graph_writer *w)
647             {
648             struct packed_commit *packed_commit;
649             size_t i;
650              
651 0 0         if (!w)
652 0           return;
653              
654 0 0         git_vector_foreach (&w->commits, i, packed_commit)
655 0           packed_commit_free(packed_commit);
656 0           git_vector_free(&w->commits);
657 0           git_str_dispose(&w->objects_info_dir);
658 0           git__free(w);
659             }
660              
661             struct object_entry_cb_state {
662             git_repository *repo;
663             git_odb *db;
664             git_vector *commits;
665             };
666              
667 0           static int object_entry__cb(const git_oid *id, void *data)
668             {
669 0           struct object_entry_cb_state *state = (struct object_entry_cb_state *)data;
670 0           git_commit *commit = NULL;
671 0           struct packed_commit *packed_commit = NULL;
672             size_t header_len;
673             git_object_t header_type;
674 0           int error = 0;
675              
676 0           error = git_odb_read_header(&header_len, &header_type, state->db, id);
677 0 0         if (error < 0)
678 0           return error;
679              
680 0 0         if (header_type != GIT_OBJECT_COMMIT)
681 0           return 0;
682              
683 0           error = git_commit_lookup(&commit, state->repo, id);
684 0 0         if (error < 0)
685 0           return error;
686              
687 0           packed_commit = packed_commit_new(commit);
688 0           git_commit_free(commit);
689 0 0         GIT_ERROR_CHECK_ALLOC(packed_commit);
690              
691 0           error = git_vector_insert(state->commits, packed_commit);
692 0 0         if (error < 0) {
693 0           packed_commit_free(packed_commit);
694 0           return error;
695             }
696              
697 0           return 0;
698             }
699              
700 0           int git_commit_graph_writer_add_index_file(
701             git_commit_graph_writer *w,
702             git_repository *repo,
703             const char *idx_path)
704             {
705             int error;
706 0           struct git_pack_file *p = NULL;
707 0           struct object_entry_cb_state state = {0};
708 0           state.repo = repo;
709 0           state.commits = &w->commits;
710              
711 0           error = git_repository_odb(&state.db, repo);
712 0 0         if (error < 0)
713 0           goto cleanup;
714              
715 0           error = git_mwindow_get_pack(&p, idx_path);
716 0 0         if (error < 0)
717 0           goto cleanup;
718              
719 0           error = git_pack_foreach_entry(p, object_entry__cb, &state);
720 0 0         if (error < 0)
721 0           goto cleanup;
722              
723             cleanup:
724 0 0         if (p)
725 0           git_mwindow_put_pack(p);
726 0           git_odb_free(state.db);
727 0           return error;
728             }
729              
730 0           int git_commit_graph_writer_add_revwalk(git_commit_graph_writer *w, git_revwalk *walk)
731             {
732             int error;
733             git_oid id;
734 0           git_repository *repo = git_revwalk_repository(walk);
735             git_commit *commit;
736             struct packed_commit *packed_commit;
737              
738 0 0         while ((git_revwalk_next(&id, walk)) == 0) {
739 0           error = git_commit_lookup(&commit, repo, &id);
740 0 0         if (error < 0)
741 0           return error;
742              
743 0           packed_commit = packed_commit_new(commit);
744 0           git_commit_free(commit);
745 0 0         GIT_ERROR_CHECK_ALLOC(packed_commit);
746              
747 0           error = git_vector_insert(&w->commits, packed_commit);
748 0 0         if (error < 0) {
749 0           packed_commit_free(packed_commit);
750 0           return error;
751             }
752             }
753              
754 0           return 0;
755             }
756              
757             enum generation_number_commit_state {
758             GENERATION_NUMBER_COMMIT_STATE_UNVISITED = 0,
759             GENERATION_NUMBER_COMMIT_STATE_ADDED = 1,
760             GENERATION_NUMBER_COMMIT_STATE_EXPANDED = 2,
761             GENERATION_NUMBER_COMMIT_STATE_VISITED = 3
762             };
763              
764 0           static int compute_generation_numbers(git_vector *commits)
765             {
766 0           git_array_t(size_t) index_stack = GIT_ARRAY_INIT;
767             size_t i, j;
768             size_t *parent_idx;
769 0           enum generation_number_commit_state *commit_states = NULL;
770             struct packed_commit *child_packed_commit;
771 0           git_oidmap *packed_commit_map = NULL;
772 0           int error = 0;
773              
774             /* First populate the parent indices fields */
775 0           error = git_oidmap_new(&packed_commit_map);
776 0 0         if (error < 0)
777 0           goto cleanup;
778 0 0         git_vector_foreach (commits, i, child_packed_commit) {
779 0           child_packed_commit->index = i;
780 0           error = git_oidmap_set(
781 0           packed_commit_map, &child_packed_commit->sha1, child_packed_commit);
782 0 0         if (error < 0)
783 0           goto cleanup;
784             }
785              
786 0 0         git_vector_foreach (commits, i, child_packed_commit) {
787             size_t parent_i, *parent_idx_ptr;
788             struct packed_commit *parent_packed_commit;
789             git_oid *parent_id;
790 0           git_array_init_to_size(
791             child_packed_commit->parent_indices,
792             git_array_size(child_packed_commit->parents));
793 0 0         if (git_array_size(child_packed_commit->parents)
794 0 0         && !child_packed_commit->parent_indices.ptr) {
795 0           error = -1;
796 0           goto cleanup;
797             }
798 0 0         git_array_foreach (child_packed_commit->parents, parent_i, parent_id) {
    0          
799 0           parent_packed_commit = git_oidmap_get(packed_commit_map, parent_id);
800 0 0         if (!parent_packed_commit) {
801 0           git_error_set(GIT_ERROR_ODB,
802             "parent commit %s not found in commit graph",
803             git_oid_tostr_s(parent_id));
804 0           error = GIT_ENOTFOUND;
805 0           goto cleanup;
806             }
807 0 0         parent_idx_ptr = git_array_alloc(child_packed_commit->parent_indices);
    0          
808 0 0         if (!parent_idx_ptr) {
809 0           error = -1;
810 0           goto cleanup;
811             }
812 0           *parent_idx_ptr = parent_packed_commit->index;
813             }
814             }
815              
816             /*
817             * We copy all the commits to the stack and then during visitation,
818             * each node can be added up to two times to the stack.
819             */
820 0           git_array_init_to_size(index_stack, 3 * git_vector_length(commits));
821 0 0         if (!index_stack.ptr) {
822 0           error = -1;
823 0           goto cleanup;
824             }
825              
826 0           commit_states = (enum generation_number_commit_state *)git__calloc(
827             git_vector_length(commits), sizeof(enum generation_number_commit_state));
828 0 0         if (!commit_states) {
829 0           error = -1;
830 0           goto cleanup;
831             }
832              
833             /*
834             * Perform a Post-Order traversal so that all parent nodes are fully
835             * visited before the child node.
836             */
837 0 0         git_vector_foreach (commits, i, child_packed_commit)
838 0 0         *(size_t *)git_array_alloc(index_stack) = i;
    0          
839              
840 0 0         while (git_array_size(index_stack)) {
841 0 0         size_t *index_ptr = git_array_pop(index_stack);
842 0           i = *index_ptr;
843 0           child_packed_commit = git_vector_get(commits, i);
844              
845 0 0         if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_VISITED) {
846             /* This commit has already been fully visited. */
847 0           continue;
848             }
849 0 0         if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_EXPANDED) {
850             /* All of the commits parents have been visited. */
851 0           child_packed_commit->generation = 0;
852 0 0         git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) {
    0          
853 0           struct packed_commit *parent = git_vector_get(commits, *parent_idx);
854 0 0         if (child_packed_commit->generation < parent->generation)
855 0           child_packed_commit->generation = parent->generation;
856             }
857 0 0         if (child_packed_commit->generation
858             < GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) {
859 0           ++child_packed_commit->generation;
860             }
861 0           commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED;
862 0           continue;
863             }
864              
865             /*
866             * This is the first time we see this commit. We need
867             * to visit all its parents before we can fully visit
868             * it.
869             */
870 0 0         if (git_array_size(child_packed_commit->parent_indices) == 0) {
871             /*
872             * Special case: if the commit has no parents, there's
873             * no need to add it to the stack just to immediately
874             * remove it.
875             */
876 0           commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED;
877 0           child_packed_commit->generation = 1;
878 0           continue;
879             }
880              
881             /*
882             * Add this current commit again so that it is visited
883             * again once all its children have been visited.
884             */
885 0 0         *(size_t *)git_array_alloc(index_stack) = i;
    0          
886 0 0         git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) {
    0          
887 0 0         if (commit_states[*parent_idx]
888 0           != GENERATION_NUMBER_COMMIT_STATE_UNVISITED) {
889             /* This commit has already been considered. */
890 0           continue;
891             }
892              
893 0           commit_states[*parent_idx] = GENERATION_NUMBER_COMMIT_STATE_ADDED;
894 0 0         *(size_t *)git_array_alloc(index_stack) = *parent_idx;
    0          
895             }
896 0           commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_EXPANDED;
897             }
898              
899             cleanup:
900 0           git_oidmap_free(packed_commit_map);
901 0           git__free(commit_states);
902 0           git_array_clear(index_stack);
903              
904 0           return error;
905             }
906              
907 0           static int write_offset(off64_t offset, commit_graph_write_cb write_cb, void *cb_data)
908             {
909             int error;
910             uint32_t word;
911              
912 0           word = htonl((uint32_t)((offset >> 32) & 0xffffffffu));
913 0           error = write_cb((const char *)&word, sizeof(word), cb_data);
914 0 0         if (error < 0)
915 0           return error;
916 0           word = htonl((uint32_t)((offset >> 0) & 0xffffffffu));
917 0           error = write_cb((const char *)&word, sizeof(word), cb_data);
918 0 0         if (error < 0)
919 0           return error;
920              
921 0           return 0;
922             }
923              
924 0           static int write_chunk_header(
925             int chunk_id,
926             off64_t offset,
927             commit_graph_write_cb write_cb,
928             void *cb_data)
929             {
930 0           uint32_t word = htonl(chunk_id);
931 0           int error = write_cb((const char *)&word, sizeof(word), cb_data);
932 0 0         if (error < 0)
933 0           return error;
934 0           return write_offset(offset, write_cb, cb_data);
935             }
936              
937 0           static int commit_graph_write_buf(const char *buf, size_t size, void *data)
938             {
939 0           git_str *b = (git_str *)data;
940 0           return git_str_put(b, buf, size);
941             }
942              
943             struct commit_graph_write_hash_context {
944             commit_graph_write_cb write_cb;
945             void *cb_data;
946             git_hash_ctx *ctx;
947             };
948              
949 0           static int commit_graph_write_hash(const char *buf, size_t size, void *data)
950             {
951 0           struct commit_graph_write_hash_context *ctx = data;
952             int error;
953              
954 0           error = git_hash_update(ctx->ctx, buf, size);
955 0 0         if (error < 0)
956 0           return error;
957              
958 0           return ctx->write_cb(buf, size, ctx->cb_data);
959             }
960              
961 0           static void packed_commit_free_dup(void *packed_commit)
962             {
963 0           packed_commit_free(packed_commit);
964 0           }
965              
966 0           static int commit_graph_write(
967             git_commit_graph_writer *w,
968             commit_graph_write_cb write_cb,
969             void *cb_data)
970             {
971 0           int error = 0;
972             size_t i;
973             struct packed_commit *packed_commit;
974 0           struct git_commit_graph_header hdr = {0};
975             uint32_t oid_fanout_count;
976             uint32_t extra_edge_list_count;
977             uint32_t oid_fanout[256];
978             off64_t offset;
979 0           git_str oid_lookup = GIT_STR_INIT, commit_data = GIT_STR_INIT,
980 0           extra_edge_list = GIT_STR_INIT;
981             unsigned char checksum[GIT_HASH_SHA1_SIZE];
982             size_t checksum_size;
983             git_hash_ctx ctx;
984 0           struct commit_graph_write_hash_context hash_cb_data = {0};
985              
986 0           hdr.signature = htonl(COMMIT_GRAPH_SIGNATURE);
987 0           hdr.version = COMMIT_GRAPH_VERSION;
988 0           hdr.object_id_version = COMMIT_GRAPH_OBJECT_ID_VERSION;
989 0           hdr.chunks = 0;
990 0           hdr.base_graph_files = 0;
991 0           hash_cb_data.write_cb = write_cb;
992 0           hash_cb_data.cb_data = cb_data;
993 0           hash_cb_data.ctx = &ctx;
994              
995 0           checksum_size = GIT_HASH_SHA1_SIZE;
996 0           error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1);
997 0 0         if (error < 0)
998 0           return error;
999 0           cb_data = &hash_cb_data;
1000 0           write_cb = commit_graph_write_hash;
1001              
1002             /* Sort the commits. */
1003 0           git_vector_sort(&w->commits);
1004 0           git_vector_uniq(&w->commits, packed_commit_free_dup);
1005 0           error = compute_generation_numbers(&w->commits);
1006 0 0         if (error < 0)
1007 0           goto cleanup;
1008              
1009             /* Fill the OID Fanout table. */
1010 0           oid_fanout_count = 0;
1011 0 0         for (i = 0; i < 256; i++) {
1012 0 0         while (oid_fanout_count < git_vector_length(&w->commits) &&
    0          
1013 0 0         (packed_commit = (struct packed_commit *)git_vector_get(&w->commits, oid_fanout_count)) &&
1014 0           packed_commit->sha1.id[0] <= i)
1015 0           ++oid_fanout_count;
1016 0           oid_fanout[i] = htonl(oid_fanout_count);
1017             }
1018              
1019             /* Fill the OID Lookup table. */
1020 0 0         git_vector_foreach (&w->commits, i, packed_commit) {
1021 0           error = git_str_put(&oid_lookup,
1022 0           (const char *)&packed_commit->sha1.id,
1023             GIT_OID_RAWSZ);
1024              
1025 0 0         if (error < 0)
1026 0           goto cleanup;
1027             }
1028              
1029             /* Fill the Commit Data and Extra Edge List tables. */
1030 0           extra_edge_list_count = 0;
1031 0 0         git_vector_foreach (&w->commits, i, packed_commit) {
1032             uint64_t commit_time;
1033             uint32_t generation;
1034             uint32_t word;
1035             size_t *packed_index;
1036 0           unsigned int parentcount = (unsigned int)git_array_size(packed_commit->parents);
1037              
1038 0           error = git_str_put(&commit_data,
1039 0           (const char *)&packed_commit->tree_oid.id,
1040             GIT_OID_RAWSZ);
1041              
1042 0 0         if (error < 0)
1043 0           goto cleanup;
1044              
1045 0 0         if (parentcount == 0) {
1046 0           word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT);
1047             } else {
1048 0 0         packed_index = git_array_get(packed_commit->parent_indices, 0);
1049 0           word = htonl((uint32_t)*packed_index);
1050             }
1051 0           error = git_str_put(&commit_data, (const char *)&word, sizeof(word));
1052 0 0         if (error < 0)
1053 0           goto cleanup;
1054              
1055 0 0         if (parentcount < 2) {
1056 0           word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT);
1057 0 0         } else if (parentcount == 2) {
1058 0 0         packed_index = git_array_get(packed_commit->parent_indices, 1);
1059 0           word = htonl((uint32_t)*packed_index);
1060             } else {
1061 0           word = htonl(0x80000000u | extra_edge_list_count);
1062             }
1063 0           error = git_str_put(&commit_data, (const char *)&word, sizeof(word));
1064 0 0         if (error < 0)
1065 0           goto cleanup;
1066              
1067 0 0         if (parentcount > 2) {
1068             unsigned int parent_i;
1069 0 0         for (parent_i = 1; parent_i < parentcount; ++parent_i) {
1070 0 0         packed_index = git_array_get(
1071             packed_commit->parent_indices, parent_i);
1072 0 0         word = htonl((uint32_t)(*packed_index | (parent_i + 1 == parentcount ? 0x80000000u : 0)));
1073              
1074 0           error = git_str_put(&extra_edge_list,
1075             (const char *)&word,
1076             sizeof(word));
1077 0 0         if (error < 0)
1078 0           goto cleanup;
1079             }
1080 0           extra_edge_list_count += parentcount - 1;
1081             }
1082              
1083 0           generation = packed_commit->generation;
1084 0           commit_time = (uint64_t)packed_commit->commit_time;
1085 0 0         if (generation > GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX)
1086 0           generation = GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX;
1087 0           word = ntohl((uint32_t)((generation << 2) | (((uint32_t)(commit_time >> 32)) & 0x3) ));
1088 0           error = git_str_put(&commit_data, (const char *)&word, sizeof(word));
1089 0 0         if (error < 0)
1090 0           goto cleanup;
1091 0           word = ntohl((uint32_t)(commit_time & 0xfffffffful));
1092 0           error = git_str_put(&commit_data, (const char *)&word, sizeof(word));
1093 0 0         if (error < 0)
1094 0           goto cleanup;
1095             }
1096              
1097             /* Write the header. */
1098 0           hdr.chunks = 3;
1099 0 0         if (git_str_len(&extra_edge_list) > 0)
1100 0           hdr.chunks++;
1101 0           error = write_cb((const char *)&hdr, sizeof(hdr), cb_data);
1102 0 0         if (error < 0)
1103 0           goto cleanup;
1104              
1105             /* Write the chunk headers. */
1106 0           offset = sizeof(hdr) + (hdr.chunks + 1) * 12;
1107 0           error = write_chunk_header(COMMIT_GRAPH_OID_FANOUT_ID, offset, write_cb, cb_data);
1108 0 0         if (error < 0)
1109 0           goto cleanup;
1110 0           offset += sizeof(oid_fanout);
1111 0           error = write_chunk_header(COMMIT_GRAPH_OID_LOOKUP_ID, offset, write_cb, cb_data);
1112 0 0         if (error < 0)
1113 0           goto cleanup;
1114 0           offset += git_str_len(&oid_lookup);
1115 0           error = write_chunk_header(COMMIT_GRAPH_COMMIT_DATA_ID, offset, write_cb, cb_data);
1116 0 0         if (error < 0)
1117 0           goto cleanup;
1118 0           offset += git_str_len(&commit_data);
1119 0 0         if (git_str_len(&extra_edge_list) > 0) {
1120 0           error = write_chunk_header(
1121             COMMIT_GRAPH_EXTRA_EDGE_LIST_ID, offset, write_cb, cb_data);
1122 0 0         if (error < 0)
1123 0           goto cleanup;
1124 0           offset += git_str_len(&extra_edge_list);
1125             }
1126 0           error = write_chunk_header(0, offset, write_cb, cb_data);
1127 0 0         if (error < 0)
1128 0           goto cleanup;
1129              
1130             /* Write all the chunks. */
1131 0           error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data);
1132 0 0         if (error < 0)
1133 0           goto cleanup;
1134 0           error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data);
1135 0 0         if (error < 0)
1136 0           goto cleanup;
1137 0           error = write_cb(git_str_cstr(&commit_data), git_str_len(&commit_data), cb_data);
1138 0 0         if (error < 0)
1139 0           goto cleanup;
1140 0           error = write_cb(git_str_cstr(&extra_edge_list), git_str_len(&extra_edge_list), cb_data);
1141 0 0         if (error < 0)
1142 0           goto cleanup;
1143              
1144             /* Finalize the checksum and write the trailer. */
1145 0           error = git_hash_final(checksum, &ctx);
1146 0 0         if (error < 0)
1147 0           goto cleanup;
1148 0           error = write_cb((char *)checksum, checksum_size, cb_data);
1149 0 0         if (error < 0)
1150 0           goto cleanup;
1151              
1152             cleanup:
1153 0           git_str_dispose(&oid_lookup);
1154 0           git_str_dispose(&commit_data);
1155 0           git_str_dispose(&extra_edge_list);
1156 0           git_hash_ctx_cleanup(&ctx);
1157 0           return error;
1158             }
1159              
1160 0           static int commit_graph_write_filebuf(const char *buf, size_t size, void *data)
1161             {
1162 0           git_filebuf *f = (git_filebuf *)data;
1163 0           return git_filebuf_write(f, buf, size);
1164             }
1165              
1166 0           int git_commit_graph_writer_options_init(
1167             git_commit_graph_writer_options *opts,
1168             unsigned int version)
1169             {
1170 0 0         GIT_INIT_STRUCTURE_FROM_TEMPLATE(
1171             opts,
1172             version,
1173             git_commit_graph_writer_options,
1174             GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT);
1175 0           return 0;
1176             }
1177              
1178 0           int git_commit_graph_writer_commit(
1179             git_commit_graph_writer *w,
1180             git_commit_graph_writer_options *opts)
1181             {
1182             int error;
1183 0           int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER;
1184 0           git_str commit_graph_path = GIT_STR_INIT;
1185 0           git_filebuf output = GIT_FILEBUF_INIT;
1186              
1187             /* TODO: support options and fill in defaults. */
1188 0           GIT_UNUSED(opts);
1189              
1190 0           error = git_str_joinpath(
1191 0           &commit_graph_path, git_str_cstr(&w->objects_info_dir), "commit-graph");
1192 0 0         if (error < 0)
1193 0           return error;
1194              
1195 0 0         if (git_repository__fsync_gitdir)
1196 0           filebuf_flags |= GIT_FILEBUF_FSYNC;
1197 0           error = git_filebuf_open(&output, git_str_cstr(&commit_graph_path), filebuf_flags, 0644);
1198 0           git_str_dispose(&commit_graph_path);
1199 0 0         if (error < 0)
1200 0           return error;
1201              
1202 0           error = commit_graph_write(w, commit_graph_write_filebuf, &output);
1203 0 0         if (error < 0) {
1204 0           git_filebuf_cleanup(&output);
1205 0           return error;
1206             }
1207              
1208 0           return git_filebuf_commit(&output);
1209             }
1210              
1211 0           int git_commit_graph_writer_dump(
1212             git_buf *cgraph,
1213             git_commit_graph_writer *w,
1214             git_commit_graph_writer_options *opts)
1215             {
1216 0 0         GIT_BUF_WRAP_PRIVATE(cgraph, git_commit_graph__writer_dump, w, opts);
    0          
1217             }
1218              
1219 0           int git_commit_graph__writer_dump(
1220             git_str *cgraph,
1221             git_commit_graph_writer *w,
1222             git_commit_graph_writer_options *opts)
1223             {
1224             /* TODO: support options. */
1225 0           GIT_UNUSED(opts);
1226 0           return commit_graph_write(w, commit_graph_write_buf, cgraph);
1227             }