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.

PackWriterTest.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. /*
  2. * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> 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.file;
  11. import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
  12. import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES;
  13. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  14. import static org.junit.Assert.assertEquals;
  15. import static org.junit.Assert.assertFalse;
  16. import static org.junit.Assert.assertNotNull;
  17. import static org.junit.Assert.assertTrue;
  18. import static org.junit.Assert.fail;
  19. import static org.mockito.ArgumentMatchers.any;
  20. import static org.mockito.Mockito.doNothing;
  21. import static org.mockito.Mockito.times;
  22. import static org.mockito.Mockito.verify;
  23. import java.io.ByteArrayInputStream;
  24. import java.io.ByteArrayOutputStream;
  25. import java.io.File;
  26. import java.io.FileOutputStream;
  27. import java.io.IOException;
  28. import java.text.ParseException;
  29. import java.time.Duration;
  30. import java.util.ArrayList;
  31. import java.util.Arrays;
  32. import java.util.Collections;
  33. import java.util.HashSet;
  34. import java.util.List;
  35. import java.util.Set;
  36. import org.eclipse.jgit.api.Git;
  37. import org.eclipse.jgit.errors.MissingObjectException;
  38. import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
  39. import org.eclipse.jgit.internal.storage.pack.PackExt;
  40. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  41. import org.eclipse.jgit.junit.JGitTestUtil;
  42. import org.eclipse.jgit.junit.TestRepository;
  43. import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
  44. import org.eclipse.jgit.lib.NullProgressMonitor;
  45. import org.eclipse.jgit.lib.ObjectId;
  46. import org.eclipse.jgit.lib.ObjectIdSet;
  47. import org.eclipse.jgit.lib.ObjectInserter;
  48. import org.eclipse.jgit.lib.Ref;
  49. import org.eclipse.jgit.lib.Repository;
  50. import org.eclipse.jgit.lib.Sets;
  51. import org.eclipse.jgit.revwalk.DepthWalk;
  52. import org.eclipse.jgit.revwalk.ObjectWalk;
  53. import org.eclipse.jgit.revwalk.RevBlob;
  54. import org.eclipse.jgit.revwalk.RevCommit;
  55. import org.eclipse.jgit.revwalk.RevObject;
  56. import org.eclipse.jgit.revwalk.RevWalk;
  57. import org.eclipse.jgit.storage.pack.PackConfig;
  58. import org.eclipse.jgit.storage.pack.PackStatistics;
  59. import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
  60. import org.eclipse.jgit.transport.PackParser;
  61. import org.junit.After;
  62. import org.junit.Before;
  63. import org.junit.Test;
  64. import org.mockito.Mockito;
  65. public class PackWriterTest extends SampleDataRepositoryTestCase {
  66. private static final List<RevObject> EMPTY_LIST_REVS = Collections
  67. .<RevObject> emptyList();
  68. private static final Set<ObjectIdSet> EMPTY_ID_SET = Collections
  69. .<ObjectIdSet> emptySet();
  70. private PackConfig config;
  71. private PackWriter writer;
  72. private ByteArrayOutputStream os;
  73. private Pack pack;
  74. private ObjectInserter inserter;
  75. private FileRepository dst;
  76. private RevBlob contentA;
  77. private RevBlob contentB;
  78. private RevBlob contentC;
  79. private RevBlob contentD;
  80. private RevBlob contentE;
  81. private RevCommit c1;
  82. private RevCommit c2;
  83. private RevCommit c3;
  84. private RevCommit c4;
  85. private RevCommit c5;
  86. @Override
  87. @Before
  88. public void setUp() throws Exception {
  89. super.setUp();
  90. os = new ByteArrayOutputStream();
  91. config = new PackConfig(db);
  92. dst = createBareRepository();
  93. File alt = new File(dst.getObjectDatabase().getDirectory(), INFO_ALTERNATES);
  94. alt.getParentFile().mkdirs();
  95. write(alt, db.getObjectDatabase().getDirectory().getAbsolutePath() + "\n");
  96. }
  97. @Override
  98. @After
  99. public void tearDown() throws Exception {
  100. if (writer != null) {
  101. writer.close();
  102. writer = null;
  103. }
  104. if (inserter != null) {
  105. inserter.close();
  106. inserter = null;
  107. }
  108. super.tearDown();
  109. }
  110. /**
  111. * Test constructor for exceptions, default settings, initialization.
  112. *
  113. * @throws IOException
  114. */
  115. @Test
  116. public void testContructor() throws IOException {
  117. writer = new PackWriter(config, db.newObjectReader());
  118. assertFalse(writer.isDeltaBaseAsOffset());
  119. assertTrue(config.isReuseDeltas());
  120. assertTrue(config.isReuseObjects());
  121. assertEquals(0, writer.getObjectCount());
  122. }
  123. /**
  124. * Change default settings and verify them.
  125. */
  126. @Test
  127. public void testModifySettings() {
  128. config.setReuseDeltas(false);
  129. config.setReuseObjects(false);
  130. config.setDeltaBaseAsOffset(false);
  131. assertFalse(config.isReuseDeltas());
  132. assertFalse(config.isReuseObjects());
  133. assertFalse(config.isDeltaBaseAsOffset());
  134. writer = new PackWriter(config, db.newObjectReader());
  135. writer.setDeltaBaseAsOffset(true);
  136. assertTrue(writer.isDeltaBaseAsOffset());
  137. assertFalse(config.isDeltaBaseAsOffset());
  138. }
  139. /**
  140. * Write empty pack by providing empty sets of interesting/uninteresting
  141. * objects and check for correct format.
  142. *
  143. * @throws IOException
  144. */
  145. @Test
  146. public void testWriteEmptyPack1() throws IOException {
  147. createVerifyOpenPack(NONE, NONE, false, false);
  148. assertEquals(0, writer.getObjectCount());
  149. assertEquals(0, pack.getObjectCount());
  150. assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", writer
  151. .computeName().name());
  152. }
  153. /**
  154. * Write empty pack by providing empty iterator of objects to write and
  155. * check for correct format.
  156. *
  157. * @throws IOException
  158. */
  159. @Test
  160. public void testWriteEmptyPack2() throws IOException {
  161. createVerifyOpenPack(EMPTY_LIST_REVS);
  162. assertEquals(0, writer.getObjectCount());
  163. assertEquals(0, pack.getObjectCount());
  164. }
  165. /**
  166. * Try to pass non-existing object as uninteresting, with non-ignoring
  167. * setting.
  168. *
  169. * @throws IOException
  170. */
  171. @Test
  172. public void testNotIgnoreNonExistingObjects() throws IOException {
  173. final ObjectId nonExisting = ObjectId
  174. .fromString("0000000000000000000000000000000000000001");
  175. try {
  176. createVerifyOpenPack(NONE, haves(nonExisting), false, false);
  177. fail("Should have thrown MissingObjectException");
  178. } catch (MissingObjectException x) {
  179. // expected
  180. }
  181. }
  182. /**
  183. * Try to pass non-existing object as uninteresting, with ignoring setting.
  184. *
  185. * @throws IOException
  186. */
  187. @Test
  188. public void testIgnoreNonExistingObjects() throws IOException {
  189. final ObjectId nonExisting = ObjectId
  190. .fromString("0000000000000000000000000000000000000001");
  191. createVerifyOpenPack(NONE, haves(nonExisting), false, true);
  192. // shouldn't throw anything
  193. }
  194. /**
  195. * Try to pass non-existing object as uninteresting, with ignoring setting.
  196. * Use a repo with bitmap indexes because then PackWriter will use
  197. * PackWriterBitmapWalker which had problems with this situation.
  198. *
  199. * @throws IOException
  200. * @throws ParseException
  201. */
  202. @Test
  203. public void testIgnoreNonExistingObjectsWithBitmaps() throws IOException,
  204. ParseException {
  205. final ObjectId nonExisting = ObjectId
  206. .fromString("0000000000000000000000000000000000000001");
  207. new GC(db).gc();
  208. createVerifyOpenPack(NONE, haves(nonExisting), false, true, true);
  209. // shouldn't throw anything
  210. }
  211. /**
  212. * Create pack basing on only interesting objects, then precisely verify
  213. * content. No delta reuse here.
  214. *
  215. * @throws IOException
  216. */
  217. @Test
  218. public void testWritePack1() throws IOException {
  219. config.setReuseDeltas(false);
  220. writeVerifyPack1();
  221. }
  222. /**
  223. * Test writing pack without object reuse. Pack content/preparation as in
  224. * {@link #testWritePack1()}.
  225. *
  226. * @throws IOException
  227. */
  228. @Test
  229. public void testWritePack1NoObjectReuse() throws IOException {
  230. config.setReuseDeltas(false);
  231. config.setReuseObjects(false);
  232. writeVerifyPack1();
  233. }
  234. /**
  235. * Create pack basing on both interesting and uninteresting objects, then
  236. * precisely verify content. No delta reuse here.
  237. *
  238. * @throws IOException
  239. */
  240. @Test
  241. public void testWritePack2() throws IOException {
  242. writeVerifyPack2(false);
  243. }
  244. /**
  245. * Test pack writing with deltas reuse, delta-base first rule. Pack
  246. * content/preparation as in {@link #testWritePack2()}.
  247. *
  248. * @throws IOException
  249. */
  250. @Test
  251. public void testWritePack2DeltasReuseRefs() throws IOException {
  252. writeVerifyPack2(true);
  253. }
  254. /**
  255. * Test pack writing with delta reuse. Delta bases referred as offsets. Pack
  256. * configuration as in {@link #testWritePack2DeltasReuseRefs()}.
  257. *
  258. * @throws IOException
  259. */
  260. @Test
  261. public void testWritePack2DeltasReuseOffsets() throws IOException {
  262. config.setDeltaBaseAsOffset(true);
  263. writeVerifyPack2(true);
  264. }
  265. /**
  266. * Test pack writing with delta reuse. Raw-data copy (reuse) is made on a
  267. * pack with CRC32 index. Pack configuration as in
  268. * {@link #testWritePack2DeltasReuseRefs()}.
  269. *
  270. * @throws IOException
  271. */
  272. @Test
  273. public void testWritePack2DeltasCRC32Copy() throws IOException {
  274. final File packDir = db.getObjectDatabase().getPackDirectory();
  275. final PackFile crc32Pack = new PackFile(packDir,
  276. "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack");
  277. final PackFile crc32Idx = new PackFile(packDir,
  278. "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx");
  279. copyFile(JGitTestUtil.getTestResourceFile(
  280. "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"),
  281. crc32Idx);
  282. db.openPack(crc32Pack);
  283. writeVerifyPack2(true);
  284. }
  285. /**
  286. * Create pack basing on fixed objects list, then precisely verify content.
  287. * No delta reuse here.
  288. *
  289. * @throws IOException
  290. * @throws MissingObjectException
  291. *
  292. */
  293. @Test
  294. public void testWritePack3() throws MissingObjectException, IOException {
  295. config.setReuseDeltas(false);
  296. final ObjectId forcedOrder[] = new ObjectId[] {
  297. ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
  298. ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
  299. ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
  300. ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
  301. ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") ,
  302. ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
  303. try (RevWalk parser = new RevWalk(db)) {
  304. final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length];
  305. for (int i = 0; i < forcedOrder.length; i++)
  306. forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]);
  307. createVerifyOpenPack(Arrays.asList(forcedOrderRevs));
  308. }
  309. assertEquals(forcedOrder.length, writer.getObjectCount());
  310. verifyObjectsOrder(forcedOrder);
  311. assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer
  312. .computeName().name());
  313. }
  314. /**
  315. * Another pack creation: basing on both interesting and uninteresting
  316. * objects. No delta reuse possible here, as this is a specific case when we
  317. * write only 1 commit, associated with 1 tree, 1 blob.
  318. *
  319. * @throws IOException
  320. */
  321. @Test
  322. public void testWritePack4() throws IOException {
  323. writeVerifyPack4(false);
  324. }
  325. /**
  326. * Test thin pack writing: 1 blob delta base is on objects edge. Pack
  327. * configuration as in {@link #testWritePack4()}.
  328. *
  329. * @throws IOException
  330. */
  331. @Test
  332. public void testWritePack4ThinPack() throws IOException {
  333. writeVerifyPack4(true);
  334. }
  335. /**
  336. * Compare sizes of packs created using {@link #testWritePack2()} and
  337. * {@link #testWritePack2DeltasReuseRefs()}. The pack using deltas should
  338. * be smaller.
  339. *
  340. * @throws Exception
  341. */
  342. @Test
  343. public void testWritePack2SizeDeltasVsNoDeltas() throws Exception {
  344. config.setReuseDeltas(false);
  345. config.setDeltaCompress(false);
  346. testWritePack2();
  347. final long sizePack2NoDeltas = os.size();
  348. tearDown();
  349. setUp();
  350. testWritePack2DeltasReuseRefs();
  351. final long sizePack2DeltasRefs = os.size();
  352. assertTrue(sizePack2NoDeltas > sizePack2DeltasRefs);
  353. }
  354. /**
  355. * Compare sizes of packs created using
  356. * {@link #testWritePack2DeltasReuseRefs()} and
  357. * {@link #testWritePack2DeltasReuseOffsets()}. The pack with delta bases
  358. * written as offsets should be smaller.
  359. *
  360. * @throws Exception
  361. */
  362. @Test
  363. public void testWritePack2SizeOffsetsVsRefs() throws Exception {
  364. testWritePack2DeltasReuseRefs();
  365. final long sizePack2DeltasRefs = os.size();
  366. tearDown();
  367. setUp();
  368. testWritePack2DeltasReuseOffsets();
  369. final long sizePack2DeltasOffsets = os.size();
  370. assertTrue(sizePack2DeltasRefs > sizePack2DeltasOffsets);
  371. }
  372. /**
  373. * Compare sizes of packs created using {@link #testWritePack4()} and
  374. * {@link #testWritePack4ThinPack()}. Obviously, the thin pack should be
  375. * smaller.
  376. *
  377. * @throws Exception
  378. */
  379. @Test
  380. public void testWritePack4SizeThinVsNoThin() throws Exception {
  381. testWritePack4();
  382. final long sizePack4 = os.size();
  383. tearDown();
  384. setUp();
  385. testWritePack4ThinPack();
  386. final long sizePack4Thin = os.size();
  387. assertTrue(sizePack4 > sizePack4Thin);
  388. }
  389. @Test
  390. public void testDeltaStatistics() throws Exception {
  391. config.setDeltaCompress(true);
  392. // TestRepository will close repo
  393. FileRepository repo = createBareRepository();
  394. ArrayList<RevObject> blobs = new ArrayList<>();
  395. try (TestRepository<FileRepository> testRepo = new TestRepository<>(
  396. repo)) {
  397. blobs.add(testRepo.blob(genDeltableData(1000)));
  398. blobs.add(testRepo.blob(genDeltableData(1005)));
  399. try (PackWriter pw = new PackWriter(repo)) {
  400. NullProgressMonitor m = NullProgressMonitor.INSTANCE;
  401. pw.preparePack(blobs.iterator());
  402. pw.writePack(m, m, os);
  403. PackStatistics stats = pw.getStatistics();
  404. assertEquals(1, stats.getTotalDeltas());
  405. assertTrue("Delta bytes not set.",
  406. stats.byObjectType(OBJ_BLOB).getDeltaBytes() > 0);
  407. }
  408. }
  409. }
  410. // Generate consistent junk data for building files that delta well
  411. private String genDeltableData(int length) {
  412. assertTrue("Generated data must have a length > 0", length > 0);
  413. char[] data = {'a', 'b', 'c', '\n'};
  414. StringBuilder builder = new StringBuilder(length);
  415. for (int i = 0; i < length; i++) {
  416. builder.append(data[i % 4]);
  417. }
  418. return builder.toString();
  419. }
  420. @Test
  421. public void testWriteIndex() throws Exception {
  422. config.setIndexVersion(2);
  423. writeVerifyPack4(false);
  424. PackFile packFile = pack.getPackFile();
  425. PackFile indexFile = packFile.create(PackExt.INDEX);
  426. // Validate that IndexPack came up with the right CRC32 value.
  427. final PackIndex idx1 = PackIndex.open(indexFile);
  428. assertTrue(idx1 instanceof PackIndexV2);
  429. assertEquals(0x4743F1E4L, idx1.findCRC32(ObjectId
  430. .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")));
  431. // Validate that an index written by PackWriter is the same.
  432. final File idx2File = new File(indexFile.getAbsolutePath() + ".2");
  433. try (FileOutputStream is = new FileOutputStream(idx2File)) {
  434. writer.writeIndex(is);
  435. }
  436. final PackIndex idx2 = PackIndex.open(idx2File);
  437. assertTrue(idx2 instanceof PackIndexV2);
  438. assertEquals(idx1.getObjectCount(), idx2.getObjectCount());
  439. assertEquals(idx1.getOffset64Count(), idx2.getOffset64Count());
  440. for (int i = 0; i < idx1.getObjectCount(); i++) {
  441. final ObjectId id = idx1.getObjectId(i);
  442. assertEquals(id, idx2.getObjectId(i));
  443. assertEquals(idx1.findOffset(id), idx2.findOffset(id));
  444. assertEquals(idx1.findCRC32(id), idx2.findCRC32(id));
  445. }
  446. }
  447. @Test
  448. public void testExclude() throws Exception {
  449. // TestRepository closes repo
  450. FileRepository repo = createBareRepository();
  451. try (TestRepository<FileRepository> testRepo = new TestRepository<>(
  452. repo)) {
  453. BranchBuilder bb = testRepo.branch("refs/heads/master");
  454. contentA = testRepo.blob("A");
  455. c1 = bb.commit().add("f", contentA).create();
  456. testRepo.getRevWalk().parseHeaders(c1);
  457. PackIndex pf1 = writePack(repo, wants(c1), EMPTY_ID_SET);
  458. assertContent(pf1, Arrays.asList(c1.getId(), c1.getTree().getId(),
  459. contentA.getId()));
  460. contentB = testRepo.blob("B");
  461. c2 = bb.commit().add("f", contentB).create();
  462. testRepo.getRevWalk().parseHeaders(c2);
  463. PackIndex pf2 = writePack(repo, wants(c2),
  464. Sets.of((ObjectIdSet) pf1));
  465. assertContent(pf2, Arrays.asList(c2.getId(), c2.getTree().getId(),
  466. contentB.getId()));
  467. }
  468. }
  469. private static void assertContent(PackIndex pi, List<ObjectId> expected) {
  470. assertEquals("Pack index has wrong size.", expected.size(),
  471. pi.getObjectCount());
  472. for (int i = 0; i < pi.getObjectCount(); i++)
  473. assertTrue(
  474. "Pack index didn't contain the expected id "
  475. + pi.getObjectId(i),
  476. expected.contains(pi.getObjectId(i)));
  477. }
  478. @Test
  479. public void testShallowIsMinimalDepth1() throws Exception {
  480. try (FileRepository repo = setupRepoForShallowFetch()) {
  481. PackIndex idx = writeShallowPack(repo, 1, wants(c2), NONE, NONE);
  482. assertContent(idx, Arrays.asList(c2.getId(), c2.getTree().getId(),
  483. contentA.getId(), contentB.getId()));
  484. // Client already has blobs A and B, verify those are not packed.
  485. idx = writeShallowPack(repo, 1, wants(c5), haves(c2), shallows(c2));
  486. assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
  487. contentC.getId(), contentD.getId(), contentE.getId()));
  488. }
  489. }
  490. @Test
  491. public void testShallowIsMinimalDepth2() throws Exception {
  492. try (FileRepository repo = setupRepoForShallowFetch()) {
  493. PackIndex idx = writeShallowPack(repo, 2, wants(c2), NONE, NONE);
  494. assertContent(idx,
  495. Arrays.asList(c1.getId(), c2.getId(), c1.getTree().getId(),
  496. c2.getTree().getId(), contentA.getId(),
  497. contentB.getId()));
  498. // Client already has blobs A and B, verify those are not packed.
  499. idx = writeShallowPack(repo, 2, wants(c5), haves(c1, c2),
  500. shallows(c1));
  501. assertContent(idx,
  502. Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
  503. c5.getTree().getId(), contentC.getId(),
  504. contentD.getId(), contentE.getId()));
  505. }
  506. }
  507. @Test
  508. public void testShallowFetchShallowParentDepth1() throws Exception {
  509. try (FileRepository repo = setupRepoForShallowFetch()) {
  510. PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
  511. assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
  512. contentA.getId(), contentB.getId(), contentC.getId(),
  513. contentD.getId(), contentE.getId()));
  514. idx = writeShallowPack(repo, 1, wants(c4), haves(c5), shallows(c5));
  515. assertContent(idx, Arrays.asList(c4.getId(), c4.getTree().getId()));
  516. }
  517. }
  518. @Test
  519. public void testShallowFetchShallowParentDepth2() throws Exception {
  520. try (FileRepository repo = setupRepoForShallowFetch()) {
  521. PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
  522. assertContent(idx,
  523. Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
  524. c5.getTree().getId(), contentA.getId(),
  525. contentB.getId(), contentC.getId(),
  526. contentD.getId(), contentE.getId()));
  527. idx = writeShallowPack(repo, 2, wants(c3), haves(c4, c5),
  528. shallows(c4));
  529. assertContent(idx, Arrays.asList(c2.getId(), c3.getId(),
  530. c2.getTree().getId(), c3.getTree().getId()));
  531. }
  532. }
  533. @Test
  534. public void testShallowFetchShallowAncestorDepth1() throws Exception {
  535. try (FileRepository repo = setupRepoForShallowFetch()) {
  536. PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
  537. assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
  538. contentA.getId(), contentB.getId(), contentC.getId(),
  539. contentD.getId(), contentE.getId()));
  540. idx = writeShallowPack(repo, 1, wants(c3), haves(c5), shallows(c5));
  541. assertContent(idx, Arrays.asList(c3.getId(), c3.getTree().getId()));
  542. }
  543. }
  544. @Test
  545. public void testShallowFetchShallowAncestorDepth2() throws Exception {
  546. try (FileRepository repo = setupRepoForShallowFetch()) {
  547. PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
  548. assertContent(idx,
  549. Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
  550. c5.getTree().getId(), contentA.getId(),
  551. contentB.getId(), contentC.getId(),
  552. contentD.getId(), contentE.getId()));
  553. idx = writeShallowPack(repo, 2, wants(c2), haves(c4, c5),
  554. shallows(c4));
  555. assertContent(idx, Arrays.asList(c1.getId(), c2.getId(),
  556. c1.getTree().getId(), c2.getTree().getId()));
  557. }
  558. }
  559. @Test
  560. public void testTotalPackFilesScanWhenSearchForReuseTimeoutNotSet()
  561. throws Exception {
  562. FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
  563. PackWriter mockedPackWriter = Mockito
  564. .spy(new PackWriter(config, fileRepository.newObjectReader()));
  565. doNothing().when(mockedPackWriter).select(any(), any());
  566. try (FileOutputStream packOS = new FileOutputStream(
  567. getPackFileToWrite(fileRepository, mockedPackWriter))) {
  568. mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
  569. NullProgressMonitor.INSTANCE, packOS);
  570. }
  571. long numberOfPackFiles = new GC(fileRepository)
  572. .getStatistics().numberOfPackFiles;
  573. int expectedSelectCalls =
  574. // Objects contained in multiple packfiles * number of packfiles
  575. 2 * (int) numberOfPackFiles +
  576. // Objects in single packfile
  577. 1;
  578. verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
  579. any());
  580. }
  581. @Test
  582. public void testTotalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck()
  583. throws Exception {
  584. FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
  585. PackConfig packConfig = new PackConfig();
  586. packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
  587. PackWriter mockedPackWriter = Mockito.spy(
  588. new PackWriter(packConfig, fileRepository.newObjectReader()));
  589. doNothing().when(mockedPackWriter).select(any(), any());
  590. try (FileOutputStream packOS = new FileOutputStream(
  591. getPackFileToWrite(fileRepository, mockedPackWriter))) {
  592. mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
  593. NullProgressMonitor.INSTANCE, packOS);
  594. }
  595. long numberOfPackFiles = new GC(fileRepository)
  596. .getStatistics().numberOfPackFiles;
  597. int expectedSelectCalls =
  598. // Objects contained in multiple packfiles * number of packfiles
  599. 2 * (int) numberOfPackFiles +
  600. // Objects contained in single packfile
  601. 1;
  602. verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
  603. any());
  604. }
  605. @Test
  606. public void testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck()
  607. throws Exception {
  608. FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
  609. PackConfig packConfig = new PackConfig();
  610. packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
  611. PackWriter mockedPackWriter = Mockito.spy(
  612. new PackWriter(packConfig, fileRepository.newObjectReader()));
  613. mockedPackWriter.enableSearchForReuseTimeout();
  614. doNothing().when(mockedPackWriter).select(any(), any());
  615. try (FileOutputStream packOS = new FileOutputStream(
  616. getPackFileToWrite(fileRepository, mockedPackWriter))) {
  617. mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
  618. NullProgressMonitor.INSTANCE, packOS);
  619. }
  620. int expectedSelectCalls = 3; // Objects in packfiles
  621. verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
  622. any());
  623. }
  624. /**
  625. * Creates objects and packfiles in the following order:
  626. * <ul>
  627. * <li>Creates 2 objects (C1 = commit, T1 = tree)
  628. * <li>Creates packfile P1 (containing C1, T1)
  629. * <li>Creates 1 object (C2 commit)
  630. * <li>Creates packfile P2 (containing C1, T1, C2)
  631. * <li>Create 1 object (C3 commit)
  632. * </ul>
  633. *
  634. * @throws Exception
  635. */
  636. private FileRepository setUpRepoWithMultiplePackfiles() throws Exception {
  637. FileRepository fileRepository = createWorkRepository();
  638. try (Git git = new Git(fileRepository)) {
  639. // Creates 2 objects (C1 = commit, T1 = tree)
  640. git.commit().setMessage("First commit").call();
  641. GC gc = new GC(fileRepository);
  642. gc.setPackExpireAgeMillis(Long.MAX_VALUE);
  643. gc.setExpireAgeMillis(Long.MAX_VALUE);
  644. // Creates packfile P1 (containing C1, T1)
  645. gc.gc();
  646. // Creates 1 object (C2 commit)
  647. git.commit().setMessage("Second commit").call();
  648. // Creates packfile P2 (containing C1, T1, C2)
  649. gc.gc();
  650. // Create 1 object (C3 commit)
  651. git.commit().setMessage("Third commit").call();
  652. }
  653. return fileRepository;
  654. }
  655. private PackFile getPackFileToWrite(FileRepository fileRepository,
  656. PackWriter mockedPackWriter) throws IOException {
  657. File packdir = fileRepository.getObjectDatabase().getPackDirectory();
  658. PackFile packFile = new PackFile(packdir,
  659. mockedPackWriter.computeName(), PackExt.PACK);
  660. Set<ObjectId> all = new HashSet<>();
  661. for (Ref r : fileRepository.getRefDatabase().getRefs()) {
  662. all.add(r.getObjectId());
  663. }
  664. mockedPackWriter.preparePack(NullProgressMonitor.INSTANCE, all,
  665. PackWriter.NONE);
  666. return packFile;
  667. }
  668. private FileRepository setupRepoForShallowFetch() throws Exception {
  669. FileRepository repo = createBareRepository();
  670. // TestRepository will close the repo, but we need to return an open
  671. // one!
  672. repo.incrementOpen();
  673. try (TestRepository<Repository> r = new TestRepository<>(repo)) {
  674. BranchBuilder bb = r.branch("refs/heads/master");
  675. contentA = r.blob("A");
  676. contentB = r.blob("B");
  677. contentC = r.blob("C");
  678. contentD = r.blob("D");
  679. contentE = r.blob("E");
  680. c1 = bb.commit().add("a", contentA).create();
  681. c2 = bb.commit().add("b", contentB).create();
  682. c3 = bb.commit().add("c", contentC).create();
  683. c4 = bb.commit().add("d", contentD).create();
  684. c5 = bb.commit().add("e", contentE).create();
  685. r.getRevWalk().parseHeaders(c5); // fully initialize the tip RevCommit
  686. return repo;
  687. }
  688. }
  689. private static PackIndex writePack(FileRepository repo,
  690. Set<? extends ObjectId> want, Set<ObjectIdSet> excludeObjects)
  691. throws IOException {
  692. try (RevWalk walk = new RevWalk(repo)) {
  693. return writePack(repo, walk, 0, want, NONE, excludeObjects);
  694. }
  695. }
  696. private static PackIndex writeShallowPack(FileRepository repo, int depth,
  697. Set<? extends ObjectId> want, Set<? extends ObjectId> have,
  698. Set<? extends ObjectId> shallow) throws IOException {
  699. // During negotiation, UploadPack would have set up a DepthWalk and
  700. // marked the client's "shallow" commits. Emulate that here.
  701. try (DepthWalk.RevWalk walk = new DepthWalk.RevWalk(repo, depth - 1)) {
  702. walk.assumeShallow(shallow);
  703. return writePack(repo, walk, depth, want, have, EMPTY_ID_SET);
  704. }
  705. }
  706. private static PackIndex writePack(FileRepository repo, RevWalk walk,
  707. int depth, Set<? extends ObjectId> want,
  708. Set<? extends ObjectId> have, Set<ObjectIdSet> excludeObjects)
  709. throws IOException {
  710. try (PackWriter pw = new PackWriter(repo)) {
  711. pw.setDeltaBaseAsOffset(true);
  712. pw.setReuseDeltaCommits(false);
  713. for (ObjectIdSet idx : excludeObjects) {
  714. pw.excludeObjects(idx);
  715. }
  716. if (depth > 0) {
  717. pw.setShallowPack(depth, null);
  718. }
  719. // ow doesn't need to be closed; caller closes walk.
  720. ObjectWalk ow = walk.toObjectWalkWithSameObjects();
  721. pw.preparePack(NullProgressMonitor.INSTANCE, ow, want, have, NONE);
  722. File packdir = repo.getObjectDatabase().getPackDirectory();
  723. PackFile packFile = new PackFile(packdir, pw.computeName(),
  724. PackExt.PACK);
  725. try (FileOutputStream packOS = new FileOutputStream(packFile)) {
  726. pw.writePack(NullProgressMonitor.INSTANCE,
  727. NullProgressMonitor.INSTANCE, packOS);
  728. }
  729. PackFile idxFile = packFile.create(PackExt.INDEX);
  730. try (FileOutputStream idxOS = new FileOutputStream(idxFile)) {
  731. pw.writeIndex(idxOS);
  732. }
  733. return PackIndex.open(idxFile);
  734. }
  735. }
  736. // TODO: testWritePackDeltasCycle()
  737. // TODO: testWritePackDeltasDepth()
  738. private void writeVerifyPack1() throws IOException {
  739. final HashSet<ObjectId> interestings = new HashSet<>();
  740. interestings.add(ObjectId
  741. .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
  742. createVerifyOpenPack(interestings, NONE, false, false);
  743. final ObjectId expectedOrder[] = new ObjectId[] {
  744. ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
  745. ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
  746. ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"),
  747. ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
  748. ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
  749. ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"),
  750. ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"),
  751. ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
  752. assertEquals(expectedOrder.length, writer.getObjectCount());
  753. verifyObjectsOrder(expectedOrder);
  754. assertEquals("34be9032ac282b11fa9babdc2b2a93ca996c9c2f", writer
  755. .computeName().name());
  756. }
  757. private void writeVerifyPack2(boolean deltaReuse) throws IOException {
  758. config.setReuseDeltas(deltaReuse);
  759. final HashSet<ObjectId> interestings = new HashSet<>();
  760. interestings.add(ObjectId
  761. .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
  762. final HashSet<ObjectId> uninterestings = new HashSet<>();
  763. uninterestings.add(ObjectId
  764. .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"));
  765. createVerifyOpenPack(interestings, uninterestings, false, false);
  766. final ObjectId expectedOrder[] = new ObjectId[] {
  767. ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
  768. ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
  769. ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
  770. ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
  771. ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") ,
  772. ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
  773. if (!config.isReuseDeltas() && !config.isDeltaCompress()) {
  774. // If no deltas are in the file the final two entries swap places.
  775. swap(expectedOrder, 4, 5);
  776. }
  777. assertEquals(expectedOrder.length, writer.getObjectCount());
  778. verifyObjectsOrder(expectedOrder);
  779. assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer
  780. .computeName().name());
  781. }
  782. private static void swap(ObjectId[] arr, int a, int b) {
  783. ObjectId tmp = arr[a];
  784. arr[a] = arr[b];
  785. arr[b] = tmp;
  786. }
  787. private void writeVerifyPack4(final boolean thin) throws IOException {
  788. final HashSet<ObjectId> interestings = new HashSet<>();
  789. interestings.add(ObjectId
  790. .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
  791. final HashSet<ObjectId> uninterestings = new HashSet<>();
  792. uninterestings.add(ObjectId
  793. .fromString("c59759f143fb1fe21c197981df75a7ee00290799"));
  794. createVerifyOpenPack(interestings, uninterestings, thin, false);
  795. final ObjectId writtenObjects[] = new ObjectId[] {
  796. ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
  797. ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
  798. ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
  799. assertEquals(writtenObjects.length, writer.getObjectCount());
  800. ObjectId expectedObjects[];
  801. if (thin) {
  802. expectedObjects = new ObjectId[4];
  803. System.arraycopy(writtenObjects, 0, expectedObjects, 0,
  804. writtenObjects.length);
  805. expectedObjects[3] = ObjectId
  806. .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3");
  807. } else {
  808. expectedObjects = writtenObjects;
  809. }
  810. verifyObjectsOrder(expectedObjects);
  811. assertEquals("cded4b74176b4456afa456768b2b5aafb41c44fc", writer
  812. .computeName().name());
  813. }
  814. private void createVerifyOpenPack(final Set<ObjectId> interestings,
  815. final Set<ObjectId> uninterestings, final boolean thin,
  816. final boolean ignoreMissingUninteresting)
  817. throws MissingObjectException, IOException {
  818. createVerifyOpenPack(interestings, uninterestings, thin,
  819. ignoreMissingUninteresting, false);
  820. }
  821. private void createVerifyOpenPack(final Set<ObjectId> interestings,
  822. final Set<ObjectId> uninterestings, final boolean thin,
  823. final boolean ignoreMissingUninteresting, boolean useBitmaps)
  824. throws MissingObjectException, IOException {
  825. NullProgressMonitor m = NullProgressMonitor.INSTANCE;
  826. writer = new PackWriter(config, db.newObjectReader());
  827. writer.setUseBitmaps(useBitmaps);
  828. writer.setThin(thin);
  829. writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting);
  830. writer.preparePack(m, interestings, uninterestings);
  831. writer.writePack(m, m, os);
  832. writer.close();
  833. verifyOpenPack(thin);
  834. }
  835. private void createVerifyOpenPack(List<RevObject> objectSource)
  836. throws MissingObjectException, IOException {
  837. NullProgressMonitor m = NullProgressMonitor.INSTANCE;
  838. writer = new PackWriter(config, db.newObjectReader());
  839. writer.preparePack(objectSource.iterator());
  840. assertEquals(objectSource.size(), writer.getObjectCount());
  841. writer.writePack(m, m, os);
  842. writer.close();
  843. verifyOpenPack(false);
  844. }
  845. private void verifyOpenPack(boolean thin) throws IOException {
  846. final byte[] packData = os.toByteArray();
  847. if (thin) {
  848. PackParser p = index(packData);
  849. try {
  850. p.parse(NullProgressMonitor.INSTANCE);
  851. fail("indexer should grumble about missing object");
  852. } catch (IOException x) {
  853. // expected
  854. }
  855. }
  856. ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(packData);
  857. p.setKeepEmpty(true);
  858. p.setAllowThin(thin);
  859. p.setIndexVersion(2);
  860. p.parse(NullProgressMonitor.INSTANCE);
  861. pack = p.getPack();
  862. assertNotNull("have PackFile after parsing", pack);
  863. }
  864. private PackParser index(byte[] packData) throws IOException {
  865. if (inserter == null)
  866. inserter = dst.newObjectInserter();
  867. return inserter.newPackParser(new ByteArrayInputStream(packData));
  868. }
  869. private void verifyObjectsOrder(ObjectId objectsOrder[]) {
  870. final List<PackIndex.MutableEntry> entries = new ArrayList<>();
  871. for (MutableEntry me : pack) {
  872. entries.add(me.cloneEntry());
  873. }
  874. Collections.sort(entries, (MutableEntry o1, MutableEntry o2) -> Long
  875. .signum(o1.getOffset() - o2.getOffset()));
  876. int i = 0;
  877. for (MutableEntry me : entries) {
  878. assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId());
  879. }
  880. }
  881. private static Set<ObjectId> haves(ObjectId... objects) {
  882. return Sets.of(objects);
  883. }
  884. private static Set<ObjectId> wants(ObjectId... objects) {
  885. return Sets.of(objects);
  886. }
  887. private static Set<ObjectId> shallows(ObjectId... objects) {
  888. return Sets.of(objects);
  889. }
  890. }