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.

PackInserterTest.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /*
  2. * Copyright (C) 2017, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.internal.storage.file;
  44. import static java.util.Comparator.comparing;
  45. import static java.util.stream.Collectors.toList;
  46. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  47. import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
  48. import static org.hamcrest.MatcherAssert.assertThat;
  49. import static org.hamcrest.Matchers.greaterThan;
  50. import static org.hamcrest.Matchers.lessThan;
  51. import static org.junit.Assert.assertArrayEquals;
  52. import static org.junit.Assert.assertEquals;
  53. import static org.junit.Assert.assertNotEquals;
  54. import static org.junit.Assert.fail;
  55. import java.io.ByteArrayInputStream;
  56. import java.io.File;
  57. import java.io.IOException;
  58. import java.nio.file.FileVisitResult;
  59. import java.nio.file.Files;
  60. import java.nio.file.Path;
  61. import java.nio.file.SimpleFileVisitor;
  62. import java.nio.file.attribute.BasicFileAttributes;
  63. import java.util.ArrayList;
  64. import java.util.Collection;
  65. import java.util.List;
  66. import java.util.Random;
  67. import java.util.function.Predicate;
  68. import java.util.regex.Matcher;
  69. import java.util.regex.Pattern;
  70. import org.eclipse.jgit.dircache.DirCache;
  71. import org.eclipse.jgit.dircache.DirCacheBuilder;
  72. import org.eclipse.jgit.dircache.DirCacheEntry;
  73. import org.eclipse.jgit.errors.MissingObjectException;
  74. import org.eclipse.jgit.junit.RepositoryTestCase;
  75. import org.eclipse.jgit.lib.CommitBuilder;
  76. import org.eclipse.jgit.lib.Constants;
  77. import org.eclipse.jgit.lib.FileMode;
  78. import org.eclipse.jgit.lib.ObjectId;
  79. import org.eclipse.jgit.lib.ObjectLoader;
  80. import org.eclipse.jgit.lib.ObjectReader;
  81. import org.eclipse.jgit.lib.ObjectStream;
  82. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  83. import org.eclipse.jgit.treewalk.CanonicalTreeParser;
  84. import org.eclipse.jgit.util.IO;
  85. import org.junit.After;
  86. import org.junit.Before;
  87. import org.junit.Test;
  88. @SuppressWarnings("boxing")
  89. public class PackInserterTest extends RepositoryTestCase {
  90. private WindowCacheConfig origWindowCacheConfig;
  91. private static final Random random = new Random(0);
  92. @Before
  93. public void setWindowCacheConfig() {
  94. origWindowCacheConfig = new WindowCacheConfig();
  95. origWindowCacheConfig.install();
  96. }
  97. @After
  98. public void resetWindowCacheConfig() {
  99. origWindowCacheConfig.install();
  100. }
  101. @Before
  102. public void emptyAtSetUp() throws Exception {
  103. assertEquals(0, listPacks().size());
  104. assertNoObjects();
  105. }
  106. @Test
  107. public void noFlush() throws Exception {
  108. try (PackInserter ins = newInserter()) {
  109. ins.insert(OBJ_BLOB, Constants.encode("foo contents"));
  110. // No flush.
  111. }
  112. assertNoObjects();
  113. }
  114. @Test
  115. public void flushEmptyPack() throws Exception {
  116. try (PackInserter ins = newInserter()) {
  117. ins.flush();
  118. }
  119. assertNoObjects();
  120. }
  121. @Test
  122. public void singlePack() throws Exception {
  123. ObjectId blobId;
  124. byte[] blob = Constants.encode("foo contents");
  125. ObjectId treeId;
  126. ObjectId commitId;
  127. byte[] commit;
  128. try (PackInserter ins = newInserter()) {
  129. blobId = ins.insert(OBJ_BLOB, blob);
  130. DirCache dc = DirCache.newInCore();
  131. DirCacheBuilder b = dc.builder();
  132. DirCacheEntry dce = new DirCacheEntry("foo");
  133. dce.setFileMode(FileMode.REGULAR_FILE);
  134. dce.setObjectId(blobId);
  135. b.add(dce);
  136. b.finish();
  137. treeId = dc.writeTree(ins);
  138. CommitBuilder cb = new CommitBuilder();
  139. cb.setTreeId(treeId);
  140. cb.setAuthor(author);
  141. cb.setCommitter(committer);
  142. cb.setMessage("Commit message");
  143. commit = cb.toByteArray();
  144. commitId = ins.insert(cb);
  145. ins.flush();
  146. }
  147. assertPacksOnly();
  148. List<Pack> packs = listPacks();
  149. assertEquals(1, packs.size());
  150. assertEquals(3, packs.get(0).getObjectCount());
  151. try (ObjectReader reader = db.newObjectReader()) {
  152. assertBlob(reader, blobId, blob);
  153. CanonicalTreeParser treeParser =
  154. new CanonicalTreeParser(null, reader, treeId);
  155. assertEquals("foo", treeParser.getEntryPathString());
  156. assertEquals(blobId, treeParser.getEntryObjectId());
  157. ObjectLoader commitLoader = reader.open(commitId);
  158. assertEquals(OBJ_COMMIT, commitLoader.getType());
  159. assertArrayEquals(commit, commitLoader.getBytes());
  160. }
  161. }
  162. @Test
  163. public void multiplePacks() throws Exception {
  164. ObjectId blobId1;
  165. ObjectId blobId2;
  166. byte[] blob1 = Constants.encode("blob1");
  167. byte[] blob2 = Constants.encode("blob2");
  168. try (PackInserter ins = newInserter()) {
  169. blobId1 = ins.insert(OBJ_BLOB, blob1);
  170. ins.flush();
  171. blobId2 = ins.insert(OBJ_BLOB, blob2);
  172. ins.flush();
  173. }
  174. assertPacksOnly();
  175. List<Pack> packs = listPacks();
  176. assertEquals(2, packs.size());
  177. assertEquals(1, packs.get(0).getObjectCount());
  178. assertEquals(1, packs.get(1).getObjectCount());
  179. try (ObjectReader reader = db.newObjectReader()) {
  180. assertBlob(reader, blobId1, blob1);
  181. assertBlob(reader, blobId2, blob2);
  182. }
  183. }
  184. @Test
  185. public void largeBlob() throws Exception {
  186. ObjectId blobId;
  187. byte[] blob = newLargeBlob();
  188. try (PackInserter ins = newInserter()) {
  189. assertThat(blob.length, greaterThan(ins.getBufferSize()));
  190. blobId =
  191. ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob));
  192. ins.flush();
  193. }
  194. assertPacksOnly();
  195. Collection<Pack> packs = listPacks();
  196. assertEquals(1, packs.size());
  197. Pack p = packs.iterator().next();
  198. assertEquals(1, p.getObjectCount());
  199. try (ObjectReader reader = db.newObjectReader()) {
  200. assertBlob(reader, blobId, blob);
  201. }
  202. }
  203. @Test
  204. public void overwriteExistingPack() throws Exception {
  205. ObjectId blobId;
  206. byte[] blob = Constants.encode("foo contents");
  207. try (PackInserter ins = newInserter()) {
  208. blobId = ins.insert(OBJ_BLOB, blob);
  209. ins.flush();
  210. }
  211. assertPacksOnly();
  212. List<Pack> packs = listPacks();
  213. assertEquals(1, packs.size());
  214. Pack pack = packs.get(0);
  215. assertEquals(1, pack.getObjectCount());
  216. String inode = getInode(pack.getPackFile());
  217. try (PackInserter ins = newInserter()) {
  218. ins.checkExisting(false);
  219. assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
  220. ins.flush();
  221. }
  222. assertPacksOnly();
  223. packs = listPacks();
  224. assertEquals(1, packs.size());
  225. pack = packs.get(0);
  226. assertEquals(1, pack.getObjectCount());
  227. if (inode != null) {
  228. // Old file was overwritten with new file, although objects were
  229. // equivalent.
  230. assertNotEquals(inode, getInode(pack.getPackFile()));
  231. }
  232. }
  233. @Test
  234. public void checkExisting() throws Exception {
  235. ObjectId blobId;
  236. byte[] blob = Constants.encode("foo contents");
  237. try (PackInserter ins = newInserter()) {
  238. blobId = ins.insert(OBJ_BLOB, blob);
  239. ins.insert(OBJ_BLOB, Constants.encode("another blob"));
  240. ins.flush();
  241. }
  242. assertPacksOnly();
  243. assertEquals(1, listPacks().size());
  244. try (PackInserter ins = newInserter()) {
  245. assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
  246. ins.flush();
  247. }
  248. assertPacksOnly();
  249. assertEquals(1, listPacks().size());
  250. try (PackInserter ins = newInserter()) {
  251. ins.checkExisting(false);
  252. assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
  253. ins.flush();
  254. }
  255. assertPacksOnly();
  256. assertEquals(2, listPacks().size());
  257. try (ObjectReader reader = db.newObjectReader()) {
  258. assertBlob(reader, blobId, blob);
  259. }
  260. }
  261. @Test
  262. public void insertSmallInputStreamRespectsCheckExisting() throws Exception {
  263. ObjectId blobId;
  264. byte[] blob = Constants.encode("foo contents");
  265. try (PackInserter ins = newInserter()) {
  266. assertThat(blob.length, lessThan(ins.getBufferSize()));
  267. blobId = ins.insert(OBJ_BLOB, blob);
  268. ins.insert(OBJ_BLOB, Constants.encode("another blob"));
  269. ins.flush();
  270. }
  271. assertPacksOnly();
  272. assertEquals(1, listPacks().size());
  273. try (PackInserter ins = newInserter()) {
  274. assertEquals(blobId,
  275. ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob)));
  276. ins.flush();
  277. }
  278. assertPacksOnly();
  279. assertEquals(1, listPacks().size());
  280. }
  281. @Test
  282. public void insertLargeInputStreamBypassesCheckExisting() throws Exception {
  283. ObjectId blobId;
  284. byte[] blob = newLargeBlob();
  285. try (PackInserter ins = newInserter()) {
  286. assertThat(blob.length, greaterThan(ins.getBufferSize()));
  287. blobId = ins.insert(OBJ_BLOB, blob);
  288. ins.insert(OBJ_BLOB, Constants.encode("another blob"));
  289. ins.flush();
  290. }
  291. assertPacksOnly();
  292. assertEquals(1, listPacks().size());
  293. try (PackInserter ins = newInserter()) {
  294. assertEquals(blobId,
  295. ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob)));
  296. ins.flush();
  297. }
  298. assertPacksOnly();
  299. assertEquals(2, listPacks().size());
  300. }
  301. @Test
  302. public void readBackSmallFiles() throws Exception {
  303. ObjectId blobId1;
  304. ObjectId blobId2;
  305. ObjectId blobId3;
  306. byte[] blob1 = Constants.encode("blob1");
  307. byte[] blob2 = Constants.encode("blob2");
  308. byte[] blob3 = Constants.encode("blob3");
  309. try (PackInserter ins = newInserter()) {
  310. assertThat(blob1.length, lessThan(ins.getBufferSize()));
  311. blobId1 = ins.insert(OBJ_BLOB, blob1);
  312. try (ObjectReader reader = ins.newReader()) {
  313. assertBlob(reader, blobId1, blob1);
  314. }
  315. // Read-back should not mess up the file pointer.
  316. blobId2 = ins.insert(OBJ_BLOB, blob2);
  317. ins.flush();
  318. blobId3 = ins.insert(OBJ_BLOB, blob3);
  319. }
  320. assertPacksOnly();
  321. List<Pack> packs = listPacks();
  322. assertEquals(1, packs.size());
  323. assertEquals(2, packs.get(0).getObjectCount());
  324. try (ObjectReader reader = db.newObjectReader()) {
  325. assertBlob(reader, blobId1, blob1);
  326. assertBlob(reader, blobId2, blob2);
  327. try {
  328. reader.open(blobId3);
  329. fail("Expected MissingObjectException");
  330. } catch (MissingObjectException expected) {
  331. // Expected.
  332. }
  333. }
  334. }
  335. @Test
  336. public void readBackLargeFile() throws Exception {
  337. ObjectId blobId;
  338. byte[] blob = newLargeBlob();
  339. WindowCacheConfig wcc = new WindowCacheConfig();
  340. wcc.setStreamFileThreshold(1024);
  341. wcc.install();
  342. try (ObjectReader reader = db.newObjectReader()) {
  343. assertThat(blob.length, greaterThan(reader.getStreamFileThreshold()));
  344. }
  345. try (PackInserter ins = newInserter()) {
  346. blobId = ins.insert(OBJ_BLOB, blob);
  347. try (ObjectReader reader = ins.newReader()) {
  348. // Double-check threshold is propagated.
  349. assertThat(blob.length, greaterThan(reader.getStreamFileThreshold()));
  350. assertBlob(reader, blobId, blob);
  351. }
  352. }
  353. assertPacksOnly();
  354. // Pack was streamed out to disk and read back from the temp file, but
  355. // ultimately rolled back and deleted.
  356. assertEquals(0, listPacks().size());
  357. try (ObjectReader reader = db.newObjectReader()) {
  358. try {
  359. reader.open(blobId);
  360. fail("Expected MissingObjectException");
  361. } catch (MissingObjectException expected) {
  362. // Expected.
  363. }
  364. }
  365. }
  366. @Test
  367. public void readBackFallsBackToRepo() throws Exception {
  368. ObjectId blobId;
  369. byte[] blob = Constants.encode("foo contents");
  370. try (PackInserter ins = newInserter()) {
  371. assertThat(blob.length, lessThan(ins.getBufferSize()));
  372. blobId = ins.insert(OBJ_BLOB, blob);
  373. ins.flush();
  374. }
  375. try (PackInserter ins = newInserter();
  376. ObjectReader reader = ins.newReader()) {
  377. assertBlob(reader, blobId, blob);
  378. }
  379. }
  380. @Test
  381. public void readBackSmallObjectBeforeLargeObject() throws Exception {
  382. WindowCacheConfig wcc = new WindowCacheConfig();
  383. wcc.setStreamFileThreshold(1024);
  384. wcc.install();
  385. ObjectId blobId1;
  386. ObjectId blobId2;
  387. ObjectId largeId;
  388. byte[] blob1 = Constants.encode("blob1");
  389. byte[] blob2 = Constants.encode("blob2");
  390. byte[] largeBlob = newLargeBlob();
  391. try (PackInserter ins = newInserter()) {
  392. assertThat(blob1.length, lessThan(ins.getBufferSize()));
  393. assertThat(largeBlob.length, greaterThan(ins.getBufferSize()));
  394. blobId1 = ins.insert(OBJ_BLOB, blob1);
  395. largeId = ins.insert(OBJ_BLOB, largeBlob);
  396. try (ObjectReader reader = ins.newReader()) {
  397. // A previous bug did not reset the file pointer to EOF after reading
  398. // back. We need to seek to something further back than a full buffer,
  399. // since the read-back code eagerly reads a full buffer's worth of data
  400. // from the file to pass to the inflater. If we seeked back just a small
  401. // amount, this step would consume the rest of the file, so the file
  402. // pointer would coincidentally end up back at EOF, hiding the bug.
  403. assertBlob(reader, blobId1, blob1);
  404. }
  405. blobId2 = ins.insert(OBJ_BLOB, blob2);
  406. try (ObjectReader reader = ins.newReader()) {
  407. assertBlob(reader, blobId1, blob1);
  408. assertBlob(reader, blobId2, blob2);
  409. assertBlob(reader, largeId, largeBlob);
  410. }
  411. ins.flush();
  412. }
  413. try (ObjectReader reader = db.newObjectReader()) {
  414. assertBlob(reader, blobId1, blob1);
  415. assertBlob(reader, blobId2, blob2);
  416. assertBlob(reader, largeId, largeBlob);
  417. }
  418. }
  419. private List<Pack> listPacks() throws Exception {
  420. List<Pack> fromOpenDb = listPacks(db);
  421. List<Pack> reopened;
  422. try (FileRepository db2 = new FileRepository(db.getDirectory())) {
  423. reopened = listPacks(db2);
  424. }
  425. assertEquals(fromOpenDb.size(), reopened.size());
  426. for (int i = 0 ; i < fromOpenDb.size(); i++) {
  427. Pack a = fromOpenDb.get(i);
  428. Pack b = reopened.get(i);
  429. assertEquals(a.getPackName(), b.getPackName());
  430. assertEquals(
  431. a.getPackFile().getAbsolutePath(), b.getPackFile().getAbsolutePath());
  432. assertEquals(a.getObjectCount(), b.getObjectCount());
  433. a.getObjectCount();
  434. }
  435. return fromOpenDb;
  436. }
  437. private static List<Pack> listPacks(FileRepository db) throws Exception {
  438. return db.getObjectDatabase().getPacks().stream()
  439. .sorted(comparing(Pack::getPackName)).collect(toList());
  440. }
  441. private PackInserter newInserter() {
  442. return db.getObjectDatabase().newPackInserter();
  443. }
  444. private static byte[] newLargeBlob() {
  445. byte[] blob = new byte[10240];
  446. random.nextBytes(blob);
  447. return blob;
  448. }
  449. private static String getInode(File f) throws Exception {
  450. BasicFileAttributes attrs = Files.readAttributes(
  451. f.toPath(), BasicFileAttributes.class);
  452. Object k = attrs.fileKey();
  453. if (k == null) {
  454. return null;
  455. }
  456. Pattern p = Pattern.compile("^\\(dev=[^,]*,ino=(\\d+)\\)$");
  457. Matcher m = p.matcher(k.toString());
  458. return m.matches() ? m.group(1) : null;
  459. }
  460. private static void assertBlob(ObjectReader reader, ObjectId id,
  461. byte[] expected) throws Exception {
  462. ObjectLoader loader = reader.open(id);
  463. assertEquals(OBJ_BLOB, loader.getType());
  464. assertEquals(expected.length, loader.getSize());
  465. try (ObjectStream s = loader.openStream()) {
  466. int n = (int) s.getSize();
  467. byte[] actual = new byte[n];
  468. assertEquals(n, IO.readFully(s, actual, 0));
  469. assertArrayEquals(expected, actual);
  470. }
  471. }
  472. private void assertPacksOnly() throws Exception {
  473. new BadFileCollector(f -> !f.endsWith(".pack") && !f.endsWith(".idx"))
  474. .assertNoBadFiles(db.getObjectDatabase().getDirectory());
  475. }
  476. private void assertNoObjects() throws Exception {
  477. new BadFileCollector(f -> true)
  478. .assertNoBadFiles(db.getObjectDatabase().getDirectory());
  479. }
  480. private static class BadFileCollector extends SimpleFileVisitor<Path> {
  481. private final Predicate<String> badName;
  482. private List<String> bad;
  483. BadFileCollector(Predicate<String> badName) {
  484. this.badName = badName;
  485. }
  486. void assertNoBadFiles(File f) throws IOException {
  487. bad = new ArrayList<>();
  488. Files.walkFileTree(f.toPath(), this);
  489. if (!bad.isEmpty()) {
  490. fail("unexpected files in object directory: " + bad);
  491. }
  492. }
  493. @Override
  494. public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
  495. Path fileName = file.getFileName();
  496. if (fileName != null) {
  497. String name = fileName.toString();
  498. if (!attrs.isDirectory() && badName.test(name)) {
  499. bad.add(name);
  500. }
  501. }
  502. return FileVisitResult.CONTINUE;
  503. }
  504. }
  505. }