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