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
44
45
46 package org.eclipse.jgit.junit;
47
48 import static java.nio.charset.StandardCharsets.UTF_8;
49 import static org.junit.Assert.assertFalse;
50 import static org.junit.Assert.fail;
51
52 import java.io.File;
53 import java.io.IOException;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Set;
61 import java.util.TreeSet;
62
63 import org.eclipse.jgit.dircache.DirCache;
64 import org.eclipse.jgit.dircache.DirCacheEntry;
65 import org.eclipse.jgit.internal.storage.file.FileRepository;
66 import org.eclipse.jgit.lib.ConfigConstants;
67 import org.eclipse.jgit.lib.Constants;
68 import org.eclipse.jgit.lib.ObjectId;
69 import org.eclipse.jgit.lib.PersonIdent;
70 import org.eclipse.jgit.lib.Repository;
71 import org.eclipse.jgit.lib.RepositoryCache;
72 import org.eclipse.jgit.storage.file.FileBasedConfig;
73 import org.eclipse.jgit.storage.file.WindowCacheConfig;
74 import org.eclipse.jgit.util.FS;
75 import org.eclipse.jgit.util.FileUtils;
76 import org.eclipse.jgit.util.SystemReader;
77 import org.junit.After;
78 import org.junit.Before;
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 public abstract class LocalDiskRepositoryTestCase {
99 private static final boolean useMMAP = "true".equals(System
100 .getProperty("jgit.junit.usemmap"));
101
102
103 protected PersonIdent author;
104
105
106 protected PersonIdent committer;
107
108
109
110
111
112 protected MockSystemReader mockSystemReader;
113
114 private final Set<Repository> toClose = new HashSet<>();
115 private File tmp;
116
117
118
119
120
121
122 @Before
123 public void setUp() throws Exception {
124 tmp = File.createTempFile("jgit_test_", "_tmp");
125 CleanupThread.deleteOnShutdown(tmp);
126 if (!tmp.delete() || !tmp.mkdir())
127 throw new IOException("Cannot create " + tmp);
128
129 mockSystemReader = new MockSystemReader();
130 mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp,
131 "usergitconfig"), FS.DETECTED);
132
133
134
135 mockSystemReader.userGitConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION,
136 null, ConfigConstants.CONFIG_KEY_AUTODETACH, false);
137 mockSystemReader.userGitConfig.save();
138 ceilTestDirectories(getCeilings());
139 SystemReader.setInstance(mockSystemReader);
140
141 author = new PersonIdent("J. Author", "jauthor@example.com");
142 committer = new PersonIdent("J. Committer", "jcommitter@example.com");
143
144 final WindowCacheConfigowCacheConfig.html#WindowCacheConfig">WindowCacheConfig c = new WindowCacheConfig();
145 c.setPackedGitLimit(128 * WindowCacheConfig.KB);
146 c.setPackedGitWindowSize(8 * WindowCacheConfig.KB);
147 c.setPackedGitMMAP(useMMAP);
148 c.setDeltaBaseCacheLimit(8 * WindowCacheConfig.KB);
149 c.install();
150 }
151
152
153
154
155
156
157 protected File getTemporaryDirectory() {
158 return tmp.getAbsoluteFile();
159 }
160
161
162
163
164
165
166 protected List<File> getCeilings() {
167 return Collections.singletonList(getTemporaryDirectory());
168 }
169
170 private void ceilTestDirectories(List<File> ceilings) {
171 mockSystemReader.setProperty(Constants.GIT_CEILING_DIRECTORIES_KEY, makePath(ceilings));
172 }
173
174 private static String makePath(List<?> objects) {
175 final StringBuilder stringBuilder = new StringBuilder();
176 for (Object object : objects) {
177 if (stringBuilder.length() > 0)
178 stringBuilder.append(File.pathSeparatorChar);
179 stringBuilder.append(object.toString());
180 }
181 return stringBuilder.toString();
182 }
183
184
185
186
187
188
189 @After
190 public void tearDown() throws Exception {
191 RepositoryCache.clear();
192 for (Repository r : toClose)
193 r.close();
194 toClose.clear();
195
196
197
198
199
200 if (useMMAP)
201 System.gc();
202 if (tmp != null)
203 recursiveDelete(tmp, false, true);
204 if (tmp != null && !tmp.exists())
205 CleanupThread.removed(tmp);
206
207 SystemReader.setInstance(null);
208 }
209
210
211
212
213 protected void tick() {
214 mockSystemReader.tick(5 * 60);
215 final long now = mockSystemReader.getCurrentTime();
216 final int tz = mockSystemReader.getTimezone(now);
217
218 author = new PersonIdent(author, now, tz);
219 committer = new PersonIdent(committer, now, tz);
220 }
221
222
223
224
225
226
227
228 protected void recursiveDelete(File dir) {
229 recursiveDelete(dir, false, true);
230 }
231
232 private static boolean recursiveDelete(final File dir,
233 boolean silent, boolean failOnError) {
234 assert !(silent && failOnError);
235 if (!dir.exists())
236 return silent;
237 final File[] ls = dir.listFiles();
238 if (ls != null)
239 for (int k = 0; k < ls.length; k++) {
240 final File e = ls[k];
241 if (e.isDirectory())
242 silent = recursiveDelete(e, silent, failOnError);
243 else if (!e.delete()) {
244 if (!silent)
245 reportDeleteFailure(failOnError, e);
246 silent = !failOnError;
247 }
248 }
249 if (!dir.delete()) {
250 if (!silent)
251 reportDeleteFailure(failOnError, dir);
252 silent = !failOnError;
253 }
254 return silent;
255 }
256
257 private static void reportDeleteFailure(boolean failOnError, File e) {
258 String severity = failOnError ? "ERROR" : "WARNING";
259 String msg = severity + ": Failed to delete " + e;
260 if (failOnError)
261 fail(msg);
262 else
263 System.err.println(msg);
264 }
265
266
267 public static final int MOD_TIME = 1;
268
269
270 public static final int SMUDGE = 2;
271
272
273 public static final int LENGTH = 4;
274
275
276 public static final int CONTENT_ID = 8;
277
278
279 public static final int CONTENT = 16;
280
281
282 public static final int ASSUME_UNCHANGED = 32;
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 public static String indexState(Repository repo, int includedOptions)
322 throws IllegalStateException, IOException {
323 DirCache dc = repo.readDirCache();
324 StringBuilder sb = new StringBuilder();
325 TreeSet<Long> timeStamps = new TreeSet<>();
326
327
328 if (0 != (includedOptions & MOD_TIME)) {
329 for (int i=0; i<dc.getEntryCount(); ++i)
330 timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified()));
331 }
332
333
334 for (int i=0; i<dc.getEntryCount(); ++i) {
335 DirCacheEntry entry = dc.getEntry(i);
336 sb.append("["+entry.getPathString()+", mode:" + entry.getFileMode());
337 int stage = entry.getStage();
338 if (stage != 0)
339 sb.append(", stage:" + stage);
340 if (0 != (includedOptions & MOD_TIME)) {
341 sb.append(", time:t"+
342 timeStamps.headSet(Long.valueOf(entry.getLastModified())).size());
343 }
344 if (0 != (includedOptions & SMUDGE))
345 if (entry.isSmudged())
346 sb.append(", smudged");
347 if (0 != (includedOptions & LENGTH))
348 sb.append(", length:"
349 + Integer.toString(entry.getLength()));
350 if (0 != (includedOptions & CONTENT_ID))
351 sb.append(", sha1:" + ObjectId.toString(entry.getObjectId()));
352 if (0 != (includedOptions & CONTENT)) {
353 sb.append(", content:"
354 + new String(repo.open(entry.getObjectId(),
355 Constants.OBJ_BLOB).getCachedBytes(), UTF_8));
356 }
357 if (0 != (includedOptions & ASSUME_UNCHANGED))
358 sb.append(", assume-unchanged:"
359 + Boolean.toString(entry.isAssumeValid()));
360 sb.append("]");
361 }
362 return sb.toString();
363 }
364
365
366
367
368
369
370
371
372
373 protected FileRepository createBareRepository() throws IOException {
374 return createRepository(true );
375 }
376
377
378
379
380
381
382
383
384 protected FileRepository createWorkRepository() throws IOException {
385 return createRepository(false );
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399 protected FileRepository createRepository(boolean bare)
400 throws IOException {
401 return createRepository(bare, false );
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 @Deprecated
418 public FileRepository createRepository(boolean bare, boolean autoClose)
419 throws IOException {
420 File gitdir = createUniqueTestGitDir(bare);
421 FileRepository db = new FileRepository(gitdir);
422 assertFalse(gitdir.exists());
423 db.create(bare);
424 if (autoClose) {
425 addRepoToClose(db);
426 }
427 return db;
428 }
429
430
431
432
433
434
435
436
437 public void addRepoToClose(Repository r) {
438 toClose.add(r);
439 }
440
441
442
443
444
445
446
447
448
449 protected File createTempDirectory(String name) throws IOException {
450 File directory = new File(createTempFile(), name);
451 FileUtils.mkdirs(directory);
452 return directory.getCanonicalFile();
453 }
454
455
456
457
458
459
460
461
462
463
464 protected File createUniqueTestGitDir(boolean bare) throws IOException {
465 String gitdirName = createTempFile().getPath();
466 if (!bare)
467 gitdirName += "/";
468 return new File(gitdirName + Constants.DOT_GIT);
469 }
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484 protected File createTempFile() throws IOException {
485 File p = File.createTempFile("tmp_", "", tmp);
486 if (!p.delete()) {
487 throw new IOException("Cannot obtain unique path " + tmp);
488 }
489 return p;
490 }
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508 protected int runHook(final Repository db, final File hook,
509 final String... args) throws IOException, InterruptedException {
510 final String[] argv = new String[1 + args.length];
511 argv[0] = hook.getAbsolutePath();
512 System.arraycopy(args, 0, argv, 1, args.length);
513
514 final Map<String, String> env = cloneEnv();
515 env.put("GIT_DIR", db.getDirectory().getAbsolutePath());
516 putPersonIdent(env, "AUTHOR", author);
517 putPersonIdent(env, "COMMITTER", committer);
518
519 final File cwd = db.getWorkTree();
520 final Process p = Runtime.getRuntime().exec(argv, toEnvArray(env), cwd);
521 p.getOutputStream().close();
522 p.getErrorStream().close();
523 p.getInputStream().close();
524 return p.waitFor();
525 }
526
527 private static void putPersonIdent(final Map<String, String> env,
528 final String type, final PersonIdent who) {
529 final String ident = who.toExternalString();
530 final String date = ident.substring(ident.indexOf("> ") + 2);
531 env.put("GIT_" + type + "_NAME", who.getName());
532 env.put("GIT_" + type + "_EMAIL", who.getEmailAddress());
533 env.put("GIT_" + type + "_DATE", date);
534 }
535
536
537
538
539
540
541
542
543
544
545
546 protected File write(String body) throws IOException {
547 final File f = File.createTempFile("temp", "txt", tmp);
548 try {
549 write(f, body);
550 return f;
551 } catch (Error e) {
552 f.delete();
553 throw e;
554 } catch (RuntimeException e) {
555 f.delete();
556 throw e;
557 } catch (IOException e) {
558 f.delete();
559 throw e;
560 }
561 }
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577 protected void write(File f, String body) throws IOException {
578 JGitTestUtil.write(f, body);
579 }
580
581
582
583
584
585
586
587
588
589 protected String read(File f) throws IOException {
590 return JGitTestUtil.read(f);
591 }
592
593 private static String[] toEnvArray(Map<String, String> env) {
594 final String[] envp = new String[env.size()];
595 int i = 0;
596 for (Map.Entry<String, String> e : env.entrySet())
597 envp[i++] = e.getKey() + "=" + e.getValue();
598 return envp;
599 }
600
601 private static HashMap<String, String> cloneEnv() {
602 return new HashMap<>(System.getenv());
603 }
604
605 private static final class CleanupThread extends Thread {
606 private static final CleanupThread me;
607 static {
608 me = new CleanupThread();
609 Runtime.getRuntime().addShutdownHook(me);
610 }
611
612 static void deleteOnShutdown(File tmp) {
613 synchronized (me) {
614 me.toDelete.add(tmp);
615 }
616 }
617
618 static void removed(File tmp) {
619 synchronized (me) {
620 me.toDelete.remove(tmp);
621 }
622 }
623
624 private final List<File> toDelete = new ArrayList<>();
625
626 @Override
627 public void run() {
628
629
630
631
632
633 System.gc();
634 synchronized (this) {
635 boolean silent = false;
636 boolean failOnError = false;
637 for (File tmp : toDelete)
638 recursiveDelete(tmp, silent, failOnError);
639 }
640 }
641 }
642 }