You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GcCommitSelectionTest.java 12KB

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