
The bitmap_writer_build() method calls bitmap_builder_init() to construct a list of commits reachable from the selected commits along with a "reverse graph". This reverse graph has edges pointing from a commit to other commits that can reach that commit. After computing a reachability bitmap for a commit, the values in that bitmap are then copied to the reachability bitmaps across the edges in the reverse graph. We can now relax the role of the reverse graph to greatly reduce the number of intermediate reachability bitmaps we compute during this reverse walk. The end result is that we walk objects the same number of times as before when constructing the reachability bitmaps, but we also spend much less time copying bits between bitmaps and have much lower memory pressure in the process. The core idea is to select a set of "important" commits based on interactions among the sets of commits reachable from each selected commit. The first technical concept is to create a new 'commit_mask' member in the bb_commit struct. Note that the selected commits are provided in an ordered array. The first thing to do is to mark the ith bit in the commit_mask for the ith selected commit. As we walk the commit-graph, we copy the bits in a commit's commit_mask to its parents. At the end of the walk, the ith bit in the commit_mask for a commit C stores a boolean representing "The ith selected commit can reach C." As we walk, we will discover non-selected commits that are important. We will get into this later, but those important commits must also receive bit positions, growing the width of the bitmasks as we walk. At the true end of the walk, the ith bit means "the ith _important_ commit can reach C." MAXIMAL COMMITS --------------- We use a new 'maximal' bit in the bb_commit struct to represent whether a commit is important or not. The term "maximal" comes from the partially-ordered set of commits in the commit-graph where C >= P if P is a parent of C, and then extending the relationship transitively. Instead of taking the maximal commits across the entire commit-graph, we instead focus on selecting each commit that is maximal among commits with the same bits on in their commit_mask. This definition is important, so let's consider an example. Suppose we have three selected commits A, B, and C. These are assigned bitmasks 100, 010, and 001 to start. Each of these can be marked as maximal immediately because they each will be the uniquely maximal commit that contains their own bit. Keep in mind that that these commits may have different bitmasks after the walk; for example, if B can reach C but A cannot, then the final bitmask for C is 011. Even in these cases, C would still be a maximal commit among all commits with the third bit on in their masks. Now define sets X, Y, and Z to be the sets of commits reachable from A, B, and C, respectively. The intersections of these sets correspond to different bitmasks: * 100: X - (Y union Z) * 010: Y - (X union Z) * 001: Z - (X union Y) * 110: (X intersect Y) - Z * 101: (X intersect Z) - Y * 011: (Y intersect Z) - X * 111: X intersect Y intersect Z This can be visualized with the following Hasse diagram: 100 010 001 | \ / \ / | | \/ \/ | | /\ /\ | | / \ / \ | 110 101 011 \___ | ___/ \ | / 111 Some of these bitmasks may not be represented, depending on the topology of the commit-graph. In fact, we are counting on it, since the number of possible bitmasks is exponential in the number of selected commits, but is also limited by the total number of commits. In practice, very few bitmasks are possible because most commits converge on a common "trunk" in the commit history. With this three-bit example, we wish to find commits that are maximal for each bitmask. How can we identify this as we are walking? As we walk, we visit a commit C. Since we are walking the commits in topo-order, we know that C is visited after all of its children are visited. Thus, when we get C from the revision walk we inspect the 'maximal' property of its bb_data and use that to determine if C is truly important. Its commit_mask is also nearly final. If C is not one of the originally-selected commits, then assign a bit position to C (by incrementing num_maximal) and set that bit on in commit_mask. See "MULTIPLE MAXIMAL COMMITS" below for more detail on this. Now that the commit C is known to be maximal or not, consider each parent P of C. Compute two new values: * c_not_p : true if and only if the commit_mask for C contains a bit that is not contained in the commit_mask for P. * p_not_c : true if and only if the commit_mask for P contains a bit that is not contained in the commit_mask for P. If c_not_p is false, then P already has all of the bits that C would provide to its commit_mask. In this case, move on to other parents as C has nothing to contribute to P's state that was not already provided by other children of P. We continue with the case that c_not_p is true. This means there are bits in C's commit_mask to copy to P's commit_mask, so use bitmap_or() to add those bits. If p_not_c is also true, then set the maximal bit for P to one. This means that if no other commit has P as a parent, then P is definitely maximal. This is because no child had the same bitmask. It is important to think about the maximal bit for P at this point as a temporary state: "P is maximal based on current information." In contrast, if p_not_c is false, then set the maximal bit for P to zero. Further, clear all reverse_edges for P since any edges that were previously assigned to P are no longer important. P will gain all reverse edges based on C. The final thing we need to do is to update the reverse edges for P. These reverse edges respresent "which closest maximal commits contributed bits to my commit_mask?" Since C contributed bits to P's commit_mask in this case, C must add to the reverse edges of P. If C is maximal, then C is a 'closest' maximal commit that contributed bits to P. Add C to P's reverse_edges list. Otherwise, C has a list of maximal commits that contributed bits to its bitmask (and this list is exactly one element). Add all of these items to P's reverse_edges list. Be careful to ignore duplicates here. After inspecting all parents P for a commit C, we can clear the commit_mask for C. This reduces the memory load to be limited to the "width" of the commit graph. Consider our ABC/XYZ example from earlier and let's inspect the state of the commits for an interesting bitmask, say 011. Suppose that D is the only maximal commit with this bitmask (in the first three bits). All other commits with bitmask 011 have D as the only entry in their reverse_edges list. D's reverse_edges list contains B and C. COMPUTING REACHABILITY BITMAPS ------------------------------ Now that we have our definition, let's zoom out and consider what happens with our new reverse graph when computing reachability bitmaps. We walk the reverse graph in reverse-topo-order, so we visit commits with largest commit_masks first. After we compute the reachability bitmap for a commit C, we push the bits in that bitmap to each commit D in the reverse edge list for C. Then, when we finally visit D we already have the bits for everything reachable from maximal commits that D can reach and we only need to walk the objects in the set-difference. In our ABC/XYZ example, when we finally walk for the commit A we only need to walk commits with bitmask equal to A's bitmask. If that bitmask is 100, then we are only walking commits in X - (Y union Z) because the bitmap already contains the bits for objects reachable from (X intersect Y) union (X intersect Z) (i.e. the bits from the reachability bitmaps for the maximal commits with bitmasks 110 and 101). The behavior is intended to walk each commit (and the trees that commit introduces) at most once while allocating and copying fewer reachability bitmaps. There is one caveat: what happens when there are multiple maximal commits with the same bitmask, with respect to the initial set of selected commits? MULTIPLE MAXIMAL COMMITS ------------------------ Earlier, we mentioned that when we discover a new maximal commit, we assign a new bit position to that commit and set that bit position to one for that commit. This is absolutely important for interesting commit-graphs such as git/git and torvalds/linux. The reason is due to the existence of "butterflies" in the commit-graph partial order. Here is an example of four commits forming a butterfly: I J |\ /| | \/ | | /\ | |/ \| M N \ / |/ Q Here, I and J both have parents M and N. In general, these do not need to be exact parent relationships, but reachability relationships. The most important part is that M and N cannot reach each other, so they are independent in the partial order. If I had commit_mask 10 and J had commit_mask 01, then M and N would both be assigned commit_mask 11 and be maximal commits with the bitmask 11. Then, what happens when M and N can both reach a commit Q? If Q is also assigned the bitmask 11, then it is not maximal but is reachable from both M and N. While this is not necessarily a deal-breaker for our abstract definition of finding maximal commits according to a given bitmask, we have a few issues that can come up in our larger picture of constructing reachability bitmaps. In particular, if we do not also consider Q to be a "maximal" commit, then we will walk commits reachable from Q twice: once when computing the reachability bitmap for M and another time when computing the reachability bitmap for N. This becomes much worse if the topology continues this pattern with multiple butterflies. The solution has already been mentioned: each of M and N are assigned their own bits to the bitmask and hence they become uniquely maximal for their bitmasks. Finally, Q also becomes maximal and thus we do not need to walk its commits multiple times. The final bitmasks for these commits are as follows: I:10 J:01 |\ /| | \ _____/ | | /\____ | |/ \ | M:111 N:1101 \ / Q:1111 Further, Q's reverse edge list is { M, N }, while M and N both have reverse edge list { I, J }. PERFORMANCE MEASUREMENTS ------------------------ Now that we've spent a LOT of time on the theory of this algorithm, let's show that this is actually worth all that effort. To test the performance, use GIT_TRACE2_PERF=1 when running 'git repack -abd' in a repository with no existing reachability bitmaps. This avoids any issues with keeping existing bitmaps to skew the numbers. Inspect the "building_bitmaps_total" region in the trace2 output to focus on the portion of work that is affected by this change. Here are the performance comparisons for a few repositories. The timings are for the following versions of Git: "multi" is the timing from before any reverse graph is constructed, where we might perform multiple traversals. "reverse" is for the previous change where the reverse graph has every reachable commit. Finally "maximal" is the version introduced here where the reverse graph only contains the maximal commits. Repository: git/git multi: 2.628 sec reverse: 2.344 sec maximal: 2.047 sec Repository: torvalds/linux multi: 64.7 sec reverse: 205.3 sec maximal: 44.7 sec So in all cases we've not only recovered any time lost to switching to the reverse-edge algorithm, but we come out ahead of "multi" in all cases. Likewise, peak heap has gone back to something reasonable: Repository: torvalds/linux multi: 2.087 GB reverse: 3.141 GB maximal: 2.288 GB While I do not have access to full fork networks on GitHub, Peff has run this algorithm on the chromium/chromium fork network and reported a change from 3 hours to ~233 seconds. That network is particularly beneficial for this approach because it has a long, linear history along with many tags. The "multi" approach was obviously quadratic and the new approach is linear. Helped-by: Jeff King <peff@peff.net> Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
677 lines
16 KiB
C
677 lines
16 KiB
C
#include "cache.h"
|
|
#include "object-store.h"
|
|
#include "commit.h"
|
|
#include "tag.h"
|
|
#include "diff.h"
|
|
#include "revision.h"
|
|
#include "list-objects.h"
|
|
#include "progress.h"
|
|
#include "pack-revindex.h"
|
|
#include "pack.h"
|
|
#include "pack-bitmap.h"
|
|
#include "sha1-lookup.h"
|
|
#include "pack-objects.h"
|
|
#include "commit-reach.h"
|
|
#include "prio-queue.h"
|
|
|
|
struct bitmapped_commit {
|
|
struct commit *commit;
|
|
struct ewah_bitmap *bitmap;
|
|
struct ewah_bitmap *write_as;
|
|
int flags;
|
|
int xor_offset;
|
|
uint32_t commit_pos;
|
|
};
|
|
|
|
struct bitmap_writer {
|
|
struct ewah_bitmap *commits;
|
|
struct ewah_bitmap *trees;
|
|
struct ewah_bitmap *blobs;
|
|
struct ewah_bitmap *tags;
|
|
|
|
kh_oid_map_t *bitmaps;
|
|
kh_oid_map_t *reused;
|
|
struct packing_data *to_pack;
|
|
|
|
struct bitmapped_commit *selected;
|
|
unsigned int selected_nr, selected_alloc;
|
|
|
|
struct progress *progress;
|
|
int show_progress;
|
|
unsigned char pack_checksum[GIT_MAX_RAWSZ];
|
|
};
|
|
|
|
static struct bitmap_writer writer;
|
|
|
|
void bitmap_writer_show_progress(int show)
|
|
{
|
|
writer.show_progress = show;
|
|
}
|
|
|
|
/**
|
|
* Build the initial type index for the packfile
|
|
*/
|
|
void bitmap_writer_build_type_index(struct packing_data *to_pack,
|
|
struct pack_idx_entry **index,
|
|
uint32_t index_nr)
|
|
{
|
|
uint32_t i;
|
|
|
|
writer.commits = ewah_new();
|
|
writer.trees = ewah_new();
|
|
writer.blobs = ewah_new();
|
|
writer.tags = ewah_new();
|
|
ALLOC_ARRAY(to_pack->in_pack_pos, to_pack->nr_objects);
|
|
|
|
for (i = 0; i < index_nr; ++i) {
|
|
struct object_entry *entry = (struct object_entry *)index[i];
|
|
enum object_type real_type;
|
|
|
|
oe_set_in_pack_pos(to_pack, entry, i);
|
|
|
|
switch (oe_type(entry)) {
|
|
case OBJ_COMMIT:
|
|
case OBJ_TREE:
|
|
case OBJ_BLOB:
|
|
case OBJ_TAG:
|
|
real_type = oe_type(entry);
|
|
break;
|
|
|
|
default:
|
|
real_type = oid_object_info(to_pack->repo,
|
|
&entry->idx.oid, NULL);
|
|
break;
|
|
}
|
|
|
|
switch (real_type) {
|
|
case OBJ_COMMIT:
|
|
ewah_set(writer.commits, i);
|
|
break;
|
|
|
|
case OBJ_TREE:
|
|
ewah_set(writer.trees, i);
|
|
break;
|
|
|
|
case OBJ_BLOB:
|
|
ewah_set(writer.blobs, i);
|
|
break;
|
|
|
|
case OBJ_TAG:
|
|
ewah_set(writer.tags, i);
|
|
break;
|
|
|
|
default:
|
|
die("Missing type information for %s (%d/%d)",
|
|
oid_to_hex(&entry->idx.oid), real_type,
|
|
oe_type(entry));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compute the actual bitmaps
|
|
*/
|
|
|
|
static inline void push_bitmapped_commit(struct commit *commit, struct ewah_bitmap *reused)
|
|
{
|
|
if (writer.selected_nr >= writer.selected_alloc) {
|
|
writer.selected_alloc = (writer.selected_alloc + 32) * 2;
|
|
REALLOC_ARRAY(writer.selected, writer.selected_alloc);
|
|
}
|
|
|
|
writer.selected[writer.selected_nr].commit = commit;
|
|
writer.selected[writer.selected_nr].bitmap = reused;
|
|
writer.selected[writer.selected_nr].flags = 0;
|
|
|
|
writer.selected_nr++;
|
|
}
|
|
|
|
static uint32_t find_object_pos(const struct object_id *oid)
|
|
{
|
|
struct object_entry *entry = packlist_find(writer.to_pack, oid);
|
|
|
|
if (!entry) {
|
|
die("Failed to write bitmap index. Packfile doesn't have full closure "
|
|
"(object %s is missing)", oid_to_hex(oid));
|
|
}
|
|
|
|
return oe_in_pack_pos(writer.to_pack, entry);
|
|
}
|
|
|
|
static void compute_xor_offsets(void)
|
|
{
|
|
static const int MAX_XOR_OFFSET_SEARCH = 10;
|
|
|
|
int i, next = 0;
|
|
|
|
while (next < writer.selected_nr) {
|
|
struct bitmapped_commit *stored = &writer.selected[next];
|
|
|
|
int best_offset = 0;
|
|
struct ewah_bitmap *best_bitmap = stored->bitmap;
|
|
struct ewah_bitmap *test_xor;
|
|
|
|
for (i = 1; i <= MAX_XOR_OFFSET_SEARCH; ++i) {
|
|
int curr = next - i;
|
|
|
|
if (curr < 0)
|
|
break;
|
|
|
|
test_xor = ewah_pool_new();
|
|
ewah_xor(writer.selected[curr].bitmap, stored->bitmap, test_xor);
|
|
|
|
if (test_xor->buffer_size < best_bitmap->buffer_size) {
|
|
if (best_bitmap != stored->bitmap)
|
|
ewah_pool_free(best_bitmap);
|
|
|
|
best_bitmap = test_xor;
|
|
best_offset = i;
|
|
} else {
|
|
ewah_pool_free(test_xor);
|
|
}
|
|
}
|
|
|
|
stored->xor_offset = best_offset;
|
|
stored->write_as = best_bitmap;
|
|
|
|
next++;
|
|
}
|
|
}
|
|
|
|
struct bb_commit {
|
|
struct commit_list *reverse_edges;
|
|
struct bitmap *commit_mask;
|
|
struct bitmap *bitmap;
|
|
unsigned selected:1,
|
|
maximal:1;
|
|
unsigned idx; /* within selected array */
|
|
};
|
|
|
|
define_commit_slab(bb_data, struct bb_commit);
|
|
|
|
struct bitmap_builder {
|
|
struct bb_data data;
|
|
struct commit **commits;
|
|
size_t commits_nr, commits_alloc;
|
|
};
|
|
|
|
static void bitmap_builder_init(struct bitmap_builder *bb,
|
|
struct bitmap_writer *writer)
|
|
{
|
|
struct rev_info revs;
|
|
struct commit *commit;
|
|
unsigned int i, num_maximal;
|
|
|
|
memset(bb, 0, sizeof(*bb));
|
|
init_bb_data(&bb->data);
|
|
|
|
reset_revision_walk();
|
|
repo_init_revisions(writer->to_pack->repo, &revs, NULL);
|
|
revs.topo_order = 1;
|
|
|
|
for (i = 0; i < writer->selected_nr; i++) {
|
|
struct commit *c = writer->selected[i].commit;
|
|
struct bb_commit *ent = bb_data_at(&bb->data, c);
|
|
|
|
ent->selected = 1;
|
|
ent->maximal = 1;
|
|
ent->idx = i;
|
|
|
|
ent->commit_mask = bitmap_new();
|
|
bitmap_set(ent->commit_mask, i);
|
|
|
|
add_pending_object(&revs, &c->object, "");
|
|
}
|
|
num_maximal = writer->selected_nr;
|
|
|
|
if (prepare_revision_walk(&revs))
|
|
die("revision walk setup failed");
|
|
|
|
while ((commit = get_revision(&revs))) {
|
|
struct commit_list *p;
|
|
struct bb_commit *c_ent;
|
|
|
|
parse_commit_or_die(commit);
|
|
|
|
c_ent = bb_data_at(&bb->data, commit);
|
|
|
|
if (c_ent->maximal) {
|
|
if (!c_ent->selected) {
|
|
bitmap_set(c_ent->commit_mask, num_maximal);
|
|
num_maximal++;
|
|
}
|
|
|
|
ALLOC_GROW(bb->commits, bb->commits_nr + 1, bb->commits_alloc);
|
|
bb->commits[bb->commits_nr++] = commit;
|
|
}
|
|
|
|
for (p = commit->parents; p; p = p->next) {
|
|
struct bb_commit *p_ent = bb_data_at(&bb->data, p->item);
|
|
int c_not_p, p_not_c;
|
|
|
|
if (!p_ent->commit_mask) {
|
|
p_ent->commit_mask = bitmap_new();
|
|
c_not_p = 1;
|
|
p_not_c = 0;
|
|
} else {
|
|
c_not_p = bitmap_is_subset(c_ent->commit_mask, p_ent->commit_mask);
|
|
p_not_c = bitmap_is_subset(p_ent->commit_mask, c_ent->commit_mask);
|
|
}
|
|
|
|
if (!c_not_p)
|
|
continue;
|
|
|
|
bitmap_or(p_ent->commit_mask, c_ent->commit_mask);
|
|
|
|
if (p_not_c)
|
|
p_ent->maximal = 1;
|
|
else {
|
|
p_ent->maximal = 0;
|
|
free_commit_list(p_ent->reverse_edges);
|
|
p_ent->reverse_edges = NULL;
|
|
}
|
|
|
|
if (c_ent->maximal) {
|
|
commit_list_insert(commit, &p_ent->reverse_edges);
|
|
} else {
|
|
struct commit_list *cc = c_ent->reverse_edges;
|
|
|
|
for (; cc; cc = cc->next) {
|
|
if (!commit_list_contains(cc->item, p_ent->reverse_edges))
|
|
commit_list_insert(cc->item, &p_ent->reverse_edges);
|
|
}
|
|
}
|
|
}
|
|
|
|
bitmap_free(c_ent->commit_mask);
|
|
c_ent->commit_mask = NULL;
|
|
}
|
|
|
|
trace2_data_intmax("pack-bitmap-write", the_repository,
|
|
"num_selected_commits", writer->selected_nr);
|
|
trace2_data_intmax("pack-bitmap-write", the_repository,
|
|
"num_maximal_commits", num_maximal);
|
|
}
|
|
|
|
static void bitmap_builder_clear(struct bitmap_builder *bb)
|
|
{
|
|
clear_bb_data(&bb->data);
|
|
free(bb->commits);
|
|
bb->commits_nr = bb->commits_alloc = 0;
|
|
}
|
|
|
|
static void fill_bitmap_tree(struct bitmap *bitmap,
|
|
struct tree *tree)
|
|
{
|
|
uint32_t pos;
|
|
struct tree_desc desc;
|
|
struct name_entry entry;
|
|
|
|
/*
|
|
* If our bit is already set, then there is nothing to do. Both this
|
|
* tree and all of its children will be set.
|
|
*/
|
|
pos = find_object_pos(&tree->object.oid);
|
|
if (bitmap_get(bitmap, pos))
|
|
return;
|
|
bitmap_set(bitmap, pos);
|
|
|
|
if (parse_tree(tree) < 0)
|
|
die("unable to load tree object %s",
|
|
oid_to_hex(&tree->object.oid));
|
|
init_tree_desc(&desc, tree->buffer, tree->size);
|
|
|
|
while (tree_entry(&desc, &entry)) {
|
|
switch (object_type(entry.mode)) {
|
|
case OBJ_TREE:
|
|
fill_bitmap_tree(bitmap,
|
|
lookup_tree(the_repository, &entry.oid));
|
|
break;
|
|
case OBJ_BLOB:
|
|
bitmap_set(bitmap, find_object_pos(&entry.oid));
|
|
break;
|
|
default:
|
|
/* Gitlink, etc; not reachable */
|
|
break;
|
|
}
|
|
}
|
|
|
|
free_tree_buffer(tree);
|
|
}
|
|
|
|
static void fill_bitmap_commit(struct bb_commit *ent,
|
|
struct commit *commit,
|
|
struct prio_queue *queue)
|
|
{
|
|
if (!ent->bitmap)
|
|
ent->bitmap = bitmap_new();
|
|
|
|
bitmap_set(ent->bitmap, find_object_pos(&commit->object.oid));
|
|
prio_queue_put(queue, commit);
|
|
|
|
while (queue->nr) {
|
|
struct commit_list *p;
|
|
struct commit *c = prio_queue_get(queue);
|
|
|
|
bitmap_set(ent->bitmap, find_object_pos(&c->object.oid));
|
|
fill_bitmap_tree(ent->bitmap, get_commit_tree(c));
|
|
|
|
for (p = c->parents; p; p = p->next) {
|
|
int pos = find_object_pos(&p->item->object.oid);
|
|
if (!bitmap_get(ent->bitmap, pos)) {
|
|
bitmap_set(ent->bitmap, pos);
|
|
prio_queue_put(queue, p->item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void store_selected(struct bb_commit *ent, struct commit *commit)
|
|
{
|
|
struct bitmapped_commit *stored = &writer.selected[ent->idx];
|
|
khiter_t hash_pos;
|
|
int hash_ret;
|
|
|
|
/*
|
|
* the "reuse bitmaps" phase may have stored something here, but
|
|
* our new algorithm doesn't use it. Drop it.
|
|
*/
|
|
if (stored->bitmap)
|
|
ewah_free(stored->bitmap);
|
|
|
|
stored->bitmap = bitmap_to_ewah(ent->bitmap);
|
|
|
|
hash_pos = kh_put_oid_map(writer.bitmaps, commit->object.oid, &hash_ret);
|
|
if (hash_ret == 0)
|
|
die("Duplicate entry when writing index: %s",
|
|
oid_to_hex(&commit->object.oid));
|
|
kh_value(writer.bitmaps, hash_pos) = stored;
|
|
}
|
|
|
|
void bitmap_writer_build(struct packing_data *to_pack)
|
|
{
|
|
struct bitmap_builder bb;
|
|
size_t i;
|
|
int nr_stored = 0; /* for progress */
|
|
struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
|
|
|
|
writer.bitmaps = kh_init_oid_map();
|
|
writer.to_pack = to_pack;
|
|
|
|
if (writer.show_progress)
|
|
writer.progress = start_progress("Building bitmaps", writer.selected_nr);
|
|
trace2_region_enter("pack-bitmap-write", "building_bitmaps_total",
|
|
the_repository);
|
|
|
|
bitmap_builder_init(&bb, &writer);
|
|
for (i = bb.commits_nr; i > 0; i--) {
|
|
struct commit *commit = bb.commits[i-1];
|
|
struct bb_commit *ent = bb_data_at(&bb.data, commit);
|
|
struct commit *child;
|
|
int reused = 0;
|
|
|
|
fill_bitmap_commit(ent, commit, &queue);
|
|
|
|
if (ent->selected) {
|
|
store_selected(ent, commit);
|
|
nr_stored++;
|
|
display_progress(writer.progress, nr_stored);
|
|
}
|
|
|
|
while ((child = pop_commit(&ent->reverse_edges))) {
|
|
struct bb_commit *child_ent =
|
|
bb_data_at(&bb.data, child);
|
|
|
|
if (child_ent->bitmap)
|
|
bitmap_or(child_ent->bitmap, ent->bitmap);
|
|
else if (reused)
|
|
child_ent->bitmap = bitmap_dup(ent->bitmap);
|
|
else {
|
|
child_ent->bitmap = ent->bitmap;
|
|
reused = 1;
|
|
}
|
|
}
|
|
if (!reused)
|
|
bitmap_free(ent->bitmap);
|
|
ent->bitmap = NULL;
|
|
}
|
|
clear_prio_queue(&queue);
|
|
bitmap_builder_clear(&bb);
|
|
|
|
trace2_region_leave("pack-bitmap-write", "building_bitmaps_total",
|
|
the_repository);
|
|
|
|
stop_progress(&writer.progress);
|
|
|
|
compute_xor_offsets();
|
|
}
|
|
|
|
/**
|
|
* Select the commits that will be bitmapped
|
|
*/
|
|
static inline unsigned int next_commit_index(unsigned int idx)
|
|
{
|
|
static const unsigned int MIN_COMMITS = 100;
|
|
static const unsigned int MAX_COMMITS = 5000;
|
|
|
|
static const unsigned int MUST_REGION = 100;
|
|
static const unsigned int MIN_REGION = 20000;
|
|
|
|
unsigned int offset, next;
|
|
|
|
if (idx <= MUST_REGION)
|
|
return 0;
|
|
|
|
if (idx <= MIN_REGION) {
|
|
offset = idx - MUST_REGION;
|
|
return (offset < MIN_COMMITS) ? offset : MIN_COMMITS;
|
|
}
|
|
|
|
offset = idx - MIN_REGION;
|
|
next = (offset < MAX_COMMITS) ? offset : MAX_COMMITS;
|
|
|
|
return (next > MIN_COMMITS) ? next : MIN_COMMITS;
|
|
}
|
|
|
|
static int date_compare(const void *_a, const void *_b)
|
|
{
|
|
struct commit *a = *(struct commit **)_a;
|
|
struct commit *b = *(struct commit **)_b;
|
|
return (long)b->date - (long)a->date;
|
|
}
|
|
|
|
void bitmap_writer_reuse_bitmaps(struct packing_data *to_pack)
|
|
{
|
|
struct bitmap_index *bitmap_git;
|
|
if (!(bitmap_git = prepare_bitmap_git(to_pack->repo)))
|
|
return;
|
|
|
|
writer.reused = kh_init_oid_map();
|
|
rebuild_existing_bitmaps(bitmap_git, to_pack, writer.reused,
|
|
writer.show_progress);
|
|
/*
|
|
* NEEDSWORK: rebuild_existing_bitmaps() makes writer.reused reference
|
|
* some bitmaps in bitmap_git, so we can't free the latter.
|
|
*/
|
|
}
|
|
|
|
static struct ewah_bitmap *find_reused_bitmap(const struct object_id *oid)
|
|
{
|
|
khiter_t hash_pos;
|
|
|
|
if (!writer.reused)
|
|
return NULL;
|
|
|
|
hash_pos = kh_get_oid_map(writer.reused, *oid);
|
|
if (hash_pos >= kh_end(writer.reused))
|
|
return NULL;
|
|
|
|
return kh_value(writer.reused, hash_pos);
|
|
}
|
|
|
|
void bitmap_writer_select_commits(struct commit **indexed_commits,
|
|
unsigned int indexed_commits_nr,
|
|
int max_bitmaps)
|
|
{
|
|
unsigned int i = 0, j, next;
|
|
|
|
QSORT(indexed_commits, indexed_commits_nr, date_compare);
|
|
|
|
if (writer.show_progress)
|
|
writer.progress = start_progress("Selecting bitmap commits", 0);
|
|
|
|
if (indexed_commits_nr < 100) {
|
|
for (i = 0; i < indexed_commits_nr; ++i)
|
|
push_bitmapped_commit(indexed_commits[i], NULL);
|
|
return;
|
|
}
|
|
|
|
for (;;) {
|
|
struct ewah_bitmap *reused_bitmap = NULL;
|
|
struct commit *chosen = NULL;
|
|
|
|
next = next_commit_index(i);
|
|
|
|
if (i + next >= indexed_commits_nr)
|
|
break;
|
|
|
|
if (max_bitmaps > 0 && writer.selected_nr >= max_bitmaps) {
|
|
writer.selected_nr = max_bitmaps;
|
|
break;
|
|
}
|
|
|
|
if (next == 0) {
|
|
chosen = indexed_commits[i];
|
|
reused_bitmap = find_reused_bitmap(&chosen->object.oid);
|
|
} else {
|
|
chosen = indexed_commits[i + next];
|
|
|
|
for (j = 0; j <= next; ++j) {
|
|
struct commit *cm = indexed_commits[i + j];
|
|
|
|
reused_bitmap = find_reused_bitmap(&cm->object.oid);
|
|
if (reused_bitmap || (cm->object.flags & NEEDS_BITMAP) != 0) {
|
|
chosen = cm;
|
|
break;
|
|
}
|
|
|
|
if (cm->parents && cm->parents->next)
|
|
chosen = cm;
|
|
}
|
|
}
|
|
|
|
push_bitmapped_commit(chosen, reused_bitmap);
|
|
|
|
i += next + 1;
|
|
display_progress(writer.progress, i);
|
|
}
|
|
|
|
stop_progress(&writer.progress);
|
|
}
|
|
|
|
|
|
static int hashwrite_ewah_helper(void *f, const void *buf, size_t len)
|
|
{
|
|
/* hashwrite will die on error */
|
|
hashwrite(f, buf, len);
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* Write the bitmap index to disk
|
|
*/
|
|
static inline void dump_bitmap(struct hashfile *f, struct ewah_bitmap *bitmap)
|
|
{
|
|
if (ewah_serialize_to(bitmap, hashwrite_ewah_helper, f) < 0)
|
|
die("Failed to write bitmap index");
|
|
}
|
|
|
|
static const unsigned char *sha1_access(size_t pos, void *table)
|
|
{
|
|
struct pack_idx_entry **index = table;
|
|
return index[pos]->oid.hash;
|
|
}
|
|
|
|
static void write_selected_commits_v1(struct hashfile *f,
|
|
struct pack_idx_entry **index,
|
|
uint32_t index_nr)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < writer.selected_nr; ++i) {
|
|
struct bitmapped_commit *stored = &writer.selected[i];
|
|
|
|
int commit_pos =
|
|
sha1_pos(stored->commit->object.oid.hash, index, index_nr, sha1_access);
|
|
|
|
if (commit_pos < 0)
|
|
BUG("trying to write commit not in index");
|
|
|
|
hashwrite_be32(f, commit_pos);
|
|
hashwrite_u8(f, stored->xor_offset);
|
|
hashwrite_u8(f, stored->flags);
|
|
|
|
dump_bitmap(f, stored->write_as);
|
|
}
|
|
}
|
|
|
|
static void write_hash_cache(struct hashfile *f,
|
|
struct pack_idx_entry **index,
|
|
uint32_t index_nr)
|
|
{
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < index_nr; ++i) {
|
|
struct object_entry *entry = (struct object_entry *)index[i];
|
|
hashwrite_be32(f, entry->hash);
|
|
}
|
|
}
|
|
|
|
void bitmap_writer_set_checksum(unsigned char *sha1)
|
|
{
|
|
hashcpy(writer.pack_checksum, sha1);
|
|
}
|
|
|
|
void bitmap_writer_finish(struct pack_idx_entry **index,
|
|
uint32_t index_nr,
|
|
const char *filename,
|
|
uint16_t options)
|
|
{
|
|
static uint16_t default_version = 1;
|
|
static uint16_t flags = BITMAP_OPT_FULL_DAG;
|
|
struct strbuf tmp_file = STRBUF_INIT;
|
|
struct hashfile *f;
|
|
|
|
struct bitmap_disk_header header;
|
|
|
|
int fd = odb_mkstemp(&tmp_file, "pack/tmp_bitmap_XXXXXX");
|
|
|
|
f = hashfd(fd, tmp_file.buf);
|
|
|
|
memcpy(header.magic, BITMAP_IDX_SIGNATURE, sizeof(BITMAP_IDX_SIGNATURE));
|
|
header.version = htons(default_version);
|
|
header.options = htons(flags | options);
|
|
header.entry_count = htonl(writer.selected_nr);
|
|
hashcpy(header.checksum, writer.pack_checksum);
|
|
|
|
hashwrite(f, &header, sizeof(header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz);
|
|
dump_bitmap(f, writer.commits);
|
|
dump_bitmap(f, writer.trees);
|
|
dump_bitmap(f, writer.blobs);
|
|
dump_bitmap(f, writer.tags);
|
|
write_selected_commits_v1(f, index, index_nr);
|
|
|
|
if (options & BITMAP_OPT_HASH_CACHE)
|
|
write_hash_cache(f, index, index_nr);
|
|
|
|
finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
|
|
|
|
if (adjust_shared_perm(tmp_file.buf))
|
|
die_errno("unable to make temporary bitmap file readable");
|
|
|
|
if (rename(tmp_file.buf, filename))
|
|
die_errno("unable to rename temporary bitmap file to '%s'", filename);
|
|
|
|
strbuf_release(&tmp_file);
|
|
}
|