#include "cache.h"
#include "config.h"
+#include "dir.h"
#include "git-compat-util.h"
#include "lockfile.h"
#include "pack.h"
#include "packfile.h"
#include "commit.h"
#include "object.h"
+#include "refs.h"
#include "revision.h"
#include "sha1-lookup.h"
#include "commit-graph.h"
#include "object-store.h"
+#include "alloc.h"
#define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
#define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
#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)
{
{
struct commit *c;
struct object_id oid;
+
+ if (pos >= g->num_commits)
+ die("invalid parent position %"PRIu64, pos);
+
hashcpy(oid.hash, g->chunk_oid_lookup + g->hash_len * pos);
c = lookup_commit(&oid);
if (!c)
return &commit_list_insert(c, pptr)->next;
}
+static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, uint32_t pos)
+{
+ const unsigned char *commit_data = g->chunk_commit_data + GRAPH_DATA_WIDTH * pos;
+ item->graph_pos = pos;
+ 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)
{
- struct object_id oid;
uint32_t edge_value;
uint32_t *parent_data_ptr;
uint64_t date_low, date_high;
item->object.parsed = 1;
item->graph_pos = pos;
- hashcpy(oid.hash, commit_data);
- item->tree = lookup_tree(&oid);
+ item->maybe_tree = NULL;
date_high = get_be32(commit_data + g->hash_len + 8) & 0x3;
date_low = get_be32(commit_data + g->hash_len + 12);
item->date = (timestamp_t)((date_high << 32) | date_low);
+ item->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
+
pptr = &item->parents;
edge_value = get_be32(commit_data + g->hash_len);
return 1;
}
-int parse_commit_in_graph(struct commit *item)
+static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
+{
+ if (item->graph_pos != COMMIT_NOT_FROM_GRAPH) {
+ *pos = item->graph_pos;
+ return 1;
+ } else {
+ return bsearch_graph(g, &(item->object.oid), pos);
+ }
+}
+
+static int parse_commit_in_graph_one(struct commit_graph *g, struct commit *item)
{
+ uint32_t pos;
+
if (!core_commit_graph)
return 0;
if (item->object.parsed)
return 1;
- prepare_commit_graph();
- if (commit_graph) {
- uint32_t pos;
- int found;
- if (item->graph_pos != COMMIT_NOT_FROM_GRAPH) {
- pos = item->graph_pos;
- found = 1;
- } else {
- found = bsearch_graph(commit_graph, &(item->object.oid), &pos);
- }
+ if (find_commit_in_graph(item, g, &pos))
+ return fill_commit_in_graph(item, g, pos);
- if (found)
- return fill_commit_in_graph(item, commit_graph, pos);
- }
+ return 0;
+}
+
+int parse_commit_in_graph(struct commit *item)
+{
+ if (!core_commit_graph)
+ return 0;
+ prepare_commit_graph();
+ if (commit_graph)
+ return parse_commit_in_graph_one(commit_graph, item);
return 0;
}
+void load_commit_graph_info(struct commit *item)
+{
+ uint32_t pos;
+ if (!core_commit_graph)
+ return;
+ prepare_commit_graph();
+ if (commit_graph && find_commit_in_graph(item, commit_graph, &pos))
+ fill_commit_graph_info(item, commit_graph, pos);
+}
+
+static struct tree *load_tree_for_commit(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(&oid);
+
+ return c->maybe_tree;
+}
+
+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_one called from non-commit-graph commit");
+
+ 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,
struct commit **commits,
int nr_commits)
uint32_t packedDate[2];
parse_commit(*list);
- hashwrite(f, (*list)->tree->object.oid.hash, hash_len);
+ hashwrite(f, get_commit_tree_oid(*list)->hash, hash_len);
parent = (*list)->parents;
else
packedDate[0] = 0;
+ packedDate[0] |= htonl((*list)->generation << 2);
+
packedDate[1] = htonl((*list)->date);
hashwrite(f, packedDate, 8);
struct object_info oi = OBJECT_INFO_INIT;
oi.typep = &type;
- if (packed_object_info(pack, offset, &oi) < 0)
+ if (packed_object_info(the_repository, pack, offset, &oi) < 0)
die("unable to get type of object %s", oid_to_hex(oid));
if (type != OBJ_COMMIT)
}
}
+static void compute_generation_numbers(struct packed_commit_list* commits)
+{
+ int i;
+ struct commit_list *list = NULL;
+
+ for (i = 0; i < commits->nr; i++) {
+ if (commits->list[i]->generation != GENERATION_NUMBER_INFINITY &&
+ commits->list[i]->generation != GENERATION_NUMBER_ZERO)
+ continue;
+
+ commit_list_insert(commits->list[i], &list);
+ while (list) {
+ struct commit *current = list->item;
+ struct commit_list *parent;
+ int all_parents_computed = 1;
+ uint32_t max_generation = 0;
+
+ for (parent = current->parents; parent; parent = parent->next) {
+ if (parent->item->generation == GENERATION_NUMBER_INFINITY ||
+ parent->item->generation == GENERATION_NUMBER_ZERO) {
+ all_parents_computed = 0;
+ commit_list_insert(parent->item, &list);
+ break;
+ } else if (parent->item->generation > max_generation) {
+ max_generation = parent->item->generation;
+ }
+ }
+
+ if (all_parents_computed) {
+ current->generation = max_generation + 1;
+ pop_commit(&list);
+
+ if (current->generation > GENERATION_NUMBER_MAX)
+ current->generation = GENERATION_NUMBER_MAX;
+ }
+ }
+ }
+}
+
+static int add_ref_to_list(const char *refname,
+ const struct object_id *oid,
+ int flags, void *cb_data)
+{
+ struct string_list *list = (struct string_list *)cb_data;
+
+ string_list_append(list, oid_to_hex(oid));
+ return 0;
+}
+
+void write_commit_graph_reachable(const char *obj_dir, int append)
+{
+ struct string_list list;
+
+ string_list_init(&list, 1);
+ for_each_ref(add_ref_to_list, &list);
+ write_commit_graph(obj_dir, NULL, &list, append);
+}
+
void write_commit_graph(const char *obj_dir,
- const char **pack_indexes,
- int nr_packs,
- const char **commit_hex,
- int nr_commits,
+ struct string_list *pack_indexes,
+ struct string_list *commit_hex,
int append)
{
struct packed_oid_list oids;
struct hashfile *f;
uint32_t i, count_distinct = 0;
char *graph_name;
- int fd;
struct lock_file lk = LOCK_INIT;
uint32_t chunk_ids[5];
uint64_t chunk_offsets[5];
int dirlen;
strbuf_addf(&packname, "%s/pack/", obj_dir);
dirlen = packname.len;
- for (i = 0; i < nr_packs; i++) {
+ for (i = 0; i < pack_indexes->nr; i++) {
struct packed_git *p;
strbuf_setlen(&packname, dirlen);
- strbuf_addstr(&packname, pack_indexes[i]);
+ strbuf_addstr(&packname, pack_indexes->items[i].string);
p = add_packed_git(packname.buf, packname.len, 1);
if (!p)
die("error adding pack %s", packname.buf);
}
if (commit_hex) {
- for (i = 0; i < nr_commits; i++) {
+ for (i = 0; i < commit_hex->nr; i++) {
const char *end;
struct object_id oid;
struct commit *result;
- if (commit_hex[i] && parse_oid_hex(commit_hex[i], &oid, &end))
+ if (commit_hex->items[i].string &&
+ parse_oid_hex(commit_hex->items[i].string, &oid, &end))
continue;
result = lookup_commit_reference_gently(&oid, 1);
if (commits.nr >= GRAPH_PARENT_MISSING)
die(_("too many commits to write graph"));
- graph_name = get_commit_graph_filename(obj_dir);
- fd = hold_lock_file_for_update(&lk, graph_name, 0);
-
- if (fd < 0) {
- struct strbuf folder = STRBUF_INIT;
- strbuf_addstr(&folder, graph_name);
- strbuf_setlen(&folder, strrchr(folder.buf, '/') - folder.buf);
-
- if (mkdir(folder.buf, 0777) < 0)
- die_errno(_("cannot mkdir %s"), folder.buf);
- strbuf_release(&folder);
+ compute_generation_numbers(&commits);
- fd = hold_lock_file_for_update(&lk, graph_name, LOCK_DIE_ON_ERROR);
-
- if (fd < 0)
- die_errno("unable to create '%s'", graph_name);
- }
+ graph_name = get_commit_graph_filename(obj_dir);
+ if (safe_create_leading_directories(graph_name))
+ die_errno(_("unable to create leading directories of %s"),
+ graph_name);
+ hold_lock_file_for_update(&lk, graph_name, LOCK_DIE_ON_ERROR);
f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
hashwrite_be32(f, GRAPH_SIGNATURE);
oids.alloc = 0;
oids.nr = 0;
}
+
+#define VERIFY_COMMIT_GRAPH_ERROR_HASH 2
+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);
+}
+
+#define GENERATION_ZERO_EXISTS 1
+#define GENERATION_NUMBER_EXISTS 2
+
+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, checksum;
+ int generation_zero = 0;
+ struct hashfile *f;
+ int devnull;
+
+ 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;
+
+ devnull = open("/dev/null", O_WRONLY);
+ f = hashfd(devnull, NULL);
+ hashwrite(f, g->data, g->data_len - g->hash_len);
+ finalize_hashfile(f, checksum.hash, CSUM_CLOSE);
+ if (hashcmp(checksum.hash, g->data + g->data_len - g->hash_len)) {
+ graph_report(_("the commit-graph file has incorrect checksum and is likely corrupt"));
+ verify_commit_graph_error = VERIFY_COMMIT_GRAPH_ERROR_HASH;
+ }
+
+ for (i = 0; i < g->num_commits; i++) {
+ struct commit *graph_commit;
+
+ 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++;
+ }
+
+ graph_commit = lookup_commit(&cur_oid);
+ if (!parse_commit_in_graph_one(g, graph_commit))
+ graph_report("failed to parse %s from commit-graph",
+ oid_to_hex(&cur_oid));
+ }
+
+ 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++;
+ }
+
+ if (verify_commit_graph_error & ~VERIFY_COMMIT_GRAPH_ERROR_HASH)
+ return verify_commit_graph_error;
+
+ for (i = 0; i < g->num_commits; i++) {
+ struct commit *graph_commit, *odb_commit;
+ struct commit_list *graph_parents, *odb_parents;
+ uint32_t max_generation = 0;
+
+ hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
+
+ graph_commit = lookup_commit(&cur_oid);
+ odb_commit = (struct commit *)create_object(r, cur_oid.hash, alloc_commit_node(r));
+ if (parse_commit_internal(odb_commit, 0, 0)) {
+ graph_report("failed to parse %s from object database",
+ oid_to_hex(&cur_oid));
+ continue;
+ }
+
+ if (oidcmp(&get_commit_tree_in_graph_one(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),
+ oid_to_hex(get_commit_tree_oid(graph_commit)),
+ oid_to_hex(get_commit_tree_oid(odb_commit)));
+
+ graph_parents = graph_commit->parents;
+ odb_parents = odb_commit->parents;
+
+ while (graph_parents) {
+ if (odb_parents == NULL) {
+ graph_report("commit-graph parent list for commit %s is too long",
+ oid_to_hex(&cur_oid));
+ break;
+ }
+
+ if (oidcmp(&graph_parents->item->object.oid, &odb_parents->item->object.oid))
+ graph_report("commit-graph parent for %s is %s != %s",
+ oid_to_hex(&cur_oid),
+ oid_to_hex(&graph_parents->item->object.oid),
+ oid_to_hex(&odb_parents->item->object.oid));
+
+ if (graph_parents->item->generation > max_generation)
+ max_generation = graph_parents->item->generation;
+
+ graph_parents = graph_parents->next;
+ odb_parents = odb_parents->next;
+ }
+
+ if (odb_parents != NULL)
+ graph_report("commit-graph parent list for commit %s terminates early",
+ oid_to_hex(&cur_oid));
+
+ if (!graph_commit->generation) {
+ if (generation_zero == GENERATION_NUMBER_EXISTS)
+ graph_report("commit-graph has generation number zero for commit %s, but non-zero elsewhere",
+ oid_to_hex(&cur_oid));
+ generation_zero = GENERATION_ZERO_EXISTS;
+ } else if (generation_zero == GENERATION_ZERO_EXISTS)
+ graph_report("commit-graph has non-zero generation number for commit %s, but zero elsewhere",
+ oid_to_hex(&cur_oid));
+
+ if (generation_zero == GENERATION_ZERO_EXISTS)
+ continue;
+
+ /*
+ * If one of our parents has generation GENERATION_NUMBER_MAX, then
+ * our generation is also GENERATION_NUMBER_MAX. Decrement to avoid
+ * extra logic in the following condition.
+ */
+ if (max_generation == GENERATION_NUMBER_MAX)
+ max_generation--;
+
+ if (graph_commit->generation != max_generation + 1)
+ graph_report("commit-graph generation for commit %s is %u != %u",
+ oid_to_hex(&cur_oid),
+ graph_commit->generation,
+ max_generation + 1);
+
+ if (graph_commit->date != odb_commit->date)
+ graph_report("commit date for commit %s in commit-graph is %"PRItime" != %"PRItime,
+ oid_to_hex(&cur_oid),
+ graph_commit->date,
+ odb_commit->date);
+ }
+
+ return verify_commit_graph_error;
+}