Skip to content

Commit

Permalink
Merge branch 'ds/commit-graph-lockfile-fix'
Browse files Browse the repository at this point in the history
Update to ds/generation-numbers topic.

* ds/commit-graph-lockfile-fix:
  commit-graph: fix UX issue when .lock file exists
  commit-graph.txt: update design document
  merge: check config before loading commits
  commit: use generation number in remove_redundant()
  commit: add short-circuit to paint_down_to_common()
  commit: use generation numbers for in_merge_bases()
  ref-filter: use generation number for --contains
  commit-graph: always load commit-graph information
  commit: use generations in paint_down_to_common()
  commit-graph: compute generation numbers
  commit: add generation number to struct commit
  ref-filter: fix outdated comment on in_commit_list
  • Loading branch information
gitster committed Jun 25, 2018
2 parents b3b2aaf + 33286dc commit a856e7d
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 56 deletions.
29 changes: 24 additions & 5 deletions Documentation/technical/commit-graph.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,29 @@ in the commit graph. We can treat these commits as having "infinite"
generation number and walk until reaching commits with known generation
number.

We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not
in the commit-graph file. If a commit-graph file was written by a version
of Git that did not compute generation numbers, then those commits will
have generation number represented by the macro GENERATION_NUMBER_ZERO = 0.

Since the commit-graph file is closed under reachability, we can guarantee
the following weaker condition on all commits:

If A and B are commits with generation numbers N amd M, respectively,
and N < M, then A cannot reach B.

Note how the strict inequality differs from the inequality when we have
fully-computed generation numbers. Using strict inequality may result in
walking a few extra commits, but the simplicity in dealing with commits
with generation number *_INFINITY or *_ZERO is valuable.

We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose
generation numbers are computed to be at least this value. We limit at
this value since it is the largest value that can be stored in the
commit-graph file using the 30 bits available to generation numbers. This
presents another case where a commit can have generation number equal to
that of a parent.

Design Details
--------------

Expand All @@ -98,18 +121,14 @@ Future Work
- The 'commit-graph' subcommand does not have a "verify" mode that is
necessary for integration with fsck.

- The file format includes room for precomputed generation numbers. These
are not currently computed, so all generation numbers will be marked as
0 (or "uncomputed"). A later patch will include this calculation.

- After computing and storing generation numbers, we must make graph
walks aware of generation numbers to gain the performance benefits they
enable. This will mostly be accomplished by swapping a commit-date-ordered
priority queue with one ordered by generation number. The following
operations are important candidates:

- paint_down_to_common()
- 'log --topo-order'
- 'tag --merged'

- Currently, parse_commit_gently() requires filling in the root tree
object for a commit. This passes through lookup_tree() and consequently
Expand Down
1 change: 1 addition & 0 deletions alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ void *alloc_commit_node(void)
c->object.type = OBJ_COMMIT;
c->index = alloc_commit_index();
c->graph_pos = COMMIT_NOT_FROM_GRAPH;
c->generation = GENERATION_NUMBER_INFINITY;
return c;
}

Expand Down
7 changes: 4 additions & 3 deletions builtin/merge.c
Original file line number Diff line number Diff line change
Expand Up @@ -1186,14 +1186,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
branch = branch_to_free = resolve_refdup("HEAD", 0, &head_oid, NULL);
if (branch)
skip_prefix(branch, "refs/heads/", &branch);

init_diff_ui_defaults();
git_config(git_merge_config, NULL);

if (!branch || is_null_oid(&head_oid))
head_commit = NULL;
else
head_commit = lookup_commit_or_die(&head_oid, "HEAD");

init_diff_ui_defaults();
git_config(git_merge_config, NULL);

if (branch_mergeoptions)
parse_branch_merge_options(branch_mergeoptions);
argc = parse_options(argc, argv, prefix, builtin_merge_options,
Expand Down
113 changes: 81 additions & 32 deletions commit-graph.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "cache.h"
#include "config.h"
#include "dir.h"
#include "git-compat-util.h"
#include "lockfile.h"
#include "pack.h"
Expand Down Expand Up @@ -248,6 +249,13 @@ static struct commit_list **insert_parent_or_die(struct commit_graph *g,
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)
{
uint32_t edge_value;
Expand All @@ -265,6 +273,8 @@ static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uin
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);
Expand Down Expand Up @@ -293,31 +303,40 @@ static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uin
return 1;
}

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);
}
}

int parse_commit_in_graph(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 (found)
return fill_commit_in_graph(item, commit_graph, pos);
}

if (commit_graph && find_commit_in_graph(item, commit_graph, &pos))
return fill_commit_in_graph(item, commit_graph, pos);
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;
Expand Down Expand Up @@ -440,6 +459,8 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
else
packedDate[0] = 0;

packedDate[0] |= htonl((*list)->generation << 2);

packedDate[1] = htonl((*list)->date);
hashwrite(f, packedDate, 8);

Expand Down Expand Up @@ -572,6 +593,45 @@ static void close_reachable(struct packed_oid_list *oids)
}
}

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;
}
}
}
}

void write_commit_graph(const char *obj_dir,
const char **pack_indexes,
int nr_packs,
Expand All @@ -584,7 +644,6 @@ void write_commit_graph(const char *obj_dir,
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];
Expand Down Expand Up @@ -695,24 +754,14 @@ void write_commit_graph(const char *obj_dir,
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);

fd = hold_lock_file_for_update(&lk, graph_name, LOCK_DIE_ON_ERROR);
compute_generation_numbers(&commits);

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);
Expand Down
8 changes: 8 additions & 0 deletions commit-graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ char *get_commit_graph_filename(const char *obj_dir);
*/
int parse_commit_in_graph(struct commit *item);

/*
* It is possible that we loaded commit contents from the commit buffer,
* but we also want to ensure the commit-graph content is correctly
* checked and filled. Fill the graph_pos and generation members of
* the given commit.
*/
void load_commit_graph_info(struct commit *item);

struct tree *get_commit_tree_in_graph(const struct commit *c);

struct commit_graph {
Expand Down
Loading

0 comments on commit a856e7d

Please sign in to comment.