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.

GCTest.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. /*
  2. * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.internal.storage.file;
  44. import static java.lang.Integer.valueOf;
  45. import static org.junit.Assert.assertEquals;
  46. import static org.junit.Assert.assertFalse;
  47. import static org.junit.Assert.assertSame;
  48. import static org.junit.Assert.assertTrue;
  49. import java.io.File;
  50. import java.io.IOException;
  51. import java.util.Collection;
  52. import java.util.Collections;
  53. import java.util.Date;
  54. import java.util.Iterator;
  55. import java.util.concurrent.BrokenBarrierException;
  56. import java.util.concurrent.Callable;
  57. import java.util.concurrent.CyclicBarrier;
  58. import java.util.concurrent.ExecutorService;
  59. import java.util.concurrent.Executors;
  60. import java.util.concurrent.Future;
  61. import java.util.concurrent.TimeUnit;
  62. import org.eclipse.jgit.api.Git;
  63. import org.eclipse.jgit.internal.JGitText;
  64. import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
  65. import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
  66. import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
  67. import org.eclipse.jgit.junit.RepositoryTestCase;
  68. import org.eclipse.jgit.junit.TestRepository;
  69. import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
  70. import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
  71. import org.eclipse.jgit.lib.AnyObjectId;
  72. import org.eclipse.jgit.lib.Constants;
  73. import org.eclipse.jgit.lib.EmptyProgressMonitor;
  74. import org.eclipse.jgit.lib.ObjectId;
  75. import org.eclipse.jgit.lib.Ref.Storage;
  76. import org.eclipse.jgit.lib.RefUpdate;
  77. import org.eclipse.jgit.lib.RefUpdate.Result;
  78. import org.eclipse.jgit.merge.MergeStrategy;
  79. import org.eclipse.jgit.merge.Merger;
  80. import org.eclipse.jgit.revwalk.RevBlob;
  81. import org.eclipse.jgit.revwalk.RevCommit;
  82. import org.eclipse.jgit.revwalk.RevTag;
  83. import org.eclipse.jgit.revwalk.RevTree;
  84. import org.eclipse.jgit.util.FileUtils;
  85. import org.junit.After;
  86. import org.junit.Before;
  87. import org.junit.Test;
  88. public class GCTest extends LocalDiskRepositoryTestCase {
  89. private TestRepository<FileRepository> tr;
  90. private FileRepository repo;
  91. private GC gc;
  92. private RepoStatistics stats;
  93. @Before
  94. public void setUp() throws Exception {
  95. super.setUp();
  96. repo = createWorkRepository();
  97. tr = new TestRepository<FileRepository>((repo));
  98. gc = new GC(repo);
  99. }
  100. @After
  101. public void tearDown() throws Exception {
  102. super.tearDown();
  103. }
  104. // GC.packRefs tests
  105. @Test
  106. public void packRefs_looseRefPacked() throws Exception {
  107. RevBlob a = tr.blob("a");
  108. tr.lightweightTag("t", a);
  109. gc.packRefs();
  110. assertSame(repo.getRef("t").getStorage(), Storage.PACKED);
  111. }
  112. @Test
  113. public void concurrentPackRefs_onlyOneWritesPackedRefs() throws Exception {
  114. RevBlob a = tr.blob("a");
  115. tr.lightweightTag("t", a);
  116. final CyclicBarrier syncPoint = new CyclicBarrier(2);
  117. Callable<Integer> packRefs = new Callable<Integer>() {
  118. /** @return 0 for success, 1 in case of error when writing pack */
  119. public Integer call() throws Exception {
  120. syncPoint.await();
  121. try {
  122. gc.packRefs();
  123. return valueOf(0);
  124. } catch (IOException e) {
  125. return valueOf(1);
  126. }
  127. }
  128. };
  129. ExecutorService pool = Executors.newFixedThreadPool(2);
  130. try {
  131. Future<Integer> p1 = pool.submit(packRefs);
  132. Future<Integer> p2 = pool.submit(packRefs);
  133. assertEquals(1, p1.get().intValue() + p2.get().intValue());
  134. } finally {
  135. pool.shutdown();
  136. pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
  137. }
  138. }
  139. @Test
  140. public void packRefsWhileRefLocked_refNotPackedNoError()
  141. throws Exception {
  142. RevBlob a = tr.blob("a");
  143. tr.lightweightTag("t1", a);
  144. tr.lightweightTag("t2", a);
  145. LockFile refLock = new LockFile(new File(repo.getDirectory(),
  146. "refs/tags/t1"), repo.getFS());
  147. try {
  148. refLock.lock();
  149. gc.packRefs();
  150. } finally {
  151. refLock.unlock();
  152. }
  153. assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE);
  154. assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED);
  155. }
  156. @Test
  157. public void packRefsWhileRefUpdated_refUpdateSucceeds()
  158. throws Exception {
  159. RevBlob a = tr.blob("a");
  160. tr.lightweightTag("t", a);
  161. final RevBlob b = tr.blob("b");
  162. final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2);
  163. final CyclicBarrier packRefsDone = new CyclicBarrier(2);
  164. ExecutorService pool = Executors.newFixedThreadPool(2);
  165. try {
  166. Future<Result> result = pool.submit(new Callable<Result>() {
  167. public Result call() throws Exception {
  168. RefUpdate update = new RefDirectoryUpdate(
  169. (RefDirectory) repo.getRefDatabase(),
  170. repo.getRef("refs/tags/t")) {
  171. @Override
  172. public boolean isForceUpdate() {
  173. try {
  174. refUpdateLockedRef.await();
  175. packRefsDone.await();
  176. } catch (InterruptedException e) {
  177. Thread.currentThread().interrupt();
  178. } catch (BrokenBarrierException e) {
  179. Thread.currentThread().interrupt();
  180. }
  181. return super.isForceUpdate();
  182. }
  183. };
  184. update.setForceUpdate(true);
  185. update.setNewObjectId(b);
  186. return update.update();
  187. }
  188. });
  189. pool.submit(new Callable<Void>() {
  190. public Void call() throws Exception {
  191. refUpdateLockedRef.await();
  192. gc.packRefs();
  193. packRefsDone.await();
  194. return null;
  195. }
  196. });
  197. assertSame(result.get(), Result.FORCED);
  198. } finally {
  199. pool.shutdownNow();
  200. pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
  201. }
  202. assertEquals(repo.getRef("refs/tags/t").getObjectId(), b);
  203. }
  204. // GC.repack tests
  205. @Test
  206. public void repackEmptyRepo_noPackCreated() throws IOException {
  207. gc.repack();
  208. assertEquals(0, repo.getObjectDatabase().getPacks().size());
  209. }
  210. @Test
  211. public void concurrentRepack() throws Exception {
  212. final CyclicBarrier syncPoint = new CyclicBarrier(2);
  213. class DoRepack extends EmptyProgressMonitor implements
  214. Callable<Integer> {
  215. public void beginTask(String title, int totalWork) {
  216. if (title.equals(JGitText.get().writingObjects)) {
  217. try {
  218. syncPoint.await();
  219. } catch (InterruptedException e) {
  220. Thread.currentThread().interrupt();
  221. } catch (BrokenBarrierException ignored) {
  222. //
  223. }
  224. }
  225. }
  226. /** @return 0 for success, 1 in case of error when writing pack */
  227. public Integer call() throws Exception {
  228. try {
  229. gc.setProgressMonitor(this);
  230. gc.repack();
  231. return valueOf(0);
  232. } catch (IOException e) {
  233. // leave the syncPoint in broken state so any awaiting
  234. // threads and any threads that call await in the future get
  235. // the BrokenBarrierException
  236. Thread.currentThread().interrupt();
  237. try {
  238. syncPoint.await();
  239. } catch (InterruptedException ignored) {
  240. //
  241. }
  242. return valueOf(1);
  243. }
  244. }
  245. }
  246. RevBlob a = tr.blob("a");
  247. tr.lightweightTag("t", a);
  248. ExecutorService pool = Executors.newFixedThreadPool(2);
  249. try {
  250. DoRepack repack1 = new DoRepack();
  251. DoRepack repack2 = new DoRepack();
  252. Future<Integer> result1 = pool.submit(repack1);
  253. Future<Integer> result2 = pool.submit(repack2);
  254. assertEquals(0, result1.get().intValue() + result2.get().intValue());
  255. } finally {
  256. pool.shutdown();
  257. pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
  258. }
  259. }
  260. // GC.prune tests
  261. @Test
  262. public void nonReferencedNonExpiredObject_notPruned() throws Exception {
  263. RevBlob a = tr.blob("a");
  264. gc.setExpire(new Date(lastModified(a)));
  265. gc.prune(Collections.<ObjectId> emptySet());
  266. assertTrue(repo.hasObject(a));
  267. }
  268. @Test
  269. public void nonReferencedExpiredObject_pruned() throws Exception {
  270. RevBlob a = tr.blob("a");
  271. gc.setExpireAgeMillis(0);
  272. fsTick();
  273. gc.prune(Collections.<ObjectId> emptySet());
  274. assertFalse(repo.hasObject(a));
  275. }
  276. @Test
  277. public void nonReferencedExpiredObjectTree_pruned() throws Exception {
  278. RevBlob a = tr.blob("a");
  279. RevTree t = tr.tree(tr.file("a", a));
  280. gc.setExpireAgeMillis(0);
  281. fsTick();
  282. gc.prune(Collections.<ObjectId> emptySet());
  283. assertFalse(repo.hasObject(t));
  284. assertFalse(repo.hasObject(a));
  285. }
  286. @Test
  287. public void nonReferencedObjects_onlyExpiredPruned() throws Exception {
  288. RevBlob a = tr.blob("a");
  289. gc.setExpire(new Date(lastModified(a) + 1));
  290. fsTick();
  291. RevBlob b = tr.blob("b");
  292. gc.prune(Collections.<ObjectId> emptySet());
  293. assertFalse(repo.hasObject(a));
  294. assertTrue(repo.hasObject(b));
  295. }
  296. @Test
  297. public void lightweightTag_objectNotPruned() throws Exception {
  298. RevBlob a = tr.blob("a");
  299. tr.lightweightTag("t", a);
  300. gc.setExpireAgeMillis(0);
  301. fsTick();
  302. gc.prune(Collections.<ObjectId> emptySet());
  303. assertTrue(repo.hasObject(a));
  304. }
  305. @Test
  306. public void annotatedTag_objectNotPruned() throws Exception {
  307. RevBlob a = tr.blob("a");
  308. RevTag t = tr.tag("t", a); // this doesn't create the refs/tags/t ref
  309. tr.lightweightTag("t", t);
  310. gc.setExpireAgeMillis(0);
  311. fsTick();
  312. gc.prune(Collections.<ObjectId> emptySet());
  313. assertTrue(repo.hasObject(t));
  314. assertTrue(repo.hasObject(a));
  315. }
  316. @Test
  317. public void branch_historyNotPruned() throws Exception {
  318. RevCommit tip = commitChain(10);
  319. tr.branch("b").update(tip);
  320. gc.setExpireAgeMillis(0);
  321. fsTick();
  322. gc.prune(Collections.<ObjectId> emptySet());
  323. do {
  324. assertTrue(repo.hasObject(tip));
  325. tr.parseBody(tip);
  326. RevTree t = tip.getTree();
  327. assertTrue(repo.hasObject(t));
  328. assertTrue(repo.hasObject(tr.get(t, "a")));
  329. tip = tip.getParentCount() > 0 ? tip.getParent(0) : null;
  330. } while (tip != null);
  331. }
  332. @Test
  333. public void deleteBranch_historyPruned() throws Exception {
  334. RevCommit tip = commitChain(10);
  335. tr.branch("b").update(tip);
  336. RefUpdate update = repo.updateRef("refs/heads/b");
  337. update.setForceUpdate(true);
  338. update.delete();
  339. gc.setExpireAgeMillis(0);
  340. fsTick();
  341. gc.prune(Collections.<ObjectId> emptySet());
  342. assertTrue(gc.getStatistics().numberOfLooseObjects == 0);
  343. }
  344. @Test
  345. public void deleteMergedBranch_historyNotPruned() throws Exception {
  346. RevCommit parent = tr.commit().create();
  347. RevCommit b1Tip = tr.branch("b1").commit().parent(parent).add("x", "x")
  348. .create();
  349. RevCommit b2Tip = tr.branch("b2").commit().parent(parent).add("y", "y")
  350. .create();
  351. // merge b1Tip and b2Tip and update refs/heads/b1 to the merge commit
  352. Merger merger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
  353. merger.merge(b1Tip, b2Tip);
  354. CommitBuilder cb = tr.commit();
  355. cb.parent(b1Tip).parent(b2Tip);
  356. cb.setTopLevelTree(merger.getResultTreeId());
  357. RevCommit mergeCommit = cb.create();
  358. RefUpdate u = repo.updateRef("refs/heads/b1");
  359. u.setNewObjectId(mergeCommit);
  360. u.update();
  361. RefUpdate update = repo.updateRef("refs/heads/b2");
  362. update.setForceUpdate(true);
  363. update.delete();
  364. gc.setExpireAgeMillis(0);
  365. fsTick();
  366. gc.prune(Collections.<ObjectId> emptySet());
  367. assertTrue(repo.hasObject(b2Tip));
  368. }
  369. @Test
  370. public void testPackAllObjectsInOnePack() throws Exception {
  371. tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
  372. .create();
  373. stats = gc.getStatistics();
  374. assertEquals(4, stats.numberOfLooseObjects);
  375. assertEquals(0, stats.numberOfPackedObjects);
  376. gc.gc();
  377. stats = gc.getStatistics();
  378. assertEquals(0, stats.numberOfLooseObjects);
  379. assertEquals(4, stats.numberOfPackedObjects);
  380. assertEquals(1, stats.numberOfPackFiles);
  381. // Do the gc again and check that it hasn't changed anything
  382. gc.gc();
  383. stats = gc.getStatistics();
  384. assertEquals(0, stats.numberOfLooseObjects);
  385. assertEquals(4, stats.numberOfPackedObjects);
  386. assertEquals(1, stats.numberOfPackFiles);
  387. }
  388. @Test
  389. public void testPackRepoWithCorruptReflog() throws Exception {
  390. // create a reflog entry "0000... 0000... foobar" by doing an initial
  391. // refupdate for HEAD which points to a non-existing ref. The
  392. // All-Projects repo of gerrit instances had such entries
  393. RefUpdate ru = repo.updateRef(Constants.HEAD);
  394. ru.link("refs/to/garbage");
  395. tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
  396. .create();
  397. // make sure HEAD exists
  398. Git.wrap(repo).checkout().setName("refs/heads/master").call();
  399. gc.gc();
  400. }
  401. @Test
  402. public void testKeepFiles() throws Exception {
  403. BranchBuilder bb = tr.branch("refs/heads/master");
  404. bb.commit().add("A", "A").add("B", "B").create();
  405. stats = gc.getStatistics();
  406. assertEquals(4, stats.numberOfLooseObjects);
  407. assertEquals(0, stats.numberOfPackedObjects);
  408. assertEquals(0, stats.numberOfPackFiles);
  409. gc.gc();
  410. stats = gc.getStatistics();
  411. assertEquals(0, stats.numberOfLooseObjects);
  412. assertEquals(4, stats.numberOfPackedObjects);
  413. assertEquals(1, stats.numberOfPackFiles);
  414. Iterator<PackFile> packIt = repo.getObjectDatabase().getPacks()
  415. .iterator();
  416. PackFile singlePack = packIt.next();
  417. assertFalse(packIt.hasNext());
  418. File keepFile = new File(singlePack.getPackFile().getPath() + ".keep");
  419. assertFalse(keepFile.exists());
  420. assertTrue(keepFile.createNewFile());
  421. bb.commit().add("A", "A2").add("B", "B2").create();
  422. stats = gc.getStatistics();
  423. assertEquals(4, stats.numberOfLooseObjects);
  424. assertEquals(4, stats.numberOfPackedObjects);
  425. assertEquals(1, stats.numberOfPackFiles);
  426. gc.gc();
  427. stats = gc.getStatistics();
  428. assertEquals(0, stats.numberOfLooseObjects);
  429. assertEquals(8, stats.numberOfPackedObjects);
  430. assertEquals(2, stats.numberOfPackFiles);
  431. // check that no object is packed twice
  432. Iterator<PackFile> packs = repo.getObjectDatabase().getPacks()
  433. .iterator();
  434. PackIndex ind1 = packs.next().getIndex();
  435. assertEquals(4, ind1.getObjectCount());
  436. PackIndex ind2 = packs.next().getIndex();
  437. assertEquals(4, ind2.getObjectCount());
  438. for (MutableEntry e: ind1)
  439. if (ind2.hasObject(e.toObjectId()))
  440. assertFalse(
  441. "the following object is in both packfiles: "
  442. + e.toObjectId(),
  443. ind2.hasObject(e.toObjectId()));
  444. }
  445. @Test
  446. public void testPackRepoWithNoRefs() throws Exception {
  447. tr.commit().add("A", "A").add("B", "B").create();
  448. stats = gc.getStatistics();
  449. assertEquals(4, stats.numberOfLooseObjects);
  450. assertEquals(0, stats.numberOfPackedObjects);
  451. gc.gc();
  452. stats = gc.getStatistics();
  453. assertEquals(4, stats.numberOfLooseObjects);
  454. assertEquals(0, stats.numberOfPackedObjects);
  455. assertEquals(0, stats.numberOfPackFiles);
  456. }
  457. @Test
  458. public void testPack2Commits() throws Exception {
  459. BranchBuilder bb = tr.branch("refs/heads/master");
  460. bb.commit().add("A", "A").add("B", "B").create();
  461. bb.commit().add("A", "A2").add("B", "B2").create();
  462. stats = gc.getStatistics();
  463. assertEquals(8, stats.numberOfLooseObjects);
  464. assertEquals(0, stats.numberOfPackedObjects);
  465. gc.gc();
  466. stats = gc.getStatistics();
  467. assertEquals(0, stats.numberOfLooseObjects);
  468. assertEquals(8, stats.numberOfPackedObjects);
  469. assertEquals(1, stats.numberOfPackFiles);
  470. }
  471. @Test
  472. public void testPackCommitsAndLooseOne() throws Exception {
  473. BranchBuilder bb = tr.branch("refs/heads/master");
  474. RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
  475. bb.commit().add("A", "A2").add("B", "B2").create();
  476. tr.update("refs/heads/master", first);
  477. stats = gc.getStatistics();
  478. assertEquals(8, stats.numberOfLooseObjects);
  479. assertEquals(0, stats.numberOfPackedObjects);
  480. gc.gc();
  481. stats = gc.getStatistics();
  482. assertEquals(0, stats.numberOfLooseObjects);
  483. assertEquals(8, stats.numberOfPackedObjects);
  484. assertEquals(2, stats.numberOfPackFiles);
  485. }
  486. @Test
  487. public void testNotPackTwice() throws Exception {
  488. BranchBuilder bb = tr.branch("refs/heads/master");
  489. RevCommit first = bb.commit().message("M").add("M", "M").create();
  490. bb.commit().message("B").add("B", "Q").create();
  491. bb.commit().message("A").add("A", "A").create();
  492. RevCommit second = tr.commit().parent(first).message("R").add("R", "Q")
  493. .create();
  494. tr.update("refs/tags/t1", second);
  495. Collection<PackFile> oldPacks = tr.getRepository().getObjectDatabase()
  496. .getPacks();
  497. assertEquals(0, oldPacks.size());
  498. stats = gc.getStatistics();
  499. assertEquals(11, stats.numberOfLooseObjects);
  500. assertEquals(0, stats.numberOfPackedObjects);
  501. gc.setExpireAgeMillis(0);
  502. fsTick();
  503. gc.gc();
  504. stats = gc.getStatistics();
  505. assertEquals(0, stats.numberOfLooseObjects);
  506. Iterator<PackFile> pIt = repo.getObjectDatabase().getPacks().iterator();
  507. long c = pIt.next().getObjectCount();
  508. if (c == 9)
  509. assertEquals(2, pIt.next().getObjectCount());
  510. else {
  511. assertEquals(2, c);
  512. assertEquals(9, pIt.next().getObjectCount());
  513. }
  514. }
  515. @Test
  516. public void testPackCommitsAndLooseOneNoReflog() throws Exception {
  517. BranchBuilder bb = tr.branch("refs/heads/master");
  518. RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
  519. bb.commit().add("A", "A2").add("B", "B2").create();
  520. tr.update("refs/heads/master", first);
  521. stats = gc.getStatistics();
  522. assertEquals(8, stats.numberOfLooseObjects);
  523. assertEquals(0, stats.numberOfPackedObjects);
  524. FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"),
  525. FileUtils.RETRY | FileUtils.SKIP_MISSING);
  526. FileUtils.delete(
  527. new File(repo.getDirectory(), "logs/refs/heads/master"),
  528. FileUtils.RETRY | FileUtils.SKIP_MISSING);
  529. gc.gc();
  530. stats = gc.getStatistics();
  531. assertEquals(4, stats.numberOfLooseObjects);
  532. assertEquals(4, stats.numberOfPackedObjects);
  533. assertEquals(1, stats.numberOfPackFiles);
  534. }
  535. @Test
  536. public void testPackCommitsAndLooseOneWithPruneNow() throws Exception {
  537. BranchBuilder bb = tr.branch("refs/heads/master");
  538. RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
  539. bb.commit().add("A", "A2").add("B", "B2").create();
  540. tr.update("refs/heads/master", first);
  541. stats = gc.getStatistics();
  542. assertEquals(8, stats.numberOfLooseObjects);
  543. assertEquals(0, stats.numberOfPackedObjects);
  544. gc.setExpireAgeMillis(0);
  545. fsTick();
  546. gc.gc();
  547. stats = gc.getStatistics();
  548. assertEquals(0, stats.numberOfLooseObjects);
  549. assertEquals(8, stats.numberOfPackedObjects);
  550. assertEquals(2, stats.numberOfPackFiles);
  551. }
  552. @Test
  553. public void testPackCommitsAndLooseOneWithPruneNowNoReflog()
  554. throws Exception {
  555. BranchBuilder bb = tr.branch("refs/heads/master");
  556. RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
  557. bb.commit().add("A", "A2").add("B", "B2").create();
  558. tr.update("refs/heads/master", first);
  559. stats = gc.getStatistics();
  560. assertEquals(8, stats.numberOfLooseObjects);
  561. assertEquals(0, stats.numberOfPackedObjects);
  562. FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"),
  563. FileUtils.RETRY | FileUtils.SKIP_MISSING);
  564. FileUtils.delete(
  565. new File(repo.getDirectory(), "logs/refs/heads/master"),
  566. FileUtils.RETRY | FileUtils.SKIP_MISSING);
  567. gc.setExpireAgeMillis(0);
  568. gc.gc();
  569. stats = gc.getStatistics();
  570. assertEquals(0, stats.numberOfLooseObjects);
  571. assertEquals(4, stats.numberOfPackedObjects);
  572. assertEquals(1, stats.numberOfPackFiles);
  573. }
  574. @Test
  575. public void testIndexSavesObjects() throws Exception {
  576. BranchBuilder bb = tr.branch("refs/heads/master");
  577. bb.commit().add("A", "A").add("B", "B").create();
  578. bb.commit().add("A", "A2").add("B", "B2").create();
  579. bb.commit().add("A", "A3"); // this new content in index should survive
  580. stats = gc.getStatistics();
  581. assertEquals(9, stats.numberOfLooseObjects);
  582. assertEquals(0, stats.numberOfPackedObjects);
  583. gc.gc();
  584. stats = gc.getStatistics();
  585. assertEquals(1, stats.numberOfLooseObjects);
  586. assertEquals(8, stats.numberOfPackedObjects);
  587. assertEquals(1, stats.numberOfPackFiles);
  588. }
  589. @Test
  590. public void testIndexSavesObjectsWithPruneNow() throws Exception {
  591. BranchBuilder bb = tr.branch("refs/heads/master");
  592. bb.commit().add("A", "A").add("B", "B").create();
  593. bb.commit().add("A", "A2").add("B", "B2").create();
  594. bb.commit().add("A", "A3"); // this new content in index should survive
  595. stats = gc.getStatistics();
  596. assertEquals(9, stats.numberOfLooseObjects);
  597. assertEquals(0, stats.numberOfPackedObjects);
  598. gc.setExpireAgeMillis(0);
  599. fsTick();
  600. gc.gc();
  601. stats = gc.getStatistics();
  602. assertEquals(0, stats.numberOfLooseObjects);
  603. assertEquals(8, stats.numberOfPackedObjects);
  604. assertEquals(1, stats.numberOfPackFiles);
  605. }
  606. @Test
  607. public void testPruneNone() throws Exception {
  608. BranchBuilder bb = tr.branch("refs/heads/master");
  609. bb.commit().add("A", "A").add("B", "B").create();
  610. bb.commit().add("A", "A2").add("B", "B2").create();
  611. new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master")
  612. .delete();
  613. stats = gc.getStatistics();
  614. assertEquals(8, stats.numberOfLooseObjects);
  615. gc.setExpireAgeMillis(0);
  616. fsTick();
  617. gc.prune(Collections.<ObjectId> emptySet());
  618. stats = gc.getStatistics();
  619. assertEquals(8, stats.numberOfLooseObjects);
  620. tr.blob("x");
  621. stats = gc.getStatistics();
  622. assertEquals(9, stats.numberOfLooseObjects);
  623. gc.prune(Collections.<ObjectId> emptySet());
  624. stats = gc.getStatistics();
  625. assertEquals(8, stats.numberOfLooseObjects);
  626. }
  627. /**
  628. * Create a chain of commits of given depth.
  629. * <p>
  630. * Each commit contains one file named "a" containing the index of the
  631. * commit in the chain as its content. The created commit chain is
  632. * referenced from any ref.
  633. * <p>
  634. * A chain of depth = N will create 3*N objects in Gits object database. For
  635. * each depth level three objects are created: the commit object, the
  636. * top-level tree object and a blob for the content of the file "a".
  637. *
  638. * @param depth
  639. * the depth of the commit chain.
  640. * @return the commit that is the tip of the commit chain
  641. * @throws Exception
  642. */
  643. private RevCommit commitChain(int depth) throws Exception {
  644. if (depth <= 0)
  645. throw new IllegalArgumentException("Chain depth must be > 0");
  646. CommitBuilder cb = tr.commit();
  647. RevCommit tip;
  648. do {
  649. --depth;
  650. tip = cb.add("a", "" + depth).message("" + depth).create();
  651. cb = cb.child();
  652. } while (depth > 0);
  653. return tip;
  654. }
  655. private long lastModified(AnyObjectId objectId) {
  656. return repo.getObjectDatabase().fileFor(objectId).lastModified();
  657. }
  658. private static void fsTick() throws InterruptedException, IOException {
  659. RepositoryTestCase.fsTick(null);
  660. }
  661. }