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.

ConcurrentRepackTest.java 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /*
  2. * Copyright (C) 2009-2010, Google Inc.
  3. * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.internal.storage.file;
  12. import static org.junit.Assert.assertArrayEquals;
  13. import static org.junit.Assert.assertEquals;
  14. import static org.junit.Assert.assertFalse;
  15. import static org.junit.Assert.assertNotNull;
  16. import static org.junit.Assert.assertNotSame;
  17. import static org.junit.Assert.fail;
  18. import java.io.BufferedOutputStream;
  19. import java.io.File;
  20. import java.io.FileOutputStream;
  21. import java.io.IOException;
  22. import java.io.OutputStream;
  23. import java.time.Instant;
  24. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  25. import org.eclipse.jgit.errors.MissingObjectException;
  26. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  27. import org.eclipse.jgit.junit.RepositoryTestCase;
  28. import org.eclipse.jgit.lib.AnyObjectId;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.NullProgressMonitor;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.ObjectInserter;
  33. import org.eclipse.jgit.lib.ObjectLoader;
  34. import org.eclipse.jgit.lib.Repository;
  35. import org.eclipse.jgit.revwalk.RevObject;
  36. import org.eclipse.jgit.revwalk.RevWalk;
  37. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  38. import org.eclipse.jgit.util.FS;
  39. import org.eclipse.jgit.util.FileUtils;
  40. import org.junit.After;
  41. import org.junit.Before;
  42. import org.junit.Test;
  43. public class ConcurrentRepackTest extends RepositoryTestCase {
  44. @Override
  45. @Before
  46. public void setUp() throws Exception {
  47. WindowCacheConfig windowCacheConfig = new WindowCacheConfig();
  48. windowCacheConfig.setPackedGitOpenFiles(1);
  49. windowCacheConfig.install();
  50. super.setUp();
  51. }
  52. @Override
  53. @After
  54. public void tearDown() throws Exception {
  55. super.tearDown();
  56. new WindowCacheConfig().install();
  57. }
  58. @Test
  59. public void testObjectInNewPack() throws IncorrectObjectTypeException,
  60. IOException {
  61. // Create a new object in a new pack, and test that it is present.
  62. //
  63. final Repository eden = createBareRepository();
  64. final RevObject o1 = writeBlob(eden, "o1");
  65. pack(eden, o1);
  66. assertEquals(o1.name(), parse(o1).name());
  67. }
  68. @Test
  69. public void testObjectMovedToNewPack1()
  70. throws IncorrectObjectTypeException, IOException {
  71. // Create an object and pack it. Then remove that pack and put the
  72. // object into a different pack file, with some other object. We
  73. // still should be able to access the objects.
  74. //
  75. final Repository eden = createBareRepository();
  76. final RevObject o1 = writeBlob(eden, "o1");
  77. final File[] out1 = pack(eden, o1);
  78. assertEquals(o1.name(), parse(o1).name());
  79. final RevObject o2 = writeBlob(eden, "o2");
  80. pack(eden, o2, o1);
  81. // Force close, and then delete, the old pack.
  82. //
  83. whackCache();
  84. delete(out1);
  85. // Now here is the interesting thing. Will git figure the new
  86. // object exists in the new pack, and not the old one.
  87. //
  88. assertEquals(o2.name(), parse(o2).name());
  89. assertEquals(o1.name(), parse(o1).name());
  90. }
  91. @Test
  92. public void testObjectMovedWithinPack()
  93. throws IncorrectObjectTypeException, IOException {
  94. // Create an object and pack it.
  95. //
  96. final Repository eden = createBareRepository();
  97. final RevObject o1 = writeBlob(eden, "o1");
  98. final File[] out1 = pack(eden, o1);
  99. assertEquals(o1.name(), parse(o1).name());
  100. // Force close the old pack.
  101. //
  102. whackCache();
  103. // Now overwrite the old pack in place. This method of creating a
  104. // different pack under the same file name is partially broken. We
  105. // should also have a different file name because the list of objects
  106. // within the pack has been modified.
  107. //
  108. final RevObject o2 = writeBlob(eden, "o2");
  109. try (PackWriter pw = new PackWriter(eden)) {
  110. pw.addObject(o2);
  111. pw.addObject(o1);
  112. write(out1, pw);
  113. }
  114. // Try the old name, then the new name. The old name should cause the
  115. // pack to reload when it opens and the index and pack mismatch.
  116. //
  117. assertEquals(o1.name(), parse(o1).name());
  118. assertEquals(o2.name(), parse(o2).name());
  119. }
  120. @Test
  121. public void testObjectMovedToNewPack2()
  122. throws IncorrectObjectTypeException, IOException {
  123. // Create an object and pack it. Then remove that pack and put the
  124. // object into a different pack file, with some other object. We
  125. // still should be able to access the objects.
  126. //
  127. final Repository eden = createBareRepository();
  128. final RevObject o1 = writeBlob(eden, "o1");
  129. final File[] out1 = pack(eden, o1);
  130. assertEquals(o1.name(), parse(o1).name());
  131. final ObjectLoader load1 = db.open(o1, Constants.OBJ_BLOB);
  132. assertNotNull(load1);
  133. final RevObject o2 = writeBlob(eden, "o2");
  134. pack(eden, o2, o1);
  135. // Force close, and then delete, the old pack.
  136. //
  137. whackCache();
  138. delete(out1);
  139. // Now here is the interesting thing... can the loader we made
  140. // earlier still resolve the object, even though its underlying
  141. // pack is gone, but the object still exists.
  142. //
  143. final ObjectLoader load2 = db.open(o1, Constants.OBJ_BLOB);
  144. assertNotNull(load2);
  145. assertNotSame(load1, load2);
  146. final byte[] data2 = load2.getCachedBytes();
  147. final byte[] data1 = load1.getCachedBytes();
  148. assertNotNull(data2);
  149. assertNotNull(data1);
  150. assertNotSame(data1, data2); // cache should be per-pack, not per object
  151. assertArrayEquals(data1, data2);
  152. assertEquals(load2.getType(), load1.getType());
  153. }
  154. private static void whackCache() {
  155. final WindowCacheConfig config = new WindowCacheConfig();
  156. config.setPackedGitOpenFiles(1);
  157. config.install();
  158. }
  159. private RevObject parse(AnyObjectId id)
  160. throws MissingObjectException, IOException {
  161. try (RevWalk rw = new RevWalk(db)) {
  162. return rw.parseAny(id);
  163. }
  164. }
  165. private File[] pack(Repository src, RevObject... list)
  166. throws IOException {
  167. try (PackWriter pw = new PackWriter(src)) {
  168. for (RevObject o : list) {
  169. pw.addObject(o);
  170. }
  171. final ObjectId name = pw.computeName();
  172. final File packFile = fullPackFileName(name, ".pack");
  173. final File idxFile = fullPackFileName(name, ".idx");
  174. final File[] files = new File[] { packFile, idxFile };
  175. write(files, pw);
  176. return files;
  177. }
  178. }
  179. private static void write(File[] files, PackWriter pw)
  180. throws IOException {
  181. final Instant begin = FS.DETECTED
  182. .lastModifiedInstant(files[0].getParentFile());
  183. NullProgressMonitor m = NullProgressMonitor.INSTANCE;
  184. try (OutputStream out = new BufferedOutputStream(
  185. new FileOutputStream(files[0]))) {
  186. pw.writePack(m, m, out);
  187. }
  188. try (OutputStream out = new BufferedOutputStream(
  189. new FileOutputStream(files[1]))) {
  190. pw.writeIndex(out);
  191. }
  192. touch(begin, files[0].getParentFile());
  193. }
  194. private static void delete(File[] list) throws IOException {
  195. final Instant begin = FS.DETECTED
  196. .lastModifiedInstant(list[0].getParentFile());
  197. for (File f : list) {
  198. FileUtils.delete(f);
  199. assertFalse(f + " was removed", f.exists());
  200. }
  201. touch(begin, list[0].getParentFile());
  202. }
  203. private static void touch(Instant begin, File dir) throws IOException {
  204. while (begin.compareTo(FS.DETECTED.lastModifiedInstant(dir)) >= 0) {
  205. try {
  206. Thread.sleep(25);
  207. } catch (InterruptedException ie) {
  208. //
  209. }
  210. FS.DETECTED.setLastModified(dir.toPath(), Instant.now());
  211. }
  212. }
  213. private File fullPackFileName(ObjectId name, String suffix) {
  214. final File packdir = db.getObjectDatabase().getPackDirectory();
  215. return new File(packdir, "pack-" + name.name() + suffix);
  216. }
  217. private RevObject writeBlob(Repository repo, String data)
  218. throws IOException {
  219. final byte[] bytes = Constants.encode(data);
  220. final ObjectId id;
  221. try (ObjectInserter inserter = repo.newObjectInserter()) {
  222. id = inserter.insert(Constants.OBJ_BLOB, bytes);
  223. inserter.flush();
  224. }
  225. try {
  226. parse(id);
  227. fail("Object " + id.name() + " should not exist in test repository");
  228. } catch (MissingObjectException e) {
  229. // Ok
  230. }
  231. try (RevWalk revWalk = new RevWalk(repo)) {
  232. return revWalk.lookupBlob(id);
  233. }
  234. }
  235. }