089f751360
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>
552 lines
18 KiB
Bash
Executable File
552 lines
18 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
test_description='exercise basic bitmap functionality'
|
|
. ./test-lib.sh
|
|
|
|
objpath () {
|
|
echo ".git/objects/$(echo "$1" | sed -e 's|\(..\)|\1/|')"
|
|
}
|
|
|
|
# show objects present in pack ($1 should be associated *.idx)
|
|
list_packed_objects () {
|
|
git show-index <"$1" >object-list &&
|
|
cut -d' ' -f2 object-list
|
|
}
|
|
|
|
# has_any pattern-file content-file
|
|
# tests whether content-file has any entry from pattern-file with entries being
|
|
# whole lines.
|
|
has_any () {
|
|
grep -Ff "$1" "$2"
|
|
}
|
|
|
|
# To ensure the logic for "maximal commits" is exercised, make
|
|
# the repository a bit more complicated.
|
|
#
|
|
# other master
|
|
# * *
|
|
# (99 commits) (99 commits)
|
|
# * *
|
|
# |\ /|
|
|
# | * octo-other octo-master * |
|
|
# |/|\_________ ____________/|\|
|
|
# | \ \/ __________/ |
|
|
# | | ________/\ / |
|
|
# * |/ * merge-right *
|
|
# | _|__________/ \____________ |
|
|
# |/ | \|
|
|
# (l1) * * merge-left * (r1)
|
|
# | / \________________________ |
|
|
# |/ \|
|
|
# (l2) * * (r2)
|
|
# \___________________________ |
|
|
# \|
|
|
# * (base)
|
|
#
|
|
# The important part for the maximal commit algorithm is how
|
|
# the bitmasks are extended. Assuming starting bit positions
|
|
# for master (bit 0) and other (bit 1), and some flexibility
|
|
# in the order that merge bases are visited, the bitmasks at
|
|
# the end should be:
|
|
#
|
|
# master: 1 (maximal, selected)
|
|
# other: 01 (maximal, selected)
|
|
# octo-master: 1
|
|
# octo-other: 01
|
|
# merge-right: 111 (maximal)
|
|
# (l1): 111
|
|
# (r1): 111
|
|
# merge-left: 1101 (maximal)
|
|
# (l2): 11111 (maximal)
|
|
# (r2): 111101 (maximal)
|
|
# (base): 1111111 (maximal)
|
|
|
|
test_expect_success 'setup repo with moderate-sized history' '
|
|
test_commit_bulk --id=file 10 &&
|
|
git branch -M second &&
|
|
git checkout -b other HEAD~5 &&
|
|
test_commit_bulk --id=side 10 &&
|
|
|
|
# add complicated history setup, including merges and
|
|
# ambiguous merge-bases
|
|
|
|
git checkout -b merge-left other~2 &&
|
|
git merge second~2 -m "merge-left" &&
|
|
|
|
git checkout -b merge-right second~1 &&
|
|
git merge other~1 -m "merge-right" &&
|
|
|
|
git checkout -b octo-second second &&
|
|
git merge merge-left merge-right -m "octopus-second" &&
|
|
|
|
git checkout -b octo-other other &&
|
|
git merge merge-left merge-right -m "octopus-other" &&
|
|
|
|
git checkout other &&
|
|
git merge octo-other -m "pull octopus" &&
|
|
|
|
git checkout second &&
|
|
git merge octo-second -m "pull octopus" &&
|
|
|
|
# Remove these branches so they are not selected
|
|
# as bitmap tips
|
|
git branch -D merge-left &&
|
|
git branch -D merge-right &&
|
|
git branch -D octo-other &&
|
|
git branch -D octo-second &&
|
|
|
|
# add padding to make these merges less interesting
|
|
# and avoid having them selected for bitmaps
|
|
test_commit_bulk --id=file 100 &&
|
|
git checkout other &&
|
|
test_commit_bulk --id=side 100 &&
|
|
git checkout second &&
|
|
|
|
bitmaptip=$(git rev-parse second) &&
|
|
blob=$(echo tagged-blob | git hash-object -w --stdin) &&
|
|
git tag tagged-blob $blob &&
|
|
git config repack.writebitmaps true
|
|
'
|
|
|
|
test_expect_success 'full repack creates bitmaps' '
|
|
GIT_TRACE2_EVENT_NESTING=4 GIT_TRACE2_EVENT="$(pwd)/trace" \
|
|
git repack -ad &&
|
|
ls .git/objects/pack/ | grep bitmap >output &&
|
|
test_line_count = 1 output &&
|
|
grep "\"key\":\"num_selected_commits\",\"value\":\"106\"" trace &&
|
|
grep "\"key\":\"num_maximal_commits\",\"value\":\"111\"" trace
|
|
'
|
|
|
|
test_expect_success 'rev-list --test-bitmap verifies bitmaps' '
|
|
git rev-list --test-bitmap HEAD
|
|
'
|
|
|
|
rev_list_tests_head () {
|
|
test_expect_success "counting commits via bitmap ($state, $branch)" '
|
|
git rev-list --count $branch >expect &&
|
|
git rev-list --use-bitmap-index --count $branch >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success "counting partial commits via bitmap ($state, $branch)" '
|
|
git rev-list --count $branch~5..$branch >expect &&
|
|
git rev-list --use-bitmap-index --count $branch~5..$branch >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success "counting commits with limit ($state, $branch)" '
|
|
git rev-list --count -n 1 $branch >expect &&
|
|
git rev-list --use-bitmap-index --count -n 1 $branch >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success "counting non-linear history ($state, $branch)" '
|
|
git rev-list --count other...second >expect &&
|
|
git rev-list --use-bitmap-index --count other...second >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success "counting commits with limiting ($state, $branch)" '
|
|
git rev-list --count $branch -- 1.t >expect &&
|
|
git rev-list --use-bitmap-index --count $branch -- 1.t >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success "counting objects via bitmap ($state, $branch)" '
|
|
git rev-list --count --objects $branch >expect &&
|
|
git rev-list --use-bitmap-index --count --objects $branch >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success "enumerate commits ($state, $branch)" '
|
|
git rev-list --use-bitmap-index $branch >actual &&
|
|
git rev-list $branch >expect &&
|
|
test_bitmap_traversal --no-confirm-bitmaps expect actual
|
|
'
|
|
|
|
test_expect_success "enumerate --objects ($state, $branch)" '
|
|
git rev-list --objects --use-bitmap-index $branch >actual &&
|
|
git rev-list --objects $branch >expect &&
|
|
test_bitmap_traversal expect actual
|
|
'
|
|
|
|
test_expect_success "bitmap --objects handles non-commit objects ($state, $branch)" '
|
|
git rev-list --objects --use-bitmap-index $branch tagged-blob >actual &&
|
|
grep $blob actual
|
|
'
|
|
}
|
|
|
|
rev_list_tests () {
|
|
state=$1
|
|
|
|
for branch in "second" "other"
|
|
do
|
|
rev_list_tests_head
|
|
done
|
|
}
|
|
|
|
rev_list_tests 'full bitmap'
|
|
|
|
test_expect_success 'clone from bitmapped repository' '
|
|
git clone --no-local --bare . clone.git &&
|
|
git rev-parse HEAD >expect &&
|
|
git --git-dir=clone.git rev-parse HEAD >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'partial clone from bitmapped repository' '
|
|
test_config uploadpack.allowfilter true &&
|
|
git clone --no-local --bare --filter=blob:none . partial-clone.git &&
|
|
(
|
|
cd partial-clone.git &&
|
|
pack=$(echo objects/pack/*.pack) &&
|
|
git verify-pack -v "$pack" >have &&
|
|
awk "/blob/ { print \$1 }" <have >blobs &&
|
|
# we expect this single blob because of the direct ref
|
|
git rev-parse refs/tags/tagged-blob >expect &&
|
|
test_cmp expect blobs
|
|
)
|
|
'
|
|
|
|
test_expect_success 'setup further non-bitmapped commits' '
|
|
test_commit_bulk --id=further 10
|
|
'
|
|
|
|
rev_list_tests 'partial bitmap'
|
|
|
|
test_expect_success 'fetch (partial bitmap)' '
|
|
git --git-dir=clone.git fetch origin second:second &&
|
|
git rev-parse HEAD >expect &&
|
|
git --git-dir=clone.git rev-parse HEAD >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'incremental repack fails when bitmaps are requested' '
|
|
test_commit more-1 &&
|
|
test_must_fail git repack -d 2>err &&
|
|
test_i18ngrep "Incremental repacks are incompatible with bitmap" err
|
|
'
|
|
|
|
test_expect_success 'incremental repack can disable bitmaps' '
|
|
test_commit more-2 &&
|
|
git repack -d --no-write-bitmap-index
|
|
'
|
|
|
|
test_expect_success 'pack-objects respects --local (non-local loose)' '
|
|
git init --bare alt.git &&
|
|
echo $(pwd)/alt.git/objects >.git/objects/info/alternates &&
|
|
echo content1 >file1 &&
|
|
# non-local loose object which is not present in bitmapped pack
|
|
altblob=$(GIT_DIR=alt.git git hash-object -w file1) &&
|
|
# non-local loose object which is also present in bitmapped pack
|
|
git cat-file blob $blob | GIT_DIR=alt.git git hash-object -w --stdin &&
|
|
git add file1 &&
|
|
test_tick &&
|
|
git commit -m commit_file1 &&
|
|
echo HEAD | git pack-objects --local --stdout --revs >1.pack &&
|
|
git index-pack 1.pack &&
|
|
list_packed_objects 1.idx >1.objects &&
|
|
printf "%s\n" "$altblob" "$blob" >nonlocal-loose &&
|
|
! has_any nonlocal-loose 1.objects
|
|
'
|
|
|
|
test_expect_success 'pack-objects respects --honor-pack-keep (local non-bitmapped pack)' '
|
|
echo content2 >file2 &&
|
|
blob2=$(git hash-object -w file2) &&
|
|
git add file2 &&
|
|
test_tick &&
|
|
git commit -m commit_file2 &&
|
|
printf "%s\n" "$blob2" "$bitmaptip" >keepobjects &&
|
|
pack2=$(git pack-objects pack2 <keepobjects) &&
|
|
mv pack2-$pack2.* .git/objects/pack/ &&
|
|
>.git/objects/pack/pack2-$pack2.keep &&
|
|
rm $(objpath $blob2) &&
|
|
echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >2a.pack &&
|
|
git index-pack 2a.pack &&
|
|
list_packed_objects 2a.idx >2a.objects &&
|
|
! has_any keepobjects 2a.objects
|
|
'
|
|
|
|
test_expect_success 'pack-objects respects --local (non-local pack)' '
|
|
mv .git/objects/pack/pack2-$pack2.* alt.git/objects/pack/ &&
|
|
echo HEAD | git pack-objects --local --stdout --revs >2b.pack &&
|
|
git index-pack 2b.pack &&
|
|
list_packed_objects 2b.idx >2b.objects &&
|
|
! has_any keepobjects 2b.objects
|
|
'
|
|
|
|
test_expect_success 'pack-objects respects --honor-pack-keep (local bitmapped pack)' '
|
|
ls .git/objects/pack/ | grep bitmap >output &&
|
|
test_line_count = 1 output &&
|
|
packbitmap=$(basename $(cat output) .bitmap) &&
|
|
list_packed_objects .git/objects/pack/$packbitmap.idx >packbitmap.objects &&
|
|
test_when_finished "rm -f .git/objects/pack/$packbitmap.keep" &&
|
|
>.git/objects/pack/$packbitmap.keep &&
|
|
echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >3a.pack &&
|
|
git index-pack 3a.pack &&
|
|
list_packed_objects 3a.idx >3a.objects &&
|
|
! has_any packbitmap.objects 3a.objects
|
|
'
|
|
|
|
test_expect_success 'pack-objects respects --local (non-local bitmapped pack)' '
|
|
mv .git/objects/pack/$packbitmap.* alt.git/objects/pack/ &&
|
|
rm -f .git/objects/pack/multi-pack-index &&
|
|
test_when_finished "mv alt.git/objects/pack/$packbitmap.* .git/objects/pack/" &&
|
|
echo HEAD | git pack-objects --local --stdout --revs >3b.pack &&
|
|
git index-pack 3b.pack &&
|
|
list_packed_objects 3b.idx >3b.objects &&
|
|
! has_any packbitmap.objects 3b.objects
|
|
'
|
|
|
|
test_expect_success 'pack-objects to file can use bitmap' '
|
|
# make sure we still have 1 bitmap index from previous tests
|
|
ls .git/objects/pack/ | grep bitmap >output &&
|
|
test_line_count = 1 output &&
|
|
# verify equivalent packs are generated with/without using bitmap index
|
|
packasha1=$(git pack-objects --no-use-bitmap-index --all packa </dev/null) &&
|
|
packbsha1=$(git pack-objects --use-bitmap-index --all packb </dev/null) &&
|
|
list_packed_objects packa-$packasha1.idx >packa.objects &&
|
|
list_packed_objects packb-$packbsha1.idx >packb.objects &&
|
|
test_cmp packa.objects packb.objects
|
|
'
|
|
|
|
test_expect_success 'full repack, reusing previous bitmaps' '
|
|
git repack -ad &&
|
|
ls .git/objects/pack/ | grep bitmap >output &&
|
|
test_line_count = 1 output
|
|
'
|
|
|
|
test_expect_success 'fetch (full bitmap)' '
|
|
git --git-dir=clone.git fetch origin second:second &&
|
|
git rev-parse HEAD >expect &&
|
|
git --git-dir=clone.git rev-parse HEAD >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'create objects for missing-HAVE tests' '
|
|
blob=$(echo "missing have" | git hash-object -w --stdin) &&
|
|
tree=$(printf "100644 blob $blob\tfile\n" | git mktree) &&
|
|
parent=$(echo parent | git commit-tree $tree) &&
|
|
commit=$(echo commit | git commit-tree $tree -p $parent) &&
|
|
cat >revs <<-EOF
|
|
HEAD
|
|
^HEAD^
|
|
^$commit
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'pack-objects respects --incremental' '
|
|
cat >revs2 <<-EOF &&
|
|
HEAD
|
|
$commit
|
|
EOF
|
|
git pack-objects --incremental --stdout --revs <revs2 >4.pack &&
|
|
git index-pack 4.pack &&
|
|
list_packed_objects 4.idx >4.objects &&
|
|
test_line_count = 4 4.objects &&
|
|
git rev-list --objects $commit >revlist &&
|
|
cut -d" " -f1 revlist |sort >objects &&
|
|
test_cmp 4.objects objects
|
|
'
|
|
|
|
test_expect_success 'pack with missing blob' '
|
|
rm $(objpath $blob) &&
|
|
git pack-objects --stdout --revs <revs >/dev/null
|
|
'
|
|
|
|
test_expect_success 'pack with missing tree' '
|
|
rm $(objpath $tree) &&
|
|
git pack-objects --stdout --revs <revs >/dev/null
|
|
'
|
|
|
|
test_expect_success 'pack with missing parent' '
|
|
rm $(objpath $parent) &&
|
|
git pack-objects --stdout --revs <revs >/dev/null
|
|
'
|
|
|
|
test_expect_success JGIT 'we can read jgit bitmaps' '
|
|
git clone --bare . compat-jgit.git &&
|
|
(
|
|
cd compat-jgit.git &&
|
|
rm -f objects/pack/*.bitmap &&
|
|
jgit gc &&
|
|
git rev-list --test-bitmap HEAD
|
|
)
|
|
'
|
|
|
|
test_expect_success JGIT 'jgit can read our bitmaps' '
|
|
git clone --bare . compat-us.git &&
|
|
(
|
|
cd compat-us.git &&
|
|
git repack -adb &&
|
|
# jgit gc will barf if it does not like our bitmaps
|
|
jgit gc
|
|
)
|
|
'
|
|
|
|
test_expect_success 'splitting packs does not generate bogus bitmaps' '
|
|
test-tool genrandom foo $((1024 * 1024)) >rand &&
|
|
git add rand &&
|
|
git commit -m "commit with big file" &&
|
|
git -c pack.packSizeLimit=500k repack -adb &&
|
|
git init --bare no-bitmaps.git &&
|
|
git -C no-bitmaps.git fetch .. HEAD
|
|
'
|
|
|
|
test_expect_success 'set up reusable pack' '
|
|
rm -f .git/objects/pack/*.keep &&
|
|
git repack -adb &&
|
|
reusable_pack () {
|
|
git for-each-ref --format="%(objectname)" |
|
|
git pack-objects --delta-base-offset --revs --stdout "$@"
|
|
}
|
|
'
|
|
|
|
test_expect_success 'pack reuse respects --honor-pack-keep' '
|
|
test_when_finished "rm -f .git/objects/pack/*.keep" &&
|
|
for i in .git/objects/pack/*.pack
|
|
do
|
|
>${i%.pack}.keep
|
|
done &&
|
|
reusable_pack --honor-pack-keep >empty.pack &&
|
|
git index-pack empty.pack &&
|
|
git show-index <empty.idx >actual &&
|
|
test_must_be_empty actual
|
|
'
|
|
|
|
test_expect_success 'pack reuse respects --local' '
|
|
mv .git/objects/pack/* alt.git/objects/pack/ &&
|
|
test_when_finished "mv alt.git/objects/pack/* .git/objects/pack/" &&
|
|
reusable_pack --local >empty.pack &&
|
|
git index-pack empty.pack &&
|
|
git show-index <empty.idx >actual &&
|
|
test_must_be_empty actual
|
|
'
|
|
|
|
test_expect_success 'pack reuse respects --incremental' '
|
|
reusable_pack --incremental >empty.pack &&
|
|
git index-pack empty.pack &&
|
|
git show-index <empty.idx >actual &&
|
|
test_must_be_empty actual
|
|
'
|
|
|
|
test_expect_success 'truncated bitmap fails gracefully (ewah)' '
|
|
test_config pack.writebitmaphashcache false &&
|
|
git repack -ad &&
|
|
git rev-list --use-bitmap-index --count --all >expect &&
|
|
bitmap=$(ls .git/objects/pack/*.bitmap) &&
|
|
test_when_finished "rm -f $bitmap" &&
|
|
test_copy_bytes 256 <$bitmap >$bitmap.tmp &&
|
|
mv -f $bitmap.tmp $bitmap &&
|
|
git rev-list --use-bitmap-index --count --all >actual 2>stderr &&
|
|
test_cmp expect actual &&
|
|
test_i18ngrep corrupt.ewah.bitmap stderr
|
|
'
|
|
|
|
test_expect_success 'truncated bitmap fails gracefully (cache)' '
|
|
git repack -ad &&
|
|
git rev-list --use-bitmap-index --count --all >expect &&
|
|
bitmap=$(ls .git/objects/pack/*.bitmap) &&
|
|
test_when_finished "rm -f $bitmap" &&
|
|
test_copy_bytes 512 <$bitmap >$bitmap.tmp &&
|
|
mv -f $bitmap.tmp $bitmap &&
|
|
git rev-list --use-bitmap-index --count --all >actual 2>stderr &&
|
|
test_cmp expect actual &&
|
|
test_i18ngrep corrupted.bitmap.index stderr
|
|
'
|
|
|
|
# have_delta <obj> <expected_base>
|
|
#
|
|
# Note that because this relies on cat-file, it might find _any_ copy of an
|
|
# object in the repository. The caller is responsible for making sure
|
|
# there's only one (e.g., via "repack -ad", or having just fetched a copy).
|
|
have_delta () {
|
|
echo $2 >expect &&
|
|
echo $1 | git cat-file --batch-check="%(deltabase)" >actual &&
|
|
test_cmp expect actual
|
|
}
|
|
|
|
# Create a state of history with these properties:
|
|
#
|
|
# - refs that allow a client to fetch some new history, while sharing some old
|
|
# history with the server; we use branches delta-reuse-old and
|
|
# delta-reuse-new here
|
|
#
|
|
# - the new history contains an object that is stored on the server as a delta
|
|
# against a base that is in the old history
|
|
#
|
|
# - the base object is not immediately reachable from the tip of the old
|
|
# history; finding it would involve digging down through history we know the
|
|
# other side has
|
|
#
|
|
# This should result in a state where fetching from old->new would not
|
|
# traditionally reuse the on-disk delta (because we'd have to dig to realize
|
|
# that the client has it), but we will do so if bitmaps can tell us cheaply
|
|
# that the other side has it.
|
|
test_expect_success 'set up thin delta-reuse parent' '
|
|
# This first commit contains the buried base object.
|
|
test-tool genrandom delta 16384 >file &&
|
|
git add file &&
|
|
git commit -m "delta base" &&
|
|
base=$(git rev-parse --verify HEAD:file) &&
|
|
|
|
# These intermediate commits bury the base back in history.
|
|
# This becomes the "old" state.
|
|
for i in 1 2 3 4 5
|
|
do
|
|
echo $i >file &&
|
|
git commit -am "intermediate $i" || return 1
|
|
done &&
|
|
git branch delta-reuse-old &&
|
|
|
|
# And now our new history has a delta against the buried base. Note
|
|
# that this must be smaller than the original file, since pack-objects
|
|
# prefers to create deltas from smaller objects to larger.
|
|
test-tool genrandom delta 16300 >file &&
|
|
git commit -am "delta result" &&
|
|
delta=$(git rev-parse --verify HEAD:file) &&
|
|
git branch delta-reuse-new &&
|
|
|
|
# Repack with bitmaps and double check that we have the expected delta
|
|
# relationship.
|
|
git repack -adb &&
|
|
have_delta $delta $base
|
|
'
|
|
|
|
# Now we can sanity-check the non-bitmap behavior (that the server is not able
|
|
# to reuse the delta). This isn't strictly something we care about, so this
|
|
# test could be scrapped in the future. But it makes sure that the next test is
|
|
# actually triggering the feature we want.
|
|
#
|
|
# Note that our tools for working with on-the-wire "thin" packs are limited. So
|
|
# we actually perform the fetch, retain the resulting pack, and inspect the
|
|
# result.
|
|
test_expect_success 'fetch without bitmaps ignores delta against old base' '
|
|
test_config pack.usebitmaps false &&
|
|
test_when_finished "rm -rf client.git" &&
|
|
git init --bare client.git &&
|
|
(
|
|
cd client.git &&
|
|
git config transfer.unpackLimit 1 &&
|
|
git fetch .. delta-reuse-old:delta-reuse-old &&
|
|
git fetch .. delta-reuse-new:delta-reuse-new &&
|
|
have_delta $delta $ZERO_OID
|
|
)
|
|
'
|
|
|
|
# And do the same for the bitmap case, where we do expect to find the delta.
|
|
test_expect_success 'fetch with bitmaps can reuse old base' '
|
|
test_config pack.usebitmaps true &&
|
|
test_when_finished "rm -rf client.git" &&
|
|
git init --bare client.git &&
|
|
(
|
|
cd client.git &&
|
|
git config transfer.unpackLimit 1 &&
|
|
git fetch .. delta-reuse-old:delta-reuse-old &&
|
|
git fetch .. delta-reuse-new:delta-reuse-new &&
|
|
have_delta $delta $base
|
|
)
|
|
'
|
|
|
|
test_done
|