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 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(final 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 private FileRepository createRepository(boolean bare)
399 throws IOException {
400 return createRepository(bare, true );
401 }
402
403
404
405
406
407
408
409
410
411
412
413
414
415 public FileRepository createRepository(boolean bare, boolean autoClose)
416 throws IOException {
417 File gitdir = createUniqueTestGitDir(bare);
418 FileRepository db = new FileRepository(gitdir);
419 assertFalse(gitdir.exists());
420 db.create(bare);
421 if (autoClose) {
422 addRepoToClose(db);
423 }
424 return db;
425 }
426
427
428
429
430
431
432
433
434 public void addRepoToClose(Repository r) {
435 toClose.add(r);
436 }
437
438
439
440
441
442
443
444
445
446 protected File createTempDirectory(String name) throws IOException {
447 File directory = new File(createTempFile(), name);
448 FileUtils.mkdirs(directory);
449 return directory.getCanonicalFile();
450 }
451
452
453
454
455
456
457
458
459
460
461 protected File createUniqueTestGitDir(boolean bare) throws IOException {
462 String gitdirName = createTempFile().getPath();
463 if (!bare)
464 gitdirName += "/";
465 return new File(gitdirName + Constants.DOT_GIT);
466 }
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481 protected File createTempFile() throws IOException {
482 File p = File.createTempFile("tmp_", "", tmp);
483 if (!p.delete()) {
484 throw new IOException("Cannot obtain unique path " + tmp);
485 }
486 return p;
487 }
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505 protected int runHook(final Repository db, final File hook,
506 final String... args) throws IOException, InterruptedException {
507 final String[] argv = new String[1 + args.length];
508 argv[0] = hook.getAbsolutePath();
509 System.arraycopy(args, 0, argv, 1, args.length);
510
511 final Map<String, String> env = cloneEnv();
512 env.put("GIT_DIR", db.getDirectory().getAbsolutePath());
513 putPersonIdent(env, "AUTHOR", author);
514 putPersonIdent(env, "COMMITTER", committer);
515
516 final File cwd = db.getWorkTree();
517 final Process p = Runtime.getRuntime().exec(argv, toEnvArray(env), cwd);
518 p.getOutputStream().close();
519 p.getErrorStream().close();
520 p.getInputStream().close();
521 return p.waitFor();
522 }
523
524 private static void putPersonIdent(final Map<String, String> env,
525 final String type, final PersonIdent who) {
526 final String ident = who.toExternalString();
527 final String date = ident.substring(ident.indexOf("> ") + 2);
528 env.put("GIT_" + type + "_NAME", who.getName());
529 env.put("GIT_" + type + "_EMAIL", who.getEmailAddress());
530 env.put("GIT_" + type + "_DATE", date);
531 }
532
533
534
535
536
537
538
539
540
541
542
543 protected File write(final String body) throws IOException {
544 final File f = File.createTempFile("temp", "txt", tmp);
545 try {
546 write(f, body);
547 return f;
548 } catch (Error e) {
549 f.delete();
550 throw e;
551 } catch (RuntimeException e) {
552 f.delete();
553 throw e;
554 } catch (IOException e) {
555 f.delete();
556 throw e;
557 }
558 }
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574 protected void write(final File f, final String body) throws IOException {
575 JGitTestUtil.write(f, body);
576 }
577
578
579
580
581
582
583
584
585
586 protected String read(final File f) throws IOException {
587 return JGitTestUtil.read(f);
588 }
589
590 private static String[] toEnvArray(final Map<String, String> env) {
591 final String[] envp = new String[env.size()];
592 int i = 0;
593 for (Map.Entry<String, String> e : env.entrySet())
594 envp[i++] = e.getKey() + "=" + e.getValue();
595 return envp;
596 }
597
598 private static HashMap<String, String> cloneEnv() {
599 return new HashMap<>(System.getenv());
600 }
601
602 private static final class CleanupThread extends Thread {
603 private static final CleanupThread me;
604 static {
605 me = new CleanupThread();
606 Runtime.getRuntime().addShutdownHook(me);
607 }
608
609 static void deleteOnShutdown(File tmp) {
610 synchronized (me) {
611 me.toDelete.add(tmp);
612 }
613 }
614
615 static void removed(File tmp) {
616 synchronized (me) {
617 me.toDelete.remove(tmp);
618 }
619 }
620
621 private final List<File> toDelete = new ArrayList<>();
622
623 @Override
624 public void run() {
625
626
627
628
629
630 System.gc();
631 synchronized (this) {
632 boolean silent = false;
633 boolean failOnError = false;
634 for (File tmp : toDelete)
635 recursiveDelete(tmp, silent, failOnError);
636 }
637 }
638 }
639 }