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