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 package org.eclipse.jgit.lib;
45
46 import java.io.File;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.concurrent.ConcurrentHashMap;
51 import java.util.concurrent.ScheduledFuture;
52 import java.util.concurrent.ScheduledThreadPoolExecutor;
53 import java.util.concurrent.TimeUnit;
54
55 import org.eclipse.jgit.annotations.NonNull;
56 import org.eclipse.jgit.errors.RepositoryNotFoundException;
57 import org.eclipse.jgit.internal.storage.file.FileRepository;
58 import org.eclipse.jgit.util.FS;
59 import org.eclipse.jgit.util.IO;
60 import org.eclipse.jgit.util.RawParseUtils;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64
65 public class RepositoryCache {
66 private static final RepositoryCache cache = new RepositoryCache();
67
68 private final static Logger LOG = LoggerFactory
69 .getLogger(RepositoryCache.class);
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 public static Repository open(final Key location) throws IOException,
87 RepositoryNotFoundException {
88 return open(location, true);
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 public static Repository open(final Key location, final boolean mustExist)
112 throws IOException {
113 return cache.openRepository(location, mustExist);
114 }
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 public static void register(final Repository db) {
131 if (db.getDirectory() != null) {
132 FileKey key = FileKey.exact(db.getDirectory(), db.getFS());
133 cache.registerRepository(key, db);
134 }
135 }
136
137
138
139
140
141
142
143
144
145
146 public static void close(@NonNull final Repository db) {
147 if (db.getDirectory() != null) {
148 FileKey key = FileKey.exact(db.getDirectory(), db.getFS());
149 cache.unregisterAndCloseRepository(key);
150 }
151 }
152
153
154
155
156
157
158
159
160
161
162
163
164
165 public static void unregister(final Repository db) {
166 if (db.getDirectory() != null) {
167 unregister(FileKey.exact(db.getDirectory(), db.getFS()));
168 }
169 }
170
171
172
173
174
175
176
177
178
179
180
181
182
183 public static void unregister(Key location) {
184 cache.unregisterRepository(location);
185 }
186
187
188
189
190
191 public static Collection<Key> getRegisteredKeys() {
192 return cache.getKeys();
193 }
194
195 static boolean isCached(@NonNull Repository repo) {
196 File gitDir = repo.getDirectory();
197 if (gitDir == null) {
198 return false;
199 }
200 FileKey key = new FileKey(gitDir, repo.getFS());
201 return cache.cacheMap.get(key) == repo;
202 }
203
204
205 public static void clear() {
206 cache.clearAll();
207 }
208
209 static void clearExpired() {
210 cache.clearAllExpired();
211 }
212
213 static void reconfigure(RepositoryCacheConfig repositoryCacheConfig) {
214 cache.configureEviction(repositoryCacheConfig);
215 }
216
217 private final ConcurrentHashMap<Key, Repository> cacheMap;
218
219 private final Lock[] openLocks;
220
221 private ScheduledFuture<?> cleanupTask;
222
223 private volatile long expireAfter;
224
225 private RepositoryCache() {
226 cacheMap = new ConcurrentHashMap<>();
227 openLocks = new Lock[4];
228 for (int i = 0; i < openLocks.length; i++) {
229 openLocks[i] = new Lock();
230 }
231 configureEviction(new RepositoryCacheConfig());
232 }
233
234 private void configureEviction(
235 RepositoryCacheConfig repositoryCacheConfig) {
236 expireAfter = repositoryCacheConfig.getExpireAfter();
237 ScheduledThreadPoolExecutor scheduler = WorkQueue.getExecutor();
238 synchronized (scheduler) {
239 if (cleanupTask != null) {
240 cleanupTask.cancel(false);
241 }
242 long delay = repositoryCacheConfig.getCleanupDelay();
243 if (delay == RepositoryCacheConfig.NO_CLEANUP) {
244 return;
245 }
246 cleanupTask = scheduler.scheduleWithFixedDelay(new Runnable() {
247 @Override
248 public void run() {
249 try {
250 cache.clearAllExpired();
251 } catch (Throwable e) {
252 LOG.error(e.getMessage(), e);
253 }
254 }
255 }, delay, delay, TimeUnit.MILLISECONDS);
256 }
257 }
258
259 private Repository openRepository(final Key location,
260 final boolean mustExist) throws IOException {
261 Repository db = cacheMap.get(location);
262 if (db == null) {
263 synchronized (lockFor(location)) {
264 db = cacheMap.get(location);
265 if (db == null) {
266 db = location.open(mustExist);
267 cacheMap.put(location, db);
268 } else {
269 db.incrementOpen();
270 }
271 }
272 } else {
273 db.incrementOpen();
274 }
275 return db;
276 }
277
278 private void registerRepository(final Key location, final Repository db) {
279 Repository oldDb = cacheMap.put(location, db);
280 if (oldDb != null)
281 oldDb.close();
282 }
283
284 private Repository unregisterRepository(final Key location) {
285 return cacheMap.remove(location);
286 }
287
288 private boolean isExpired(Repository db) {
289 return db != null && db.useCnt.get() <= 0
290 && (System.currentTimeMillis() - db.closedAt.get() > expireAfter);
291 }
292
293 private void unregisterAndCloseRepository(final Key location) {
294 synchronized (lockFor(location)) {
295 Repository oldDb = unregisterRepository(location);
296 if (oldDb != null) {
297 oldDb.doClose();
298 }
299 }
300 }
301
302 private Collection<Key> getKeys() {
303 return new ArrayList<>(cacheMap.keySet());
304 }
305
306 private void clearAllExpired() {
307 for (Repository db : cacheMap.values()) {
308 if (isExpired(db)) {
309 RepositoryCache.close(db);
310 }
311 }
312 }
313
314 private void clearAll() {
315 for (Key k : cacheMap.keySet()) {
316 unregisterAndCloseRepository(k);
317 }
318 }
319
320 private Lock lockFor(final Key location) {
321 return openLocks[(location.hashCode() >>> 1) % openLocks.length];
322 }
323
324 private static class Lock {
325
326 }
327
328
329
330
331
332
333
334
335 public static interface Key {
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354 Repository open(boolean mustExist) throws IOException,
355 RepositoryNotFoundException;
356 }
357
358
359 public static class FileKey implements Key {
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374 public static FileKey exact(final File directory, FS fs) {
375 return new FileKey(directory, fs);
376 }
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397 public static FileKey lenient(final File directory, FS fs) {
398 final File gitdir = resolve(directory, fs);
399 return new FileKey(gitdir != null ? gitdir : directory, fs);
400 }
401
402 private final File path;
403 private final FS fs;
404
405
406
407
408
409
410
411
412 protected FileKey(final File directory, FS fs) {
413 path = canonical(directory);
414 this.fs = fs;
415 }
416
417 private static File canonical(final File path) {
418 try {
419 return path.getCanonicalFile();
420 } catch (IOException e) {
421 return path.getAbsoluteFile();
422 }
423 }
424
425
426 public final File getFile() {
427 return path;
428 }
429
430 @Override
431 public Repository open(final boolean mustExist) throws IOException {
432 if (mustExist && !isGitRepository(path, fs))
433 throw new RepositoryNotFoundException(path);
434 return new FileRepository(path);
435 }
436
437 @Override
438 public int hashCode() {
439 return path.hashCode();
440 }
441
442 @Override
443 public boolean equals(final Object o) {
444 return o instanceof FileKey && path.equals(((FileKey) o).path);
445 }
446
447 @Override
448 public String toString() {
449 return path.toString();
450 }
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467 public static boolean isGitRepository(final File dir, FS fs) {
468 return fs.resolve(dir, "objects").exists()
469 && fs.resolve(dir, "refs").exists()
470 && isValidHead(new File(dir, Constants.HEAD));
471 }
472
473 private static boolean isValidHead(final File head) {
474 final String ref = readFirstLine(head);
475 return ref != null
476 && (ref.startsWith("ref: refs/") || ObjectId.isId(ref));
477 }
478
479 private static String readFirstLine(final File head) {
480 try {
481 final byte[] buf = IO.readFully(head, 4096);
482 int n = buf.length;
483 if (n == 0)
484 return null;
485 if (buf[n - 1] == '\n')
486 n--;
487 return RawParseUtils.decode(buf, 0, n);
488 } catch (IOException e) {
489 return null;
490 }
491 }
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512 public static File resolve(final File directory, FS fs) {
513 if (isGitRepository(directory, fs))
514 return directory;
515 if (isGitRepository(new File(directory, Constants.DOT_GIT), fs))
516 return new File(directory, Constants.DOT_GIT);
517
518 final String name = directory.getName();
519 final File parent = directory.getParentFile();
520 if (isGitRepository(new File(parent, name + Constants.DOT_GIT_EXT), fs))
521 return new File(parent, name + Constants.DOT_GIT_EXT);
522 return null;
523 }
524 }
525 }