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