1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.internal.storage.file;
44
45 import static org.junit.Assert.assertEquals;
46 import static org.junit.Assert.assertFalse;
47 import static org.junit.Assert.assertTrue;
48 import static org.junit.Assume.assumeFalse;
49 import static org.junit.Assume.assumeTrue;
50
51 import java.io.File;
52 import java.io.IOException;
53 import java.io.OutputStream;
54 import java.io.Writer;
55 import java.nio.file.Files;
56 import java.nio.file.Path;
57 import java.nio.file.Paths;
58 import java.nio.file.StandardCopyOption;
59 import java.nio.file.StandardOpenOption;
60
61 import java.text.ParseException;
62 import java.time.Instant;
63 import java.util.Collection;
64 import java.util.Iterator;
65 import java.util.Random;
66 import java.util.zip.Deflater;
67
68 import org.eclipse.jgit.api.GarbageCollectCommand;
69 import org.eclipse.jgit.api.Git;
70 import org.eclipse.jgit.api.errors.AbortedByHookException;
71 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
72 import org.eclipse.jgit.api.errors.GitAPIException;
73 import org.eclipse.jgit.api.errors.NoFilepatternException;
74 import org.eclipse.jgit.api.errors.NoHeadException;
75 import org.eclipse.jgit.api.errors.NoMessageException;
76 import org.eclipse.jgit.api.errors.UnmergedPathsException;
77 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
78 import org.eclipse.jgit.junit.RepositoryTestCase;
79 import org.eclipse.jgit.lib.AnyObjectId;
80 import org.eclipse.jgit.lib.ConfigConstants;
81 import org.eclipse.jgit.lib.ObjectId;
82 import org.eclipse.jgit.storage.file.FileBasedConfig;
83 import org.eclipse.jgit.storage.pack.PackConfig;
84 import org.eclipse.jgit.util.FS;
85 import org.junit.Test;
86
87 public class PackFileSnapshotTest extends RepositoryTestCase {
88
89 private static ObjectId unknownID = ObjectId
90 .fromString("1234567890123456789012345678901234567890");
91
92 @Test
93 public void testSamePackDifferentCompressionDetectChecksumChanged()
94 throws Exception {
95 Git git = Git.wrap(db);
96 File f = writeTrashFile("file", "foobar ");
97 for (int i = 0; i < 10; i++) {
98 appendRandomLine(f);
99 git.add().addFilepattern("file").call();
100 git.commit().setMessage("message" + i).call();
101 }
102
103 FileBasedConfig c = db.getConfig();
104 c.setInt(ConfigConstants.CONFIG_GC_SECTION, null,
105 ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, 1);
106 c.save();
107 Collection<PackFile> packs = gc(Deflater.NO_COMPRESSION);
108 assertEquals("expected 1 packfile after gc", 1, packs.size());
109 PackFile p1 = packs.iterator().next();
110 PackFileSnapshot snapshot = p1.getFileSnapshot();
111
112 packs = gc(Deflater.BEST_COMPRESSION);
113 assertEquals("expected 1 packfile after gc", 1, packs.size());
114 PackFile p2 = packs.iterator().next();
115 File pf = p2.getPackFile();
116
117
118
119
120
121
122 assertTrue("expected snapshot to detect modified pack",
123 snapshot.isModified(pf));
124 assertTrue("expected checksum changed", snapshot.isChecksumChanged(pf));
125 }
126
127 private void appendRandomLine(File f, int length, Random r)
128 throws IOException {
129 try (Writer w = Files.newBufferedWriter(f.toPath(),
130 StandardOpenOption.APPEND)) {
131 appendRandomLine(w, length, r);
132 }
133 }
134
135 private void appendRandomLine(File f) throws IOException {
136 appendRandomLine(f, 5, new Random());
137 }
138
139 private void appendRandomLine(Writer w, int len, Random r)
140 throws IOException {
141 final int c1 = 32;
142 int c2 = 126;
143 for (int i = 0; i < len; i++) {
144 w.append((char) (c1 + r.nextInt(1 + c2 - c1)));
145 }
146 }
147
148 private ObjectId createTestRepo(int testDataSeed, int testDataLength)
149 throws IOException, GitAPIException, NoFilepatternException,
150 NoHeadException, NoMessageException, UnmergedPathsException,
151 ConcurrentRefUpdateException, WrongRepositoryStateException,
152 AbortedByHookException {
153
154
155
156
157 Random r = new Random(testDataSeed);
158 Git git = Git.wrap(db);
159 File f = writeTrashFile("file", "foobar ");
160 appendRandomLine(f, testDataLength, r);
161 git.add().addFilepattern("file").call();
162 git.commit().setMessage("message1").call();
163 appendRandomLine(f, testDataLength, r);
164 git.add().addFilepattern("file").call();
165 return git.commit().setMessage("message2").call().getId();
166 }
167
168
169
170
171
172
173 @Test
174 public void testDetectModificationAlthoughSameSizeAndModificationtime()
175 throws Exception {
176 int testDataSeed = 1;
177 int testDataLength = 100;
178 FileBasedConfig config = db.getConfig();
179
180
181 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
182 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, false);
183 config.save();
184
185 createTestRepo(testDataSeed, testDataLength);
186
187
188 PackFile pf = repackAndCheck(5, null, null, null);
189 Path packFilePath = pf.getPackFile().toPath();
190 AnyObjectId chk1 = pf.getPackChecksum();
191 String name = pf.getPackName();
192 Long length = Long.valueOf(pf.getPackFile().length());
193 FS fs = db.getFS();
194 Instant m1 = fs.lastModifiedInstant(packFilePath);
195
196
197
198 fsTick(packFilePath.toFile());
199
200
201
202 AnyObjectId chk2 = repackAndCheck(6, name, length, chk1)
203 .getPackChecksum();
204 Instant m2 = fs.lastModifiedInstant(packFilePath);
205 assumeFalse(m2.equals(m1));
206
207
208
209
210 AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
211 .getPackChecksum();
212 Instant m3 = fs.lastModifiedInstant(packFilePath);
213
214
215
216
217 db.getObjectDatabase().has(unknownID);
218 assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
219 .getPackChecksum());
220 assumeTrue(m3.equals(m2));
221 }
222
223
224
225
226
227
228
229 @Test
230 public void testDetectModificationAlthoughSameSizeAndModificationtimeAndFileKey()
231 throws Exception {
232 int testDataSeed = 1;
233 int testDataLength = 100;
234 FileBasedConfig config = db.getConfig();
235 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
236 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, false);
237 config.save();
238
239 createTestRepo(testDataSeed, testDataLength);
240
241
242 PackFile pf = repackAndCheck(5, null, null, null);
243 Path packFilePath = pf.getPackFile().toPath();
244 Path packFileBasePath = packFilePath.resolveSibling(
245 packFilePath.getFileName().toString().replaceAll(".pack", ""));
246 AnyObjectId chk1 = pf.getPackChecksum();
247 String name = pf.getPackName();
248 Long length = Long.valueOf(pf.getPackFile().length());
249 copyPack(packFileBasePath, "", ".copy1");
250
251
252 AnyObjectId chk2 = repackAndCheck(6, name, length, chk1)
253 .getPackChecksum();
254 copyPack(packFileBasePath, "", ".copy2");
255
256
257 AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
258 .getPackChecksum();
259 FS fs = db.getFS();
260 Instant m3 = fs.lastModifiedInstant(packFilePath);
261 db.getObjectDatabase().has(unknownID);
262 assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
263 .getPackChecksum());
264
265
266
267 fsTick(packFilePath.toFile());
268
269
270
271 copyPack(packFileBasePath, ".copy2", "");
272 Instant m2 = fs.lastModifiedInstant(packFilePath);
273 assumeFalse(m3.equals(m2));
274
275 db.getObjectDatabase().has(unknownID);
276 assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks())
277 .getPackChecksum());
278
279
280
281 copyPack(packFileBasePath, ".copy1", "");
282 Instant m1 = fs.lastModifiedInstant(packFilePath);
283 assumeTrue(m2.equals(m1));
284 db.getObjectDatabase().has(unknownID);
285 assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks())
286 .getPackChecksum());
287 }
288
289
290
291 private Path copyFile(Path src, Path dst) throws IOException {
292 if (Files.exists(dst)) {
293 dst.toFile().setWritable(true);
294 try (OutputStream dstOut = Files.newOutputStream(dst)) {
295 Files.copy(src, dstOut);
296 return dst;
297 }
298 } else {
299 return Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
300 }
301 }
302
303 private Path copyPack(Path base, String srcSuffix, String dstSuffix)
304 throws IOException {
305 copyFile(Paths.get(base + ".idx" + srcSuffix),
306 Paths.get(base + ".idx" + dstSuffix));
307 copyFile(Paths.get(base + ".bitmap" + srcSuffix),
308 Paths.get(base + ".bitmap" + dstSuffix));
309 return copyFile(Paths.get(base + ".pack" + srcSuffix),
310 Paths.get(base + ".pack" + dstSuffix));
311 }
312
313 private PackFile repackAndCheck(int compressionLevel, String oldName,
314 Long oldLength, AnyObjectId oldChkSum)
315 throws IOException, ParseException {
316 PackFile p = getSinglePack(gc(compressionLevel));
317 File pf = p.getPackFile();
318
319
320
321
322
323
324
325 assumeTrue(oldLength == null || pf.length() == oldLength.longValue());
326 assumeTrue(oldChkSum == null || !p.getPackChecksum().equals(oldChkSum));
327 assertTrue(oldName == null || p.getPackName().equals(oldName));
328 return p;
329 }
330
331 private PackFile getSinglePack(Collection<PackFile> packs) {
332 Iterator<PackFile> pIt = packs.iterator();
333 PackFile p = pIt.next();
334 assertFalse(pIt.hasNext());
335 return p;
336 }
337
338 private Collection<PackFile> gc(int compressionLevel)
339 throws IOException, ParseException {
340 GC gc = new GC(db);
341 PackConfig pc = new PackConfig(db.getConfig());
342 pc.setCompressionLevel(compressionLevel);
343
344 pc.setSinglePack(true);
345
346
347 pc.setDeltaSearchWindowSize(
348 GarbageCollectCommand.DEFAULT_GC_AGGRESSIVE_WINDOW);
349 pc.setMaxDeltaDepth(GarbageCollectCommand.DEFAULT_GC_AGGRESSIVE_DEPTH);
350 pc.setReuseObjects(false);
351
352 gc.setPackConfig(pc);
353 gc.setExpireAgeMillis(0);
354 gc.setPackExpireAgeMillis(0);
355 return gc.gc();
356 }
357
358 }