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

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