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

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