1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
|
/*
* Copyright (C) 2015, Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal.storage.pack;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.internal.storage.file.GcTestCase;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.junit.Test;
public class GcCommitSelectionTest extends GcTestCase {
@Test
public void testBitmapSpansNoMerges() throws Exception {
testBitmapSpansNoMerges(false);
}
@Test
public void testBitmapSpansNoMergesWithTags() throws Exception {
testBitmapSpansNoMerges(true);
}
private void testBitmapSpansNoMerges(boolean withTags) throws Exception {
/*
* Commit counts -> expected bitmap counts for history without merges.
* The top 100 contiguous commits should always have bitmaps, and the
* "recent" bitmaps beyond that are spaced out every 100-200 commits.
* (Starting at 100, the next 100 commits are searched for a merge
* commit. Since one is not found, the spacing between commits is 200.
*/
int[][] bitmapCounts = { //
{ 1, 1 }, { 50, 50 }, { 99, 99 }, { 100, 100 }, { 101, 100 },
{ 200, 100 }, { 201, 100 }, { 299, 100 }, { 300, 101 },
{ 301, 101 }, { 401, 101 }, { 499, 101 }, { 500, 102 }, };
int currentCommits = 0;
BranchBuilder bb = tr.branch("refs/heads/main");
for (int[] counts : bitmapCounts) {
int nextCommitCount = counts[0];
int expectedBitmapCount = counts[1];
assertTrue(nextCommitCount > currentCommits); // programming error
for (int i = currentCommits; i < nextCommitCount; i++) {
String str = "A" + i;
RevCommit rc = bb.commit().message(str).add(str, str).create();
if (withTags) {
tr.lightweightTag(str, rc);
}
}
currentCommits = nextCommitCount;
gc.setPackExpireAgeMillis(0); // immediately delete old packs
gc.setExpireAgeMillis(0);
gc.gc().get();
assertEquals(currentCommits * 3, // commit/tree/object
gc.getStatistics().numberOfPackedObjects);
assertEquals(currentCommits + " commits: ", expectedBitmapCount,
gc.getStatistics().numberOfBitmaps);
}
}
@Test
public void testBitmapDoesNotIncludeAnnotatedTags() throws Exception {
/*
* Make sure that the bitmap generated for the following commit
* graph does not include commit2 because it is not reachable by any
* heads, despite being reachable from tag1 through the annotated-tag1.
*
* refs/heads/main
* ^
* |
* commit1 <-- commit2 <- annotated-tag1 <- tag1
* ^
* |
* commit0
*/
String mainBranch = "refs/heads/main";
BranchBuilder bb = tr.branch(mainBranch);
String commitMsg = "commit msg";
String fileBody = "file body";
String tagName = "tag1";
bb.commit().message(commitMsg + " 1").add("file1", fileBody).create();
RevCommit commit1 = bb.commit().message(commitMsg + " 2").add("file2", fileBody).create();
RevCommit commit2 = bb.commit().message(commitMsg + " 3").add("file3", fileBody).create();
tr.lightweightTag(tagName, tr.tag(tagName, commit2));
tr.branch(mainBranch).update(commit1);
gc.setExpireAgeMillis(0);
gc.gc().get();
// Create only 2 bitmaps, for commit0 and commit1, excluding commit2
assertEquals(2, gc.getStatistics().numberOfBitmaps);
}
@Test
public void testBitmapSpansWithMerges() throws Exception {
/*
* Commits that are merged. Since 55 is in the oldest history it is
* never considered. Searching goes from oldest to newest so 115 is the
* first merge commit found. After that the range 116-216 is ignored so
* 175 is never considered.
*/
List<Integer> merges = Arrays.asList(Integer.valueOf(55),
Integer.valueOf(115), Integer.valueOf(175),
Integer.valueOf(235));
/*
* Commit counts -> expected bitmap counts for history with merges. The
* top 100 contiguous commits should always have bitmaps, and the
* "recent" bitmaps beyond that are spaced out every 100-200 commits.
* Merges in the < 100 range have no effect and merges in the > 100
* range will only be considered for commit counts > 200.
*/
int[][] bitmapCounts = { //
{ 1, 1 }, { 55, 55 }, { 56, 57 }, // +1 bitmap from branch A55
{ 99, 100 }, // still +1 branch @55
{ 100, 100 }, // 101 commits, only 100 newest
{ 116, 100 }, // @55 still in 100 newest bitmaps
{ 176, 101 }, // @55 branch tip is not in 100 newest
{ 213, 101 }, // 216 commits, @115&@175 in 100 newest
{ 214, 102 }, // @55 branch tip, merge @115, @177 in newest
{ 236, 102 }, // all 4 merge points in history
{ 273, 102 }, // 277 commits, @175&@235 in newest
{ 274, 103 }, // @55, @115, merge @175, @235 in newest
{ 334, 103 }, // @55,@115,@175, @235 in newest
{ 335, 104 }, // @55,@115,@175, merge @235
{ 435, 104 }, // @55,@115,@175,@235 tips
{ 436, 104 }, // force @236
};
int currentCommits = 0;
BranchBuilder bb = tr.branch("refs/heads/main");
for (int[] counts : bitmapCounts) {
int nextCommitCount = counts[0];
int expectedBitmapCount = counts[1];
assertTrue(nextCommitCount > currentCommits); // programming error
for (int i = currentCommits; i < nextCommitCount; i++) {
String str = "A" + i;
if (!merges.contains(Integer.valueOf(i))) {
bb.commit().message(str).add(str, str).create();
} else {
BranchBuilder bbN = tr.branch("refs/heads/A" + i);
bb.commit().message(str).add(str, str)
.parent(bbN.commit().create()).create();
}
}
currentCommits = nextCommitCount;
gc.setPackExpireAgeMillis(0); // immediately delete old packs
gc.setExpireAgeMillis(0);
gc.gc().get();
assertEquals(currentCommits + " commits: ", expectedBitmapCount,
gc.getStatistics().numberOfBitmaps);
}
}
@Test
public void testBitmapsForExcessiveBranches() throws Exception {
int oneDayInSeconds = 60 * 60 * 24;
// All of branch A is committed on day1
BranchBuilder bbA = tr.branch("refs/heads/A");
for (int i = 0; i < 1001; i++) {
String msg = "A" + i;
bbA.commit().message(msg).add(msg, msg).create();
}
// All of in branch B is committed on day91
tr.tick(oneDayInSeconds * 90);
BranchBuilder bbB = tr.branch("refs/heads/B");
for (int i = 0; i < 1001; i++) {
String msg = "B" + i;
bbB.commit().message(msg).add(msg, msg).create();
}
// Create 100 other branches with a single commit
for (int i = 0; i < 100; i++) {
BranchBuilder bb = tr.branch("refs/heads/N" + i);
String msg = "singlecommit" + i;
bb.commit().message(msg).add(msg, msg).create();
}
// now is day92
tr.tick(oneDayInSeconds);
// Since there are no merges, commits in recent history are selected
// every 200 commits.
final int commitsForSparseBranch = 1 + (1001 / 200);
final int commitsForFullBranch = 100 + (901 / 200);
final int commitsForShallowBranches = 100;
// Excessive branch history pruning, one old branch.
gc.setPackExpireAgeMillis(0); // immediately delete old packs
gc.setExpireAgeMillis(0);
gc.gc().get();
assertEquals(
commitsForSparseBranch + commitsForFullBranch
+ commitsForShallowBranches,
gc.getStatistics().numberOfBitmaps);
}
@Test
public void testBitmapsForExcludedBranches() throws Exception {
createNewCommitOnNewBranch("main");
createNewCommitOnNewBranch("other");
PackConfig packConfig = new PackConfig();
packConfig.setBitmapExcludedRefsPrefixes(new String[] { "refs/heads/other" });
gc.setPackConfig(packConfig);
gc.gc().get();
assertEquals(1,
gc.getStatistics().numberOfBitmaps);
}
private void createNewCommitOnNewBranch(String branchName) throws Exception {
BranchBuilder bb = tr.branch("refs/heads/" + branchName);
String msg = "New branch " + branchName;
bb.commit().message(msg).add("some-filename.txt", msg).create();
}
@Test
public void testSelectionOrderingWithChains() throws Exception {
/*-
* Create a history like this, where 'N' is the number of seconds from
* the first commit in the branch:
*
* ---o---o---o commits b3,b5,b7
* / \
* o--o--o---o---o---o--o commits m0,m1,m2,m4,m6,m8,m9
*/
BranchBuilder bb = tr.branch("refs/heads/main");
RevCommit m0 = addCommit(bb, "m0");
RevCommit m1 = addCommit(bb, "m1", m0);
RevCommit m2 = addCommit(bb, "m2", m1);
RevCommit b3 = addCommit(bb, "b3", m1);
RevCommit m4 = addCommit(bb, "m4", m2);
RevCommit b5 = addCommit(bb, "m5", b3);
RevCommit m6 = addCommit(bb, "m6", m4);
RevCommit b7 = addCommit(bb, "m7", b5);
RevCommit m8 = addCommit(bb, "m8", m6, b7);
RevCommit m9 = addCommit(bb, "m9", m8);
List<RevCommit> commits = Arrays.asList(m0, m1, m2, b3, m4, b5, m6, b7,
m8, m9);
PackWriterBitmapPreparer preparer = newPreparer(
Collections.singleton(m9), commits, new PackConfig());
List<BitmapCommit> selection = new ArrayList<>(
preparer.selectCommits(commits.size(), PackWriter.NONE));
// Verify that the output is ordered by the separate "chains"
String[] expected = { m0.name(), m1.name(), m2.name(), m4.name(),
m6.name(), m8.name(), m9.name(), b3.name(), b5.name(),
b7.name() };
assertEquals(expected.length, selection.size());
for (int i = 0; i < expected.length; i++) {
assertEquals("Entry " + i, expected[i], selection.get(i).getName());
}
}
private RevCommit addCommit(BranchBuilder bb, String msg,
RevCommit... parents) throws Exception {
CommitBuilder commit = bb.commit().message(msg).add(msg, msg).tick(1)
.noParents();
for (RevCommit parent : parents) {
commit.parent(parent);
}
return commit.create();
}
@Test
public void testDistributionOnMultipleBranches() throws Exception {
BranchBuilder[] branches = { tr.branch("refs/heads/main"),
tr.branch("refs/heads/a"), tr.branch("refs/heads/b"),
tr.branch("refs/heads/c") };
RevCommit[] tips = new RevCommit[branches.length];
List<RevCommit> commits = createHistory(branches, tips);
PackConfig config = new PackConfig();
config.setBitmapContiguousCommitCount(1);
config.setBitmapRecentCommitSpan(5);
config.setBitmapDistantCommitSpan(20);
config.setBitmapRecentCommitCount(100);
Set<RevCommit> wants = new HashSet<>(Arrays.asList(tips));
PackWriterBitmapPreparer preparer = newPreparer(wants, commits, config);
List<BitmapCommit> selection = new ArrayList<>(
preparer.selectCommits(commits.size(), PackWriter.NONE));
Set<ObjectId> selected = new HashSet<>();
for (BitmapCommit c : selection) {
selected.add(c.toObjectId());
}
// Verify that each branch has uniform bitmap selection coverage
for (RevCommit c : wants) {
assertTrue(selected.contains(c.toObjectId()));
int count = 1;
int selectedCount = 1;
RevCommit parent = c;
while (parent.getParentCount() != 0) {
parent = parent.getParent(0);
count++;
if (selected.contains(parent.toObjectId())) {
selectedCount++;
}
}
// The selection algorithm prefers merges and will look in the
// current range plus the recent commit span before selecting a
// commit. Since this history has no merges, we expect the recent
// span should have 100/10=10 and distant commit spans should have
// 100/25=4 per 100 commit range.
int expectedCount = 10 + (count - 100 - 24) / 25;
assertTrue(expectedCount <= selectedCount);
}
}
private List<RevCommit> createHistory(BranchBuilder[] branches,
RevCommit[] tips) throws Exception {
/*-
* Create a history like this, where branches a, b and c branch off of the main branch
* at commits 100, 200 and 300, and where commit times move forward alternating between
* branches.
*
* o...o...o...o...o commits root,m0,m1,...,m399
* \ \ \
* \ \ o... commits branch_c,c300,c301,...,c399
* \ \
* \ o...o... commits branch_b,b200,b201,...,b399
* \
* o...o...o... commits branch_a,b100,b101,...,a399
*/
List<RevCommit> commits = new ArrayList<>();
String[] prefixes = { "m", "a", "b", "c" };
int branchCount = branches.length;
tips[0] = addCommit(commits, branches[0], "root");
int counter = 0;
for (int b = 0; b < branchCount; b++) {
for (int i = 0; i < 100; i++, counter++) {
for (int j = 0; j <= b; j++) {
tips[j] = addCommit(commits, branches[j],
prefixes[j] + counter);
}
}
// Create a new branch from current value of the master branch
if (b < branchCount - 1) {
tips[b + 1] = addCommit(branches[b + 1],
"branch_" + prefixes[b + 1], tips[0]);
}
}
return commits;
}
private RevCommit addCommit(List<RevCommit> commits, BranchBuilder bb,
String msg, RevCommit... parents) throws Exception {
CommitBuilder commit = bb.commit().message(msg).add(msg, msg).tick(1);
if (parents.length > 0) {
commit.noParents();
for (RevCommit parent : parents) {
commit.parent(parent);
}
}
RevCommit c = commit.create();
commits.add(c);
return c;
}
private PackWriterBitmapPreparer newPreparer(Set<RevCommit> wants,
List<RevCommit> commits, PackConfig config) throws IOException {
List<ObjectToPack> objects = new ArrayList<>(commits.size());
for (RevCommit commit : commits) {
objects.add(new ObjectToPack(commit, Constants.OBJ_COMMIT));
}
PackBitmapIndexBuilder builder = new PackBitmapIndexBuilder(objects);
return new PackWriterBitmapPreparer(
tr.getRepository().newObjectReader(), builder,
NullProgressMonitor.INSTANCE, wants, config);
}
}
|