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