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.

DfsGarbageCollector.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. /*
  2. * Copyright (C) 2011, 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.dfs;
  11. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
  12. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
  13. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST;
  14. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
  15. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.RECEIVE;
  16. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
  17. import static org.eclipse.jgit.internal.storage.dfs.DfsPackCompactor.configureReftable;
  18. import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
  19. import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
  20. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
  21. import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
  22. import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
  23. import java.io.IOException;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.Calendar;
  27. import java.util.Collection;
  28. import java.util.EnumSet;
  29. import java.util.GregorianCalendar;
  30. import java.util.HashSet;
  31. import java.util.List;
  32. import java.util.Set;
  33. import java.util.concurrent.TimeUnit;
  34. import org.eclipse.jgit.internal.JGitText;
  35. import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
  36. import org.eclipse.jgit.internal.storage.file.PackIndex;
  37. import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
  38. import org.eclipse.jgit.internal.storage.pack.PackExt;
  39. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  40. import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
  41. import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
  42. import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
  43. import org.eclipse.jgit.lib.AnyObjectId;
  44. import org.eclipse.jgit.lib.Constants;
  45. import org.eclipse.jgit.lib.NullProgressMonitor;
  46. import org.eclipse.jgit.lib.ObjectId;
  47. import org.eclipse.jgit.lib.ObjectIdSet;
  48. import org.eclipse.jgit.lib.ProgressMonitor;
  49. import org.eclipse.jgit.lib.Ref;
  50. import org.eclipse.jgit.lib.RefDatabase;
  51. import org.eclipse.jgit.revwalk.RevWalk;
  52. import org.eclipse.jgit.storage.pack.PackConfig;
  53. import org.eclipse.jgit.storage.pack.PackStatistics;
  54. import org.eclipse.jgit.util.SystemReader;
  55. import org.eclipse.jgit.util.io.CountingOutputStream;
  56. /**
  57. * Repack and garbage collect a repository.
  58. */
  59. public class DfsGarbageCollector {
  60. private final DfsRepository repo;
  61. private final RefDatabase refdb;
  62. private final DfsObjDatabase objdb;
  63. private final List<DfsPackDescription> newPackDesc;
  64. private final List<PackStatistics> newPackStats;
  65. private final List<ObjectIdSet> newPackObj;
  66. private DfsReader ctx;
  67. private PackConfig packConfig;
  68. private ReftableConfig reftableConfig;
  69. private boolean convertToReftable = true;
  70. private boolean includeDeletes;
  71. private long reftableInitialMinUpdateIndex = 1;
  72. private long reftableInitialMaxUpdateIndex = 1;
  73. // See packIsCoalesceableGarbage(), below, for how these two variables
  74. // interact.
  75. private long coalesceGarbageLimit = 50 << 20;
  76. private long garbageTtlMillis = TimeUnit.DAYS.toMillis(1);
  77. private long startTimeMillis;
  78. private List<DfsPackFile> packsBefore;
  79. private List<DfsReftable> reftablesBefore;
  80. private List<DfsPackFile> expiredGarbagePacks;
  81. private Collection<Ref> refsBefore;
  82. private Set<ObjectId> allHeadsAndTags;
  83. private Set<ObjectId> allTags;
  84. private Set<ObjectId> nonHeads;
  85. private Set<ObjectId> tagTargets;
  86. /**
  87. * Initialize a garbage collector.
  88. *
  89. * @param repository
  90. * repository objects to be packed will be read from.
  91. */
  92. public DfsGarbageCollector(DfsRepository repository) {
  93. repo = repository;
  94. refdb = repo.getRefDatabase();
  95. objdb = repo.getObjectDatabase();
  96. newPackDesc = new ArrayList<>(4);
  97. newPackStats = new ArrayList<>(4);
  98. newPackObj = new ArrayList<>(4);
  99. packConfig = new PackConfig(repo);
  100. packConfig.setIndexVersion(2);
  101. }
  102. /**
  103. * Get configuration used to generate the new pack file.
  104. *
  105. * @return configuration used to generate the new pack file.
  106. */
  107. public PackConfig getPackConfig() {
  108. return packConfig;
  109. }
  110. /**
  111. * Set the new configuration to use when creating the pack file.
  112. *
  113. * @param newConfig
  114. * the new configuration to use when creating the pack file.
  115. * @return {@code this}
  116. */
  117. public DfsGarbageCollector setPackConfig(PackConfig newConfig) {
  118. packConfig = newConfig;
  119. return this;
  120. }
  121. /**
  122. * Set configuration to write a reftable.
  123. *
  124. * @param cfg
  125. * configuration to write a reftable. Reftable writing is
  126. * disabled (default) when {@code cfg} is {@code null}.
  127. * @return {@code this}
  128. */
  129. public DfsGarbageCollector setReftableConfig(ReftableConfig cfg) {
  130. reftableConfig = cfg;
  131. return this;
  132. }
  133. /**
  134. * Whether the garbage collector should convert references to reftable.
  135. *
  136. * @param convert
  137. * if {@code true}, {@link #setReftableConfig(ReftableConfig)}
  138. * has been set non-null, and a GC reftable doesn't yet exist,
  139. * the garbage collector will make one by scanning the existing
  140. * references, and writing a new reftable. Default is
  141. * {@code true}.
  142. * @return {@code this}
  143. */
  144. public DfsGarbageCollector setConvertToReftable(boolean convert) {
  145. convertToReftable = convert;
  146. return this;
  147. }
  148. /**
  149. * Whether the garbage collector will include tombstones for deleted
  150. * references in the reftable.
  151. *
  152. * @param include
  153. * if {@code true}, the garbage collector will include tombstones
  154. * for deleted references in the reftable. Default is
  155. * {@code false}.
  156. * @return {@code this}
  157. */
  158. public DfsGarbageCollector setIncludeDeletes(boolean include) {
  159. includeDeletes = include;
  160. return this;
  161. }
  162. /**
  163. * Set minUpdateIndex for the initial reftable created during conversion.
  164. *
  165. * @param u
  166. * minUpdateIndex for the initial reftable created by scanning
  167. * {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase#getRefs(String)}.
  168. * Ignored unless caller has also set
  169. * {@link #setReftableConfig(ReftableConfig)}. Defaults to
  170. * {@code 1}. Must be {@code u >= 0}.
  171. * @return {@code this}
  172. */
  173. public DfsGarbageCollector setReftableInitialMinUpdateIndex(long u) {
  174. reftableInitialMinUpdateIndex = Math.max(u, 0);
  175. return this;
  176. }
  177. /**
  178. * Set maxUpdateIndex for the initial reftable created during conversion.
  179. *
  180. * @param u
  181. * maxUpdateIndex for the initial reftable created by scanning
  182. * {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase#getRefs(String)}.
  183. * Ignored unless caller has also set
  184. * {@link #setReftableConfig(ReftableConfig)}. Defaults to
  185. * {@code 1}. Must be {@code u >= 0}.
  186. * @return {@code this}
  187. */
  188. public DfsGarbageCollector setReftableInitialMaxUpdateIndex(long u) {
  189. reftableInitialMaxUpdateIndex = Math.max(0, u);
  190. return this;
  191. }
  192. /**
  193. * Get coalesce garbage limit
  194. *
  195. * @return coalesce garbage limit, packs smaller than this size will be
  196. * repacked.
  197. */
  198. public long getCoalesceGarbageLimit() {
  199. return coalesceGarbageLimit;
  200. }
  201. /**
  202. * Set the byte size limit for garbage packs to be repacked.
  203. * <p>
  204. * Any UNREACHABLE_GARBAGE pack smaller than this limit will be repacked at
  205. * the end of the run. This allows the garbage collector to coalesce
  206. * unreachable objects into a single file.
  207. * <p>
  208. * If an UNREACHABLE_GARBAGE pack is already larger than this limit it will
  209. * be left alone by the garbage collector. This avoids unnecessary disk IO
  210. * reading and copying the objects.
  211. * <p>
  212. * If limit is set to 0 the UNREACHABLE_GARBAGE coalesce is disabled.<br>
  213. * If limit is set to {@link java.lang.Long#MAX_VALUE}, everything is
  214. * coalesced.
  215. * <p>
  216. * Keeping unreachable garbage prevents race conditions with repository
  217. * changes that may suddenly need an object whose only copy was stored in
  218. * the UNREACHABLE_GARBAGE pack.
  219. *
  220. * @param limit
  221. * size in bytes.
  222. * @return {@code this}
  223. */
  224. public DfsGarbageCollector setCoalesceGarbageLimit(long limit) {
  225. coalesceGarbageLimit = limit;
  226. return this;
  227. }
  228. /**
  229. * Get time to live for garbage packs.
  230. *
  231. * @return garbage packs older than this limit (in milliseconds) will be
  232. * pruned as part of the garbage collection process if the value is
  233. * &gt; 0, otherwise garbage packs are retained.
  234. */
  235. public long getGarbageTtlMillis() {
  236. return garbageTtlMillis;
  237. }
  238. /**
  239. * Set the time to live for garbage objects.
  240. * <p>
  241. * Any UNREACHABLE_GARBAGE older than this limit will be pruned at the end
  242. * of the run.
  243. * <p>
  244. * If timeToLiveMillis is set to 0, UNREACHABLE_GARBAGE purging is disabled.
  245. *
  246. * @param ttl
  247. * Time to live whatever unit is specified.
  248. * @param unit
  249. * The specified time unit.
  250. * @return {@code this}
  251. */
  252. public DfsGarbageCollector setGarbageTtl(long ttl, TimeUnit unit) {
  253. garbageTtlMillis = unit.toMillis(ttl);
  254. return this;
  255. }
  256. /**
  257. * Create a single new pack file containing all of the live objects.
  258. * <p>
  259. * This method safely decides which packs can be expired after the new pack
  260. * is created by validating the references have not been modified in an
  261. * incompatible way.
  262. *
  263. * @param pm
  264. * progress monitor to receive updates on as packing may take a
  265. * while, depending on the size of the repository.
  266. * @return true if the repack was successful without race conditions. False
  267. * if a race condition was detected and the repack should be run
  268. * again later.
  269. * @throws java.io.IOException
  270. * a new pack cannot be created.
  271. */
  272. public boolean pack(ProgressMonitor pm) throws IOException {
  273. if (pm == null)
  274. pm = NullProgressMonitor.INSTANCE;
  275. if (packConfig.getIndexVersion() != 2)
  276. throw new IllegalStateException(
  277. JGitText.get().supportOnlyPackIndexVersion2);
  278. startTimeMillis = SystemReader.getInstance().getCurrentTime();
  279. ctx = objdb.newReader();
  280. try {
  281. refdb.refresh();
  282. objdb.clearCache();
  283. refsBefore = getAllRefs();
  284. readPacksBefore();
  285. readReftablesBefore();
  286. Set<ObjectId> allHeads = new HashSet<>();
  287. allHeadsAndTags = new HashSet<>();
  288. allTags = new HashSet<>();
  289. nonHeads = new HashSet<>();
  290. tagTargets = new HashSet<>();
  291. for (Ref ref : refsBefore) {
  292. if (ref.isSymbolic() || ref.getObjectId() == null) {
  293. continue;
  294. }
  295. if (isHead(ref)) {
  296. allHeads.add(ref.getObjectId());
  297. } else if (isTag(ref)) {
  298. allTags.add(ref.getObjectId());
  299. } else {
  300. nonHeads.add(ref.getObjectId());
  301. }
  302. if (ref.getPeeledObjectId() != null) {
  303. tagTargets.add(ref.getPeeledObjectId());
  304. }
  305. }
  306. // Don't exclude tags that are also branch tips.
  307. allTags.removeAll(allHeads);
  308. allHeadsAndTags.addAll(allHeads);
  309. allHeadsAndTags.addAll(allTags);
  310. // Hoist all branch tips and tags earlier in the pack file
  311. tagTargets.addAll(allHeadsAndTags);
  312. // Combine the GC_REST objects into the GC pack if requested
  313. if (packConfig.getSinglePack()) {
  314. allHeadsAndTags.addAll(nonHeads);
  315. nonHeads.clear();
  316. }
  317. boolean rollback = true;
  318. try {
  319. packHeads(pm);
  320. packRest(pm);
  321. packGarbage(pm);
  322. objdb.commitPack(newPackDesc, toPrune());
  323. rollback = false;
  324. return true;
  325. } finally {
  326. if (rollback)
  327. objdb.rollbackPack(newPackDesc);
  328. }
  329. } finally {
  330. ctx.close();
  331. }
  332. }
  333. private Collection<Ref> getAllRefs() throws IOException {
  334. Collection<Ref> refs = refdb.getRefs();
  335. List<Ref> addl = refdb.getAdditionalRefs();
  336. if (!addl.isEmpty()) {
  337. List<Ref> all = new ArrayList<>(refs.size() + addl.size());
  338. all.addAll(refs);
  339. // add additional refs which start with refs/
  340. for (Ref r : addl) {
  341. if (r.getName().startsWith(Constants.R_REFS)) {
  342. all.add(r);
  343. }
  344. }
  345. return all;
  346. }
  347. return refs;
  348. }
  349. private void readPacksBefore() throws IOException {
  350. DfsPackFile[] packs = objdb.getPacks();
  351. packsBefore = new ArrayList<>(packs.length);
  352. expiredGarbagePacks = new ArrayList<>(packs.length);
  353. long now = SystemReader.getInstance().getCurrentTime();
  354. for (DfsPackFile p : packs) {
  355. DfsPackDescription d = p.getPackDescription();
  356. if (d.getPackSource() != UNREACHABLE_GARBAGE) {
  357. packsBefore.add(p);
  358. } else if (packIsExpiredGarbage(d, now)) {
  359. expiredGarbagePacks.add(p);
  360. } else if (packIsCoalesceableGarbage(d, now)) {
  361. packsBefore.add(p);
  362. }
  363. }
  364. }
  365. private void readReftablesBefore() throws IOException {
  366. DfsReftable[] tables = objdb.getReftables();
  367. reftablesBefore = new ArrayList<>(Arrays.asList(tables));
  368. }
  369. private boolean packIsExpiredGarbage(DfsPackDescription d, long now) {
  370. // Consider the garbage pack as expired when it's older than
  371. // garbagePackTtl. This check gives concurrent inserter threads
  372. // sufficient time to identify an object is not in the graph and should
  373. // have a new copy written, rather than relying on something from an
  374. // UNREACHABLE_GARBAGE pack.
  375. return d.getPackSource() == UNREACHABLE_GARBAGE
  376. && garbageTtlMillis > 0
  377. && now - d.getLastModified() >= garbageTtlMillis;
  378. }
  379. private boolean packIsCoalesceableGarbage(DfsPackDescription d, long now) {
  380. // An UNREACHABLE_GARBAGE pack can be coalesced if its size is less than
  381. // the coalesceGarbageLimit and either garbageTtl is zero or if the pack
  382. // is created in a close time interval (on a single calendar day when
  383. // the garbageTtl is more than one day or one third of the garbageTtl).
  384. //
  385. // When the garbageTtl is more than 24 hours, garbage packs that are
  386. // created within a single calendar day are coalesced together. This
  387. // would make the effective ttl of the garbage pack as garbageTtl+23:59
  388. // and limit the number of garbage to a maximum number of
  389. // garbageTtl_in_days + 1 (assuming all of them are less than the size
  390. // of coalesceGarbageLimit).
  391. //
  392. // When the garbageTtl is less than or equal to 24 hours, garbage packs
  393. // that are created within a one third of garbageTtl are coalesced
  394. // together. This would make the effective ttl of the garbage packs as
  395. // garbageTtl + (garbageTtl / 3) and would limit the number of garbage
  396. // packs to a maximum number of 4 (assuming all of them are less than
  397. // the size of coalesceGarbageLimit).
  398. if (d.getPackSource() != UNREACHABLE_GARBAGE
  399. || d.getFileSize(PackExt.PACK) >= coalesceGarbageLimit) {
  400. return false;
  401. }
  402. if (garbageTtlMillis == 0) {
  403. return true;
  404. }
  405. long lastModified = d.getLastModified();
  406. long dayStartLastModified = dayStartInMillis(lastModified);
  407. long dayStartToday = dayStartInMillis(now);
  408. if (dayStartLastModified != dayStartToday) {
  409. return false; // this pack is not created today.
  410. }
  411. if (garbageTtlMillis > TimeUnit.DAYS.toMillis(1)) {
  412. return true; // ttl is more than one day and pack is created today.
  413. }
  414. long timeInterval = garbageTtlMillis / 3;
  415. if (timeInterval == 0) {
  416. return false; // ttl is too small, don't try to coalesce.
  417. }
  418. long modifiedTimeSlot = (lastModified - dayStartLastModified) / timeInterval;
  419. long presentTimeSlot = (now - dayStartToday) / timeInterval;
  420. return modifiedTimeSlot == presentTimeSlot;
  421. }
  422. private static long dayStartInMillis(long timeInMillis) {
  423. Calendar cal = new GregorianCalendar(
  424. SystemReader.getInstance().getTimeZone());
  425. cal.setTimeInMillis(timeInMillis);
  426. cal.set(Calendar.HOUR_OF_DAY, 0);
  427. cal.set(Calendar.MINUTE, 0);
  428. cal.set(Calendar.SECOND, 0);
  429. cal.set(Calendar.MILLISECOND, 0);
  430. return cal.getTimeInMillis();
  431. }
  432. /**
  433. * Get all of the source packs that fed into this compaction.
  434. *
  435. * @return all of the source packs that fed into this compaction.
  436. */
  437. public Set<DfsPackDescription> getSourcePacks() {
  438. return toPrune();
  439. }
  440. /**
  441. * Get new packs created by this compaction.
  442. *
  443. * @return new packs created by this compaction.
  444. */
  445. public List<DfsPackDescription> getNewPacks() {
  446. return newPackDesc;
  447. }
  448. /**
  449. * Get statistics corresponding to the {@link #getNewPacks()}.
  450. * <p>
  451. * The elements can be null if the stat is not available for the pack file.
  452. *
  453. * @return statistics corresponding to the {@link #getNewPacks()}.
  454. */
  455. public List<PackStatistics> getNewPackStatistics() {
  456. return newPackStats;
  457. }
  458. private Set<DfsPackDescription> toPrune() {
  459. Set<DfsPackDescription> toPrune = new HashSet<>();
  460. for (DfsPackFile pack : packsBefore) {
  461. toPrune.add(pack.getPackDescription());
  462. }
  463. if (reftableConfig != null) {
  464. for (DfsReftable table : reftablesBefore) {
  465. toPrune.add(table.getPackDescription());
  466. }
  467. }
  468. for (DfsPackFile pack : expiredGarbagePacks) {
  469. toPrune.add(pack.getPackDescription());
  470. }
  471. return toPrune;
  472. }
  473. private void packHeads(ProgressMonitor pm) throws IOException {
  474. if (allHeadsAndTags.isEmpty()) {
  475. writeReftable();
  476. return;
  477. }
  478. try (PackWriter pw = newPackWriter()) {
  479. pw.setTagTargets(tagTargets);
  480. pw.preparePack(pm, allHeadsAndTags, NONE, NONE, allTags);
  481. if (0 < pw.getObjectCount()) {
  482. long estSize = estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC);
  483. writePack(GC, pw, pm, estSize);
  484. } else {
  485. writeReftable();
  486. }
  487. }
  488. }
  489. private void packRest(ProgressMonitor pm) throws IOException {
  490. if (nonHeads.isEmpty())
  491. return;
  492. try (PackWriter pw = newPackWriter()) {
  493. for (ObjectIdSet packedObjs : newPackObj)
  494. pw.excludeObjects(packedObjs);
  495. pw.preparePack(pm, nonHeads, allHeadsAndTags);
  496. if (0 < pw.getObjectCount())
  497. writePack(GC_REST, pw, pm,
  498. estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC_REST));
  499. }
  500. }
  501. private void packGarbage(ProgressMonitor pm) throws IOException {
  502. PackConfig cfg = new PackConfig(packConfig);
  503. cfg.setReuseDeltas(true);
  504. cfg.setReuseObjects(true);
  505. cfg.setDeltaCompress(false);
  506. cfg.setBuildBitmaps(false);
  507. try (PackWriter pw = new PackWriter(cfg, ctx);
  508. RevWalk pool = new RevWalk(ctx)) {
  509. pw.setDeltaBaseAsOffset(true);
  510. pw.setReuseDeltaCommits(true);
  511. pm.beginTask(JGitText.get().findingGarbage, objectsBefore());
  512. long estimatedPackSize = 12 + 20; // header and trailer sizes.
  513. for (DfsPackFile oldPack : packsBefore) {
  514. PackIndex oldIdx = oldPack.getPackIndex(ctx);
  515. PackReverseIndex oldRevIdx = oldPack.getReverseIdx(ctx);
  516. long maxOffset = oldPack.getPackDescription().getFileSize(PACK)
  517. - 20; // pack size - trailer size.
  518. for (PackIndex.MutableEntry ent : oldIdx) {
  519. pm.update(1);
  520. ObjectId id = ent.toObjectId();
  521. if (pool.lookupOrNull(id) != null || anyPackHas(id))
  522. continue;
  523. long offset = ent.getOffset();
  524. int type = oldPack.getObjectType(ctx, offset);
  525. pw.addObject(pool.lookupAny(id, type));
  526. long objSize = oldRevIdx.findNextOffset(offset, maxOffset)
  527. - offset;
  528. estimatedPackSize += objSize;
  529. }
  530. }
  531. pm.endTask();
  532. if (0 < pw.getObjectCount())
  533. writePack(UNREACHABLE_GARBAGE, pw, pm, estimatedPackSize);
  534. }
  535. }
  536. private boolean anyPackHas(AnyObjectId id) {
  537. for (ObjectIdSet packedObjs : newPackObj)
  538. if (packedObjs.contains(id))
  539. return true;
  540. return false;
  541. }
  542. private static boolean isHead(Ref ref) {
  543. return ref.getName().startsWith(Constants.R_HEADS);
  544. }
  545. private static boolean isTag(Ref ref) {
  546. return ref.getName().startsWith(Constants.R_TAGS);
  547. }
  548. private int objectsBefore() {
  549. int cnt = 0;
  550. for (DfsPackFile p : packsBefore)
  551. cnt += (int) p.getPackDescription().getObjectCount();
  552. return cnt;
  553. }
  554. private PackWriter newPackWriter() {
  555. PackWriter pw = new PackWriter(packConfig, ctx);
  556. pw.setDeltaBaseAsOffset(true);
  557. pw.setReuseDeltaCommits(false);
  558. return pw;
  559. }
  560. private long estimateGcPackSize(PackSource first, PackSource... rest) {
  561. EnumSet<PackSource> sourceSet = EnumSet.of(first, rest);
  562. // Every pack file contains 12 bytes of header and 20 bytes of trailer.
  563. // Include the final pack file header and trailer size here and ignore
  564. // the same from individual pack files.
  565. long size = 32;
  566. for (DfsPackDescription pack : getSourcePacks()) {
  567. if (sourceSet.contains(pack.getPackSource())) {
  568. size += pack.getFileSize(PACK) - 32;
  569. }
  570. }
  571. return size;
  572. }
  573. private DfsPackDescription writePack(PackSource source, PackWriter pw,
  574. ProgressMonitor pm, long estimatedPackSize) throws IOException {
  575. DfsPackDescription pack = repo.getObjectDatabase().newPack(source,
  576. estimatedPackSize);
  577. if (source == GC && reftableConfig != null) {
  578. writeReftable(pack);
  579. }
  580. try (DfsOutputStream out = objdb.writeFile(pack, PACK)) {
  581. pw.writePack(pm, pm, out);
  582. pack.addFileExt(PACK);
  583. pack.setBlockSize(PACK, out.blockSize());
  584. }
  585. try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
  586. CountingOutputStream cnt = new CountingOutputStream(out);
  587. pw.writeIndex(cnt);
  588. pack.addFileExt(INDEX);
  589. pack.setFileSize(INDEX, cnt.getCount());
  590. pack.setBlockSize(INDEX, out.blockSize());
  591. pack.setIndexVersion(pw.getIndexVersion());
  592. }
  593. if (pw.prepareBitmapIndex(pm)) {
  594. try (DfsOutputStream out = objdb.writeFile(pack, BITMAP_INDEX)) {
  595. CountingOutputStream cnt = new CountingOutputStream(out);
  596. pw.writeBitmapIndex(cnt);
  597. pack.addFileExt(BITMAP_INDEX);
  598. pack.setFileSize(BITMAP_INDEX, cnt.getCount());
  599. pack.setBlockSize(BITMAP_INDEX, out.blockSize());
  600. }
  601. }
  602. PackStatistics stats = pw.getStatistics();
  603. pack.setPackStats(stats);
  604. pack.setLastModified(startTimeMillis);
  605. newPackDesc.add(pack);
  606. newPackStats.add(stats);
  607. newPackObj.add(pw.getObjectSet());
  608. return pack;
  609. }
  610. private void writeReftable() throws IOException {
  611. if (reftableConfig != null) {
  612. DfsPackDescription pack = objdb.newPack(GC);
  613. newPackDesc.add(pack);
  614. newPackStats.add(null);
  615. writeReftable(pack);
  616. }
  617. }
  618. private void writeReftable(DfsPackDescription pack) throws IOException {
  619. if (convertToReftable && !hasGcReftable()) {
  620. writeReftable(pack, refsBefore);
  621. return;
  622. }
  623. try (DfsReftableStack stack = DfsReftableStack.open(ctx, reftablesBefore);
  624. DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
  625. ReftableCompactor compact = new ReftableCompactor(out);
  626. compact.addAll(stack.readers());
  627. compact.setIncludeDeletes(includeDeletes);
  628. compact.setConfig(configureReftable(reftableConfig, out));
  629. compact.compact();
  630. pack.addFileExt(REFTABLE);
  631. pack.setReftableStats(compact.getStats());
  632. }
  633. }
  634. private boolean hasGcReftable() {
  635. for (DfsReftable table : reftablesBefore) {
  636. if (table.getPackDescription().getPackSource() == GC) {
  637. return true;
  638. }
  639. }
  640. return false;
  641. }
  642. private void writeReftable(DfsPackDescription pack, Collection<Ref> refs)
  643. throws IOException {
  644. try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
  645. ReftableConfig cfg = configureReftable(reftableConfig, out);
  646. ReftableWriter writer = new ReftableWriter(cfg, out)
  647. .setMinUpdateIndex(reftableInitialMinUpdateIndex)
  648. .setMaxUpdateIndex(reftableInitialMaxUpdateIndex).begin()
  649. .sortAndWriteRefs(refs).finish();
  650. pack.addFileExt(REFTABLE);
  651. pack.setReftableStats(writer.getStats());
  652. }
  653. }
  654. }