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 24KB

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