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.

PackWriterBitmapPreparer.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. /*
  2. * Copyright (C) 2012, 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.eclipse.jgit.internal.storage.file.PackBitmapIndex.FLAG_REUSE;
  12. import static org.eclipse.jgit.revwalk.RevFlag.SEEN;
  13. import java.io.IOException;
  14. import java.util.ArrayList;
  15. import java.util.Collection;
  16. import java.util.Collections;
  17. import java.util.Comparator;
  18. import java.util.HashSet;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.Set;
  22. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  23. import org.eclipse.jgit.errors.MissingObjectException;
  24. import org.eclipse.jgit.internal.JGitText;
  25. import org.eclipse.jgit.internal.revwalk.AddUnseenToBitmapFilter;
  26. import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
  27. import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap;
  28. import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
  29. import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
  30. import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper;
  31. import org.eclipse.jgit.lib.AnyObjectId;
  32. import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
  33. import org.eclipse.jgit.lib.Constants;
  34. import org.eclipse.jgit.lib.ObjectId;
  35. import org.eclipse.jgit.lib.ObjectReader;
  36. import org.eclipse.jgit.lib.ProgressMonitor;
  37. import org.eclipse.jgit.revwalk.BitmapWalker;
  38. import org.eclipse.jgit.revwalk.ObjectWalk;
  39. import org.eclipse.jgit.revwalk.RevCommit;
  40. import org.eclipse.jgit.revwalk.RevObject;
  41. import org.eclipse.jgit.revwalk.RevWalk;
  42. import org.eclipse.jgit.revwalk.filter.RevFilter;
  43. import org.eclipse.jgit.storage.pack.PackConfig;
  44. import org.eclipse.jgit.util.BlockList;
  45. import org.eclipse.jgit.util.SystemReader;
  46. import com.googlecode.javaewah.EWAHCompressedBitmap;
  47. /**
  48. * Helper class for the {@link PackWriter} to select commits for which to build
  49. * pack index bitmaps.
  50. */
  51. class PackWriterBitmapPreparer {
  52. private static final int DAY_IN_SECONDS = 24 * 60 * 60;
  53. private static final Comparator<RevCommit> ORDER_BY_REVERSE_TIMESTAMP = (
  54. RevCommit a, RevCommit b) -> Integer
  55. .signum(b.getCommitTime() - a.getCommitTime());
  56. private final ObjectReader reader;
  57. private final ProgressMonitor pm;
  58. private final Set<? extends ObjectId> want;
  59. private final PackBitmapIndexBuilder writeBitmaps;
  60. private final BitmapIndexImpl commitBitmapIndex;
  61. private final PackBitmapIndexRemapper bitmapRemapper;
  62. private final BitmapIndexImpl bitmapIndex;
  63. private final int contiguousCommitCount;
  64. private final int recentCommitCount;
  65. private final int recentCommitSpan;
  66. private final int distantCommitSpan;
  67. private final int excessiveBranchCount;
  68. private final long inactiveBranchTimestamp;
  69. PackWriterBitmapPreparer(ObjectReader reader,
  70. PackBitmapIndexBuilder writeBitmaps, ProgressMonitor pm,
  71. Set<? extends ObjectId> want, PackConfig config)
  72. throws IOException {
  73. this.reader = reader;
  74. this.writeBitmaps = writeBitmaps;
  75. this.pm = pm;
  76. this.want = want;
  77. this.commitBitmapIndex = new BitmapIndexImpl(writeBitmaps);
  78. this.bitmapRemapper = PackBitmapIndexRemapper.newPackBitmapIndex(
  79. reader.getBitmapIndex(), writeBitmaps);
  80. this.bitmapIndex = new BitmapIndexImpl(bitmapRemapper);
  81. this.contiguousCommitCount = config.getBitmapContiguousCommitCount();
  82. this.recentCommitCount = config.getBitmapRecentCommitCount();
  83. this.recentCommitSpan = config.getBitmapRecentCommitSpan();
  84. this.distantCommitSpan = config.getBitmapDistantCommitSpan();
  85. this.excessiveBranchCount = config.getBitmapExcessiveBranchCount();
  86. long now = SystemReader.getInstance().getCurrentTime();
  87. long ageInSeconds = config.getBitmapInactiveBranchAgeInDays()
  88. * DAY_IN_SECONDS;
  89. this.inactiveBranchTimestamp = (now / 1000) - ageInSeconds;
  90. }
  91. /**
  92. * Returns the commit objects for which bitmap indices should be built.
  93. *
  94. * @param expectedCommitCount
  95. * count of commits in the pack
  96. * @param excludeFromBitmapSelection
  97. * commits that should be excluded from bitmap selection
  98. * @return commit objects for which bitmap indices should be built
  99. * @throws IncorrectObjectTypeException
  100. * if any of the processed objects is not a commit
  101. * @throws IOException
  102. * on errors reading pack or index files
  103. * @throws MissingObjectException
  104. * if an expected object is missing
  105. */
  106. Collection<BitmapCommit> selectCommits(int expectedCommitCount,
  107. Set<? extends ObjectId> excludeFromBitmapSelection)
  108. throws IncorrectObjectTypeException, IOException,
  109. MissingObjectException {
  110. /*
  111. * Thinking of bitmap indices as a cache, if we find bitmaps at or at a
  112. * close ancestor to 'old' and 'new' when calculating old..new, then all
  113. * objects can be calculated with minimal graph walking. A distribution
  114. * that favors creating bitmaps for the most recent commits maximizes
  115. * the cache hits for clients that are close to HEAD, which is the
  116. * majority of calculations performed.
  117. */
  118. try (RevWalk rw = new RevWalk(reader);
  119. RevWalk rw2 = new RevWalk(reader)) {
  120. pm.beginTask(JGitText.get().selectingCommits,
  121. ProgressMonitor.UNKNOWN);
  122. rw.setRetainBody(false);
  123. CommitSelectionHelper selectionHelper = captureOldAndNewCommits(rw,
  124. expectedCommitCount, excludeFromBitmapSelection);
  125. pm.endTask();
  126. // Add reused bitmaps from the previous GC pack's bitmap indices.
  127. // Currently they are always fully reused, even if their spans don't
  128. // match this run's PackConfig values.
  129. int newCommits = selectionHelper.getCommitCount();
  130. BlockList<BitmapCommit> selections = new BlockList<>(
  131. selectionHelper.reusedCommits.size()
  132. + newCommits / recentCommitSpan + 1);
  133. for (BitmapCommit reuse : selectionHelper.reusedCommits) {
  134. selections.add(reuse);
  135. }
  136. if (newCommits == 0) {
  137. for (AnyObjectId id : selectionHelper.newWants) {
  138. selections.add(new BitmapCommit(id, false, 0));
  139. }
  140. return selections;
  141. }
  142. pm.beginTask(JGitText.get().selectingCommits, newCommits);
  143. int totalWants = want.size();
  144. BitmapBuilder seen = commitBitmapIndex.newBitmapBuilder();
  145. seen.or(selectionHelper.reusedCommitsBitmap);
  146. rw2.setRetainBody(false);
  147. rw2.setRevFilter(new NotInBitmapFilter(seen));
  148. // For each branch, do a revwalk to enumerate its commits. Exclude
  149. // both reused commits and any commits seen in a previous branch.
  150. // Then iterate through all new commits from oldest to newest,
  151. // selecting well-spaced commits in this branch.
  152. for (RevCommit rc : selectionHelper.newWantsByNewest) {
  153. BitmapBuilder tipBitmap = commitBitmapIndex.newBitmapBuilder();
  154. rw2.markStart((RevCommit) rw2.peel(rw2.parseAny(rc)));
  155. RevCommit rc2;
  156. while ((rc2 = rw2.next()) != null) {
  157. tipBitmap.addObject(rc2, Constants.OBJ_COMMIT);
  158. }
  159. int cardinality = tipBitmap.cardinality();
  160. seen.or(tipBitmap);
  161. // Within this branch, keep ordered lists of commits
  162. // representing chains in its history, where each chain is a
  163. // "sub-branch". Ordering commits by these chains makes for
  164. // fewer differences between consecutive selected commits, which
  165. // in turn provides better compression/on the run-length
  166. // encoding of the XORs between them.
  167. List<List<BitmapCommit>> chains = new ArrayList<>();
  168. // Mark the current branch as inactive if its tip commit isn't
  169. // recent and there are an excessive number of branches, to
  170. // prevent memory bloat of computing too many bitmaps for stale
  171. // branches.
  172. boolean isActiveBranch = true;
  173. if (totalWants > excessiveBranchCount && !isRecentCommit(rc)) {
  174. isActiveBranch = false;
  175. }
  176. // Insert bitmaps at the offsets suggested by the
  177. // nextSelectionDistance() heuristic. Only reuse bitmaps created
  178. // for more distant commits.
  179. int index = -1;
  180. int nextIn = nextSpan(cardinality);
  181. int nextFlg = nextIn == distantCommitSpan
  182. ? PackBitmapIndex.FLAG_REUSE
  183. : 0;
  184. // For the current branch, iterate through all commits from
  185. // oldest to newest.
  186. for (RevCommit c : selectionHelper) {
  187. // Optimization: if we have found all the commits for this
  188. // branch, stop searching
  189. int distanceFromTip = cardinality - index - 1;
  190. if (distanceFromTip == 0) {
  191. break;
  192. }
  193. // Ignore commits that are not in this branch
  194. if (!tipBitmap.contains(c)) {
  195. continue;
  196. }
  197. index++;
  198. nextIn--;
  199. pm.update(1);
  200. // Always pick the items in wants, prefer merge commits.
  201. if (selectionHelper.newWants.remove(c)) {
  202. if (nextIn > 0) {
  203. nextFlg = 0;
  204. }
  205. } else {
  206. boolean stillInSpan = nextIn >= 0;
  207. boolean isMergeCommit = c.getParentCount() > 1;
  208. // Force selection if:
  209. // a) we have exhausted the window looking for merges
  210. // b) we are in the top commits of an active branch
  211. // c) we are at a branch tip
  212. boolean mustPick = (nextIn <= -recentCommitSpan)
  213. || (isActiveBranch
  214. && (distanceFromTip <= contiguousCommitCount))
  215. || (distanceFromTip == 1); // most recent commit
  216. if (!mustPick && (stillInSpan || !isMergeCommit)) {
  217. continue;
  218. }
  219. }
  220. // This commit is selected.
  221. // Calculate where to look for the next one.
  222. int flags = nextFlg;
  223. nextIn = nextSpan(distanceFromTip);
  224. nextFlg = nextIn == distantCommitSpan
  225. ? PackBitmapIndex.FLAG_REUSE
  226. : 0;
  227. // Create the commit bitmap for the current commit
  228. BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder();
  229. rw.reset();
  230. rw.markStart(c);
  231. rw.setRevFilter(new AddUnseenToBitmapFilter(
  232. selectionHelper.reusedCommitsBitmap, bitmap));
  233. while (rw.next() != null) {
  234. // The filter adds the reachable commits to bitmap.
  235. }
  236. // Sort the commits by independent chains in this branch's
  237. // history, yielding better compression when building
  238. // bitmaps.
  239. List<BitmapCommit> longestAncestorChain = null;
  240. for (List<BitmapCommit> chain : chains) {
  241. BitmapCommit mostRecentCommit = chain
  242. .get(chain.size() - 1);
  243. if (bitmap.contains(mostRecentCommit)) {
  244. if (longestAncestorChain == null
  245. || longestAncestorChain.size() < chain
  246. .size()) {
  247. longestAncestorChain = chain;
  248. }
  249. }
  250. }
  251. if (longestAncestorChain == null) {
  252. longestAncestorChain = new ArrayList<>();
  253. chains.add(longestAncestorChain);
  254. }
  255. longestAncestorChain.add(new BitmapCommit(c,
  256. !longestAncestorChain.isEmpty(), flags));
  257. writeBitmaps.addBitmap(c, bitmap, 0);
  258. }
  259. for (List<BitmapCommit> chain : chains) {
  260. selections.addAll(chain);
  261. }
  262. }
  263. writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps.
  264. // Add the remaining peeledWant
  265. for (AnyObjectId remainingWant : selectionHelper.newWants) {
  266. selections.add(new BitmapCommit(remainingWant, false, 0));
  267. }
  268. pm.endTask();
  269. return selections;
  270. }
  271. }
  272. private boolean isRecentCommit(RevCommit revCommit) {
  273. return revCommit.getCommitTime() > inactiveBranchTimestamp;
  274. }
  275. /**
  276. * A RevFilter that excludes the commits named in a bitmap from the walk.
  277. * <p>
  278. * If a commit is in {@code bitmap} then that commit is not emitted by the
  279. * walk and its parents are marked as SEEN so the walk can skip them. The
  280. * bitmaps passed in have the property that the parents of any commit in
  281. * {@code bitmap} are also in {@code bitmap}, so marking the parents as
  282. * SEEN speeds up the RevWalk by saving it from walking down blind alleys
  283. * and does not change the commits emitted.
  284. */
  285. private static class NotInBitmapFilter extends RevFilter {
  286. private final BitmapBuilder bitmap;
  287. NotInBitmapFilter(BitmapBuilder bitmap) {
  288. this.bitmap = bitmap;
  289. }
  290. @Override
  291. public final boolean include(RevWalk rw, RevCommit c) {
  292. if (!bitmap.contains(c)) {
  293. return true;
  294. }
  295. for (RevCommit p : c.getParents()) {
  296. p.add(SEEN);
  297. }
  298. return false;
  299. }
  300. @Override
  301. public final NotInBitmapFilter clone() {
  302. throw new UnsupportedOperationException();
  303. }
  304. @Override
  305. public final boolean requiresCommitBody() {
  306. return false;
  307. }
  308. }
  309. /**
  310. * Records which of the {@code wants} can be found in the previous GC pack's
  311. * bitmap indices and which are new.
  312. *
  313. * @param rw
  314. * a {@link RevWalk} to find reachable objects in this repository
  315. * @param expectedCommitCount
  316. * expected count of commits. The actual count may be less due to
  317. * unreachable garbage.
  318. * @param excludeFromBitmapSelection
  319. * commits that should be excluded from bitmap selection
  320. * @return a {@link CommitSelectionHelper} capturing which commits are
  321. * covered by a previous pack's bitmaps and which new commits need
  322. * bitmap coverage
  323. * @throws IncorrectObjectTypeException
  324. * if any of the processed objects is not a commit
  325. * @throws IOException
  326. * on errors reading pack or index files
  327. * @throws MissingObjectException
  328. * if an expected object is missing
  329. */
  330. private CommitSelectionHelper captureOldAndNewCommits(RevWalk rw,
  331. int expectedCommitCount,
  332. Set<? extends ObjectId> excludeFromBitmapSelection)
  333. throws IncorrectObjectTypeException, IOException,
  334. MissingObjectException {
  335. // Track bitmaps and commits from the previous GC pack bitmap indices.
  336. BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder();
  337. List<BitmapCommit> reuseCommits = new ArrayList<>();
  338. for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) {
  339. // More recent commits did not have the reuse flag set, so skip them
  340. if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) {
  341. continue;
  342. }
  343. RevObject ro = rw.peel(rw.parseAny(entry));
  344. if (!(ro instanceof RevCommit)) {
  345. continue;
  346. }
  347. RevCommit rc = (RevCommit) ro;
  348. reuseCommits.add(new BitmapCommit(rc, false, entry.getFlags()));
  349. if (!reuse.contains(rc)) {
  350. EWAHCompressedBitmap bitmap = bitmapRemapper.ofObjectType(
  351. bitmapRemapper.getBitmap(rc), Constants.OBJ_COMMIT);
  352. reuse.or(new CompressedBitmap(bitmap, commitBitmapIndex));
  353. }
  354. }
  355. // Add branch tips that are not represented in a previous pack's bitmap
  356. // indices. Set up a RevWalk to find new commits not in the old packs.
  357. List<RevCommit> newWantsByNewest = new ArrayList<>(want.size());
  358. Set<RevCommit> newWants = new HashSet<>(want.size());
  359. for (AnyObjectId objectId : want) {
  360. RevObject ro = rw.peel(rw.parseAny(objectId));
  361. if (!(ro instanceof RevCommit) || reuse.contains(ro)
  362. || excludeFromBitmapSelection.contains(ro)) {
  363. continue;
  364. }
  365. RevCommit rc = (RevCommit) ro;
  366. rw.markStart(rc);
  367. newWants.add(rc);
  368. newWantsByNewest.add(rc);
  369. }
  370. // Create a list of commits in reverse order (older to newer) that are
  371. // not in the previous bitmap indices and are reachable.
  372. rw.setRevFilter(new NotInBitmapFilter(reuse));
  373. RevCommit[] commits = new RevCommit[expectedCommitCount];
  374. int pos = commits.length;
  375. RevCommit rc;
  376. while ((rc = rw.next()) != null && pos > 0) {
  377. commits[--pos] = rc;
  378. pm.update(1);
  379. }
  380. // Sort the new wants by reverse commit time.
  381. Collections.sort(newWantsByNewest, ORDER_BY_REVERSE_TIMESTAMP);
  382. return new CommitSelectionHelper(newWants, commits, pos,
  383. newWantsByNewest, reuse, reuseCommits);
  384. }
  385. /*-
  386. * Returns the desired distance to the next bitmap based on the distance
  387. * from the tip commit. Only differentiates recent from distant spans,
  388. * selectCommits() handles the contiguous commits at the tip for active
  389. * or inactive branches.
  390. *
  391. * A graph of this function looks like this, where
  392. * the X axis is the distance from the tip commit and the Y axis is the
  393. * bitmap selection distance.
  394. *
  395. * 5000 ____...
  396. * /
  397. * /
  398. * /
  399. * /
  400. * 100 _____/
  401. * 0 20100 25000
  402. *
  403. * Linear scaling between 20100 and 25000 prevents spans >100 for distances
  404. * <20000 (otherwise, a span of 5000 would be returned for a distance of
  405. * 21000, and the range 16000-20000 would have no selections).
  406. */
  407. int nextSpan(int distanceFromTip) {
  408. if (distanceFromTip < 0) {
  409. throw new IllegalArgumentException();
  410. }
  411. // Commits more toward the start will have more bitmaps.
  412. if (distanceFromTip <= recentCommitCount) {
  413. return recentCommitSpan;
  414. }
  415. int next = Math.min(distanceFromTip - recentCommitCount,
  416. distantCommitSpan);
  417. return Math.max(next, recentCommitSpan);
  418. }
  419. BitmapWalker newBitmapWalker() {
  420. return new BitmapWalker(
  421. new ObjectWalk(reader), bitmapIndex, null);
  422. }
  423. /**
  424. * Container for state used in the first phase of selecting commits, which
  425. * walks all of the reachable commits via the branch tips that are not
  426. * covered by a previous pack's bitmaps ({@code newWants}) and stores them
  427. * in {@code newCommitsByOldest}. {@code newCommitsByOldest} is initialized
  428. * with an expected size of all commits, but may be smaller if some commits
  429. * are unreachable and/or some commits are covered by a previous pack's
  430. * bitmaps. {@code commitStartPos} will contain a positive offset to either
  431. * the root commit or the oldest commit not covered by previous bitmaps.
  432. */
  433. private static final class CommitSelectionHelper implements Iterable<RevCommit> {
  434. final Set<? extends ObjectId> newWants;
  435. final List<RevCommit> newWantsByNewest;
  436. final BitmapBuilder reusedCommitsBitmap;
  437. final List<BitmapCommit> reusedCommits;
  438. final RevCommit[] newCommitsByOldest;
  439. final int newCommitStartPos;
  440. CommitSelectionHelper(Set<? extends ObjectId> newWants,
  441. RevCommit[] commitsByOldest, int commitStartPos,
  442. List<RevCommit> newWantsByNewest,
  443. BitmapBuilder reusedCommitsBitmap,
  444. List<BitmapCommit> reuse) {
  445. this.newWants = newWants;
  446. this.newCommitsByOldest = commitsByOldest;
  447. this.newCommitStartPos = commitStartPos;
  448. this.newWantsByNewest = newWantsByNewest;
  449. this.reusedCommitsBitmap = reusedCommitsBitmap;
  450. this.reusedCommits = reuse;
  451. }
  452. @Override
  453. public Iterator<RevCommit> iterator() {
  454. // Member variables referenced by this iterator will have synthetic
  455. // accessors generated for them if they are made private.
  456. return new Iterator<RevCommit>() {
  457. int pos = newCommitStartPos;
  458. @Override
  459. public boolean hasNext() {
  460. return pos < newCommitsByOldest.length;
  461. }
  462. @Override
  463. public RevCommit next() {
  464. return newCommitsByOldest[pos++];
  465. }
  466. @Override
  467. public void remove() {
  468. throw new UnsupportedOperationException();
  469. }
  470. };
  471. }
  472. int getCommitCount() {
  473. return newCommitsByOldest.length - newCommitStartPos;
  474. }
  475. }
  476. }