1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.file;
12
13 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
14 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
15 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
16
17 import java.io.File;
18 import java.io.FileNotFoundException;
19 import java.io.IOException;
20 import java.text.MessageFormat;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.EnumMap;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.concurrent.atomic.AtomicReference;
31
32 import org.eclipse.jgit.annotations.Nullable;
33 import org.eclipse.jgit.errors.CorruptObjectException;
34 import org.eclipse.jgit.errors.PackInvalidException;
35 import org.eclipse.jgit.errors.PackMismatchException;
36 import org.eclipse.jgit.errors.SearchForReuseTimeout;
37 import org.eclipse.jgit.internal.JGitText;
38 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
39 import org.eclipse.jgit.internal.storage.pack.PackExt;
40 import org.eclipse.jgit.internal.storage.pack.PackWriter;
41 import org.eclipse.jgit.lib.AbbreviatedObjectId;
42 import org.eclipse.jgit.lib.AnyObjectId;
43 import org.eclipse.jgit.lib.Config;
44 import org.eclipse.jgit.lib.ConfigConstants;
45 import org.eclipse.jgit.lib.ObjectId;
46 import org.eclipse.jgit.lib.ObjectLoader;
47 import org.eclipse.jgit.util.FileUtils;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51
52
53
54
55
56
57
58
59 class PackDirectory {
60 private final static Logger LOG = LoggerFactory
61 .getLogger(PackDirectory.class);
62
63 private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY,
64 new Pack[0]);
65
66 private final Config config;
67
68 private final File directory;
69
70 private final AtomicReference<PackList> packList;
71
72
73
74
75
76
77
78
79
80 PackDirectory(Config config, File directory) {
81 this.config = config;
82 this.directory = directory;
83 packList = new AtomicReference<>(NO_PACKS);
84 }
85
86
87
88
89
90
91 File getDirectory() {
92 return directory;
93 }
94
95 void create() throws IOException {
96 FileUtils.mkdir(directory);
97 }
98
99 void close() {
100 PackList packs = packList.get();
101 if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) {
102 for (Pack p : packs.packs) {
103 p.close();
104 }
105 }
106 }
107
108 Collection<Pack> getPacks() {
109 PackList list = packList.get();
110 if (list == NO_PACKS) {
111 list = scanPacks(list);
112 }
113 Pack[] packs = list.packs;
114 return Collections.unmodifiableCollection(Arrays.asList(packs));
115 }
116
117
118 @Override
119 public String toString() {
120 return "PackDirectory[" + getDirectory() + "]";
121 }
122
123
124
125
126
127
128
129
130 boolean has(AnyObjectId objectId) {
131 return getPack(objectId) != null;
132 }
133
134
135
136
137
138
139
140
141
142
143
144 @Nullable
145 Pack getPack(AnyObjectId objectId) {
146 PackList pList;
147 do {
148 pList = packList.get();
149 for (Pack p : pList.packs) {
150 try {
151 if (p.hasObject(objectId)) {
152 return p;
153 }
154 } catch (IOException e) {
155
156
157
158 LOG.warn(MessageFormat.format(
159 JGitText.get().unableToReadPackfile,
160 p.getPackFile().getAbsolutePath()), e);
161 remove(p);
162 }
163 }
164 } while (searchPacksAgain(pList));
165 return null;
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182 boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
183 int matchLimit) {
184
185
186 int oldSize = matches.size();
187 PackList pList;
188 do {
189 pList = packList.get();
190 for (Pack p : pList.packs) {
191 try {
192 p.resolve(matches, id, matchLimit);
193 p.resetTransientErrorCount();
194 } catch (IOException e) {
195 handlePackError(e, p);
196 }
197 if (matches.size() > matchLimit) {
198 return false;
199 }
200 }
201 } while (matches.size() == oldSize && searchPacksAgain(pList));
202 return true;
203 }
204
205 ObjectLoader open(WindowCursor curs, AnyObjectId objectId) {
206 PackList pList;
207 do {
208 SEARCH: for (;;) {
209 pList = packList.get();
210 for (Pack p : pList.packs) {
211 try {
212 ObjectLoader ldr = p.get(curs, objectId);
213 p.resetTransientErrorCount();
214 if (ldr != null)
215 return ldr;
216 } catch (PackMismatchException e) {
217
218 if (searchPacksAgain(pList)) {
219 continue SEARCH;
220 }
221 } catch (IOException e) {
222 handlePackError(e, p);
223 }
224 }
225 break SEARCH;
226 }
227 } while (searchPacksAgain(pList));
228 return null;
229 }
230
231 long getSize(WindowCursor curs, AnyObjectId id) {
232 PackList pList;
233 do {
234 SEARCH: for (;;) {
235 pList = packList.get();
236 for (Pack p : pList.packs) {
237 try {
238 long len = p.getObjectSize(curs, id);
239 p.resetTransientErrorCount();
240 if (0 <= len) {
241 return len;
242 }
243 } catch (PackMismatchException e) {
244
245 if (searchPacksAgain(pList)) {
246 continue SEARCH;
247 }
248 } catch (IOException e) {
249 handlePackError(e, p);
250 }
251 }
252 break SEARCH;
253 }
254 } while (searchPacksAgain(pList));
255 return -1;
256 }
257
258 void selectRepresentation(PackWriter packer, ObjectToPack otp,
259 WindowCursor curs) {
260 PackList pList = packList.get();
261 SEARCH: for (;;) {
262 for (Pack p : pList.packs) {
263 try {
264 LocalObjectRepresentation rep = p.representation(curs, otp);
265 p.resetTransientErrorCount();
266 if (rep != null) {
267 packer.select(otp, rep);
268 packer.checkSearchForReuseTimeout();
269 }
270 } catch (SearchForReuseTimeout e) {
271 break SEARCH;
272 } catch (PackMismatchException e) {
273
274
275 pList = scanPacks(pList);
276 continue SEARCH;
277 } catch (IOException e) {
278 handlePackError(e, p);
279 }
280 }
281 break SEARCH;
282 }
283 }
284
285 private void handlePackError(IOException e, Pack p) {
286 String warnTmpl = null;
287 int transientErrorCount = 0;
288 String errTmpl = JGitText.get().exceptionWhileReadingPack;
289 if ((e instanceof CorruptObjectException)
290 || (e instanceof PackInvalidException)) {
291 warnTmpl = JGitText.get().corruptPack;
292 LOG.warn(MessageFormat.format(warnTmpl,
293 p.getPackFile().getAbsolutePath()), e);
294
295 remove(p);
296 } else if (e instanceof FileNotFoundException) {
297 if (p.getPackFile().exists()) {
298 errTmpl = JGitText.get().packInaccessible;
299 transientErrorCount = p.incrementTransientErrorCount();
300 } else {
301 warnTmpl = JGitText.get().packWasDeleted;
302 remove(p);
303 }
304 } else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
305 warnTmpl = JGitText.get().packHandleIsStale;
306 remove(p);
307 } else {
308 transientErrorCount = p.incrementTransientErrorCount();
309 }
310 if (warnTmpl != null) {
311 LOG.warn(MessageFormat.format(warnTmpl,
312 p.getPackFile().getAbsolutePath()), e);
313 } else {
314 if (doLogExponentialBackoff(transientErrorCount)) {
315
316
317 LOG.error(MessageFormat.format(errTmpl,
318 p.getPackFile().getAbsolutePath(),
319 Integer.valueOf(transientErrorCount)), e);
320 }
321 }
322 }
323
324
325
326
327
328
329 private boolean doLogExponentialBackoff(int n) {
330 return (n & (n - 1)) == 0;
331 }
332
333 boolean searchPacksAgain(PackList old) {
334
335
336
337
338
339
340 boolean trustFolderStat = config.getBoolean(
341 ConfigConstants.CONFIG_CORE_SECTION,
342 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
343
344 return ((!trustFolderStat) || old.snapshot.isModified(directory))
345 && old != scanPacks(old);
346 }
347
348 void insert(Pack pack) {
349 PackList o, n;
350 do {
351 o = packList.get();
352
353
354
355
356
357 final Pack[] oldList = o.packs;
358 final String name = pack.getPackFile().getName();
359 for (Pack p : oldList) {
360 if (name.equals(p.getPackFile().getName())) {
361 return;
362 }
363 }
364
365 final Pack[] newList = new Pack[1 + oldList.length];
366 newList[0] = pack;
367 System.arraycopy(oldList, 0, newList, 1, oldList.length);
368 n = new PackList(o.snapshot, newList);
369 } while (!packList.compareAndSet(o, n));
370 }
371
372 private void remove(Pack deadPack) {
373 PackList o, n;
374 do {
375 o = packList.get();
376
377 final Pack[] oldList = o.packs;
378 final int j = indexOf(oldList, deadPack);
379 if (j < 0) {
380 break;
381 }
382
383 final Pack[] newList = new Pack[oldList.length - 1];
384 System.arraycopy(oldList, 0, newList, 0, j);
385 System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
386 n = new PackList(o.snapshot, newList);
387 } while (!packList.compareAndSet(o, n));
388 deadPack.close();
389 }
390
391 private static int indexOf(Pack[] list, Pack pack) {
392 for (int i = 0; i < list.length; i++) {
393 if (list[i] == pack) {
394 return i;
395 }
396 }
397 return -1;
398 }
399
400 private PackList scanPacks(PackList original) {
401 synchronized (packList) {
402 PackList o, n;
403 do {
404 o = packList.get();
405 if (o != original) {
406
407
408
409 return o;
410 }
411 n = scanPacksImpl(o);
412 if (n == o) {
413 return n;
414 }
415 } while (!packList.compareAndSet(o, n));
416 return n;
417 }
418 }
419
420 private PackList scanPacksImpl(PackList old) {
421 final Map<String, Pack> forReuse = reuseMap(old);
422 final FileSnapshot snapshot = FileSnapshot.save(directory);
423 Map<String, Map<PackExt, PackFile>> packFilesByExtById = getPackFilesByExtById();
424 List<Pack> list = new ArrayList<>(packFilesByExtById.size());
425 boolean foundNew = false;
426 for (Map<PackExt, PackFile> packFilesByExt : packFilesByExtById
427 .values()) {
428 PackFile packFile = packFilesByExt.get(PACK);
429 if (packFile == null || !packFilesByExt.containsKey(INDEX)) {
430
431
432
433
434 continue;
435 }
436
437 Pack oldPack = forReuse.get(packFile.getName());
438 if (oldPack != null
439 && !oldPack.getFileSnapshot().isModified(packFile)) {
440 forReuse.remove(packFile.getName());
441 list.add(oldPack);
442 continue;
443 }
444
445 list.add(new Pack(packFile, packFilesByExt.get(BITMAP_INDEX)));
446 foundNew = true;
447 }
448
449
450
451
452
453
454 if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
455 old.snapshot.setClean(snapshot);
456 return old;
457 }
458
459 for (Pack p : forReuse.values()) {
460 p.close();
461 }
462
463 if (list.isEmpty()) {
464 return new PackList(snapshot, NO_PACKS.packs);
465 }
466
467 final Pack[] r = list.toArray(new Pack[0]);
468 Arrays.sort(r, Pack.SORT);
469 return new PackList(snapshot, r);
470 }
471
472 private static Map<String, Pack> reuseMap(PackList old) {
473 final Map<String, Pack> forReuse = new HashMap<>();
474 for (Pack p : old.packs) {
475 if (p.invalid()) {
476
477
478
479 p.close();
480 continue;
481 }
482
483 final Pack prior = forReuse.put(p.getPackFile().getName(), p);
484 if (prior != null) {
485
486
487
488
489
490
491 forReuse.put(prior.getPackFile().getName(), prior);
492 p.close();
493 }
494 }
495 return forReuse;
496 }
497
498
499
500
501
502
503
504
505
506
507
508
509
510 private Map<String, Map<PackExt, PackFile>> getPackFilesByExtById() {
511 final String[] nameList = directory.list();
512 if (nameList == null) {
513 return Collections.emptyMap();
514 }
515 Map<String, Map<PackExt, PackFile>> packFilesByExtById = new HashMap<>(
516 nameList.length / 2);
517 for (String name : nameList) {
518 try {
519 PackFile pack = new PackFile(directory, name);
520 if (pack.getPackExt() != null) {
521 Map<PackExt, PackFile> packByExt = packFilesByExtById
522 .get(pack.getId());
523 if (packByExt == null) {
524 packByExt = new EnumMap<>(PackExt.class);
525 packFilesByExtById.put(pack.getId(), packByExt);
526 }
527 packByExt.put(pack.getPackExt(), pack);
528 }
529 } catch (IllegalArgumentException e) {
530 continue;
531 }
532 }
533 return packFilesByExtById;
534 }
535
536 static final class PackList {
537
538 final FileSnapshot snapshot;
539
540
541 final Pack[] packs;
542
543 PackList(FileSnapshot monitor, Pack[] packs) {
544 this.snapshot = monitor;
545 this.packs = packs;
546 }
547 }
548 }