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

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