1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.file;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
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.BufferedReader;
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.nio.file.Files;
23 import java.nio.file.NoSuchFileException;
24 import java.nio.file.StandardCopyOption;
25 import java.text.MessageFormat;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Set;
36 import java.util.concurrent.atomic.AtomicReference;
37
38 import org.eclipse.jgit.errors.CorruptObjectException;
39 import org.eclipse.jgit.errors.PackInvalidException;
40 import org.eclipse.jgit.errors.PackMismatchException;
41 import org.eclipse.jgit.internal.JGitText;
42 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
43 import org.eclipse.jgit.internal.storage.pack.PackExt;
44 import org.eclipse.jgit.internal.storage.pack.PackWriter;
45 import org.eclipse.jgit.lib.AbbreviatedObjectId;
46 import org.eclipse.jgit.lib.AnyObjectId;
47 import org.eclipse.jgit.lib.Config;
48 import org.eclipse.jgit.lib.ConfigConstants;
49 import org.eclipse.jgit.lib.Constants;
50 import org.eclipse.jgit.lib.ObjectDatabase;
51 import org.eclipse.jgit.lib.ObjectId;
52 import org.eclipse.jgit.lib.ObjectLoader;
53 import org.eclipse.jgit.lib.RepositoryCache;
54 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
55 import org.eclipse.jgit.util.FS;
56 import org.eclipse.jgit.util.FileUtils;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public class ObjectDirectory extends FileObjectDatabase {
79 private static final Logger LOG = LoggerFactory
80 .getLogger(ObjectDirectory.class);
81
82 private static final PackList NO_PACKS = new PackList(
83 FileSnapshot.DIRTY, new PackFile[0]);
84
85
86 private static final int RESOLVE_ABBREV_LIMIT = 256;
87
88 private final AlternateHandle handle = new AlternateHandle(this);
89
90 private final Config config;
91
92 private final File objects;
93
94 private final File infoDirectory;
95
96 private final File packDirectory;
97
98 private final File preservedDirectory;
99
100 private final File alternatesFile;
101
102 private final FS fs;
103
104 private final AtomicReference<AlternateHandle[]> alternates;
105
106 private final UnpackedObjectCache unpackedObjectCache;
107
108 private final File shallowFile;
109
110 private FileSnapshot shallowFileSnapshot = FileSnapshot.DIRTY;
111
112 private Set<ObjectId> shallowCommitsIds;
113
114 final AtomicReference<PackList> packList;
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 public ObjectDirectory(final Config cfg, final File dir,
135 File[] alternatePaths, FS fs, File shallowFile) throws IOException {
136 config = cfg;
137 objects = dir;
138 infoDirectory = new File(objects, "info");
139 packDirectory = new File(objects, "pack");
140 preservedDirectory = new File(packDirectory, "preserved");
141 alternatesFile = new File(objects, Constants.INFO_ALTERNATES);
142 packList = new AtomicReference<>(NO_PACKS);
143 unpackedObjectCache = new UnpackedObjectCache();
144 this.fs = fs;
145 this.shallowFile = shallowFile;
146
147 alternates = new AtomicReference<>();
148 if (alternatePaths != null) {
149 AlternateHandle[] alt;
150
151 alt = new AlternateHandle[alternatePaths.length];
152 for (int i = 0; i < alternatePaths.length; i++)
153 alt[i] = openAlternate(alternatePaths[i]);
154 alternates.set(alt);
155 }
156 }
157
158
159 @Override
160 public final File getDirectory() {
161 return objects;
162 }
163
164
165
166
167
168
169 public final File getPackDirectory() {
170 return packDirectory;
171 }
172
173
174
175
176
177
178 public final File getPreservedDirectory() {
179 return preservedDirectory;
180 }
181
182
183 @Override
184 public boolean exists() {
185 return fs.exists(objects);
186 }
187
188
189 @Override
190 public void create() throws IOException {
191 FileUtils.mkdirs(objects);
192 FileUtils.mkdir(infoDirectory);
193 FileUtils.mkdir(packDirectory);
194 }
195
196
197 @Override
198 public ObjectDirectoryInserter newInserter() {
199 return new ObjectDirectoryInserter(this, config);
200 }
201
202
203
204
205
206
207
208 public PackInserter newPackInserter() {
209 return new PackInserter(this);
210 }
211
212
213 @Override
214 public void close() {
215 unpackedObjectCache.clear();
216
217 final PackList packs = packList.get();
218 if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) {
219 for (PackFile p : packs.packs)
220 p.close();
221 }
222
223
224 AlternateHandle[] alt = alternates.get();
225 if (alt != null && alternates.compareAndSet(alt, null)) {
226 for(AlternateHandle od : alt)
227 od.close();
228 }
229 }
230
231
232 @Override
233 public Collection<PackFile> getPacks() {
234 PackList list = packList.get();
235 if (list == NO_PACKS)
236 list = scanPacks(list);
237 PackFile[] packs = list.packs;
238 return Collections.unmodifiableCollection(Arrays.asList(packs));
239 }
240
241
242
243
244
245
246 @Override
247 public PackFile openPack(File pack)
248 throws IOException {
249 final String p = pack.getName();
250 if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
251 throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, pack));
252
253
254
255
256 int extensions = PACK.getBit() | INDEX.getBit();
257 final String base = p.substring(0, p.length() - 4);
258 for (PackExt ext : PackExt.values()) {
259 if ((extensions & ext.getBit()) == 0) {
260 final String name = base + ext.getExtension();
261 if (new File(pack.getParentFile(), name).exists())
262 extensions |= ext.getBit();
263 }
264 }
265
266 PackFile res = new PackFile(pack, extensions);
267 insertPack(res);
268 return res;
269 }
270
271
272 @Override
273 public String toString() {
274 return "ObjectDirectory[" + getDirectory() + "]";
275 }
276
277
278 @Override
279 public boolean has(AnyObjectId objectId) {
280 return unpackedObjectCache.isUnpacked(objectId)
281 || hasPackedInSelfOrAlternate(objectId, null)
282 || hasLooseInSelfOrAlternate(objectId, null);
283 }
284
285 private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId,
286 Set<AlternateHandle.Id> skips) {
287 if (hasPackedObject(objectId)) {
288 return true;
289 }
290 skips = addMe(skips);
291 for (AlternateHandle alt : myAlternates()) {
292 if (!skips.contains(alt.getId())) {
293 if (alt.db.hasPackedInSelfOrAlternate(objectId, skips)) {
294 return true;
295 }
296 }
297 }
298 return false;
299 }
300
301 private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId,
302 Set<AlternateHandle.Id> skips) {
303 if (fileFor(objectId).exists()) {
304 return true;
305 }
306 skips = addMe(skips);
307 for (AlternateHandle alt : myAlternates()) {
308 if (!skips.contains(alt.getId())) {
309 if (alt.db.hasLooseInSelfOrAlternate(objectId, skips)) {
310 return true;
311 }
312 }
313 }
314 return false;
315 }
316
317 boolean hasPackedObject(AnyObjectId objectId) {
318 PackList pList;
319 do {
320 pList = packList.get();
321 for (PackFile p : pList.packs) {
322 try {
323 if (p.hasObject(objectId))
324 return true;
325 } catch (IOException e) {
326
327
328
329 LOG.warn(MessageFormat.format(
330 JGitText.get().unableToReadPackfile,
331 p.getPackFile().getAbsolutePath()), e);
332 removePack(p);
333 }
334 }
335 } while (searchPacksAgain(pList));
336 return false;
337 }
338
339 @Override
340 void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
341 throws IOException {
342 resolve(matches, id, null);
343 }
344
345 private void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
346 Set<AlternateHandle.Id> skips)
347 throws IOException {
348
349
350 int oldSize = matches.size();
351 PackList pList;
352 do {
353 pList = packList.get();
354 for (PackFile p : pList.packs) {
355 try {
356 p.resolve(matches, id, RESOLVE_ABBREV_LIMIT);
357 p.resetTransientErrorCount();
358 } catch (IOException e) {
359 handlePackError(e, p);
360 }
361 if (matches.size() > RESOLVE_ABBREV_LIMIT)
362 return;
363 }
364 } while (matches.size() == oldSize && searchPacksAgain(pList));
365
366 String fanOut = id.name().substring(0, 2);
367 String[] entries = new File(getDirectory(), fanOut).list();
368 if (entries != null) {
369 for (String e : entries) {
370 if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
371 continue;
372 try {
373 ObjectId entId = ObjectId.fromString(fanOut + e);
374 if (id.prefixCompare(entId) == 0)
375 matches.add(entId);
376 } catch (IllegalArgumentException notId) {
377 continue;
378 }
379 if (matches.size() > RESOLVE_ABBREV_LIMIT)
380 return;
381 }
382 }
383
384 skips = addMe(skips);
385 for (AlternateHandle alt : myAlternates()) {
386 if (!skips.contains(alt.getId())) {
387 alt.db.resolve(matches, id, skips);
388 if (matches.size() > RESOLVE_ABBREV_LIMIT) {
389 return;
390 }
391 }
392 }
393 }
394
395 @Override
396 ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
397 throws IOException {
398 if (unpackedObjectCache.isUnpacked(objectId)) {
399 ObjectLoader ldr = openLooseObject(curs, objectId);
400 if (ldr != null) {
401 return ldr;
402 }
403 }
404 ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId, null);
405 if (ldr != null) {
406 return ldr;
407 }
408 return openLooseFromSelfOrAlternate(curs, objectId, null);
409 }
410
411 private ObjectLoader openPackedFromSelfOrAlternate(WindowCursor curs,
412 AnyObjectId objectId, Set<AlternateHandle.Id> skips) {
413 ObjectLoader ldr = openPackedObject(curs, objectId);
414 if (ldr != null) {
415 return ldr;
416 }
417 skips = addMe(skips);
418 for (AlternateHandle alt : myAlternates()) {
419 if (!skips.contains(alt.getId())) {
420 ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId, skips);
421 if (ldr != null) {
422 return ldr;
423 }
424 }
425 }
426 return null;
427 }
428
429 private ObjectLoader openLooseFromSelfOrAlternate(WindowCursor curs,
430 AnyObjectId objectId, Set<AlternateHandle.Id> skips)
431 throws IOException {
432 ObjectLoader ldr = openLooseObject(curs, objectId);
433 if (ldr != null) {
434 return ldr;
435 }
436 skips = addMe(skips);
437 for (AlternateHandle alt : myAlternates()) {
438 if (!skips.contains(alt.getId())) {
439 ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId, skips);
440 if (ldr != null) {
441 return ldr;
442 }
443 }
444 }
445 return null;
446 }
447
448 ObjectLoader openPackedObject(WindowCursor curs, AnyObjectId objectId) {
449 PackList pList;
450 do {
451 SEARCH: for (;;) {
452 pList = packList.get();
453 for (PackFile p : pList.packs) {
454 try {
455 ObjectLoader ldr = p.get(curs, objectId);
456 p.resetTransientErrorCount();
457 if (ldr != null)
458 return ldr;
459 } catch (PackMismatchException e) {
460
461 if (searchPacksAgain(pList))
462 continue SEARCH;
463 } catch (IOException e) {
464 handlePackError(e, p);
465 }
466 }
467 break SEARCH;
468 }
469 } while (searchPacksAgain(pList));
470 return null;
471 }
472
473 @Override
474 ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
475 throws IOException {
476 File path = fileFor(id);
477 try (FileInputStream in = new FileInputStream(path)) {
478 unpackedObjectCache.add(id);
479 return UnpackedObject.open(in, path, id, curs);
480 } catch (FileNotFoundException noFile) {
481 if (path.exists()) {
482 throw noFile;
483 }
484 unpackedObjectCache.remove(id);
485 return null;
486 }
487 }
488
489 @Override
490 long getObjectSize(WindowCursor curs, AnyObjectId id)
491 throws IOException {
492 if (unpackedObjectCache.isUnpacked(id)) {
493 long len = getLooseObjectSize(curs, id);
494 if (0 <= len) {
495 return len;
496 }
497 }
498 long len = getPackedSizeFromSelfOrAlternate(curs, id, null);
499 if (0 <= len) {
500 return len;
501 }
502 return getLooseSizeFromSelfOrAlternate(curs, id, null);
503 }
504
505 private long getPackedSizeFromSelfOrAlternate(WindowCursor curs,
506 AnyObjectId id, Set<AlternateHandle.Id> skips) {
507 long len = getPackedObjectSize(curs, id);
508 if (0 <= len) {
509 return len;
510 }
511 skips = addMe(skips);
512 for (AlternateHandle alt : myAlternates()) {
513 if (!skips.contains(alt.getId())) {
514 len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id, skips);
515 if (0 <= len) {
516 return len;
517 }
518 }
519 }
520 return -1;
521 }
522
523 private long getLooseSizeFromSelfOrAlternate(WindowCursor curs,
524 AnyObjectId id, Set<AlternateHandle.Id> skips) throws IOException {
525 long len = getLooseObjectSize(curs, id);
526 if (0 <= len) {
527 return len;
528 }
529 skips = addMe(skips);
530 for (AlternateHandle alt : myAlternates()) {
531 if (!skips.contains(alt.getId())) {
532 len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id, skips);
533 if (0 <= len) {
534 return len;
535 }
536 }
537 }
538 return -1;
539 }
540
541 private long getPackedObjectSize(WindowCursor curs, AnyObjectId id) {
542 PackList pList;
543 do {
544 SEARCH: for (;;) {
545 pList = packList.get();
546 for (PackFile p : pList.packs) {
547 try {
548 long len = p.getObjectSize(curs, id);
549 p.resetTransientErrorCount();
550 if (0 <= len)
551 return len;
552 } catch (PackMismatchException e) {
553
554 if (searchPacksAgain(pList))
555 continue SEARCH;
556 } catch (IOException e) {
557 handlePackError(e, p);
558 }
559 }
560 break SEARCH;
561 }
562 } while (searchPacksAgain(pList));
563 return -1;
564 }
565
566 private long getLooseObjectSize(WindowCursor curs, AnyObjectId id)
567 throws IOException {
568 File f = fileFor(id);
569 try (FileInputStream in = new FileInputStream(f)) {
570 unpackedObjectCache.add(id);
571 return UnpackedObject.getSize(in, id, curs);
572 } catch (FileNotFoundException noFile) {
573 if (f.exists()) {
574 throw noFile;
575 }
576 unpackedObjectCache.remove(id);
577 return -1;
578 }
579 }
580
581 @Override
582 void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
583 WindowCursor curs) throws IOException {
584 selectObjectRepresentation(packer, otp, curs, null);
585 }
586
587 private void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
588 WindowCursor curs, Set<AlternateHandle.Id> skips) throws IOException {
589 PackList pList = packList.get();
590 SEARCH: for (;;) {
591 for (PackFile p : pList.packs) {
592 try {
593 LocalObjectRepresentation rep = p.representation(curs, otp);
594 p.resetTransientErrorCount();
595 if (rep != null)
596 packer.select(otp, rep);
597 } catch (PackMismatchException e) {
598
599
600 pList = scanPacks(pList);
601 continue SEARCH;
602 } catch (IOException e) {
603 handlePackError(e, p);
604 }
605 }
606 break SEARCH;
607 }
608
609 skips = addMe(skips);
610 for (AlternateHandle h : myAlternates()) {
611 if (!skips.contains(h.getId())) {
612 h.db.selectObjectRepresentation(packer, otp, curs, skips);
613 }
614 }
615 }
616
617 private void handlePackError(IOException e, PackFile p) {
618 String warnTmpl = null;
619 int transientErrorCount = 0;
620 String errTmpl = JGitText.get().exceptionWhileReadingPack;
621 if ((e instanceof CorruptObjectException)
622 || (e instanceof PackInvalidException)) {
623 warnTmpl = JGitText.get().corruptPack;
624 LOG.warn(MessageFormat.format(warnTmpl,
625 p.getPackFile().getAbsolutePath()), e);
626
627 removePack(p);
628 } else if (e instanceof FileNotFoundException) {
629 if (p.getPackFile().exists()) {
630 errTmpl = JGitText.get().packInaccessible;
631 transientErrorCount = p.incrementTransientErrorCount();
632 } else {
633 warnTmpl = JGitText.get().packWasDeleted;
634 removePack(p);
635 }
636 } else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
637 warnTmpl = JGitText.get().packHandleIsStale;
638 removePack(p);
639 } else {
640 transientErrorCount = p.incrementTransientErrorCount();
641 }
642 if (warnTmpl != null) {
643 LOG.warn(MessageFormat.format(warnTmpl,
644 p.getPackFile().getAbsolutePath()), e);
645 } else {
646 if (doLogExponentialBackoff(transientErrorCount)) {
647
648
649 LOG.error(MessageFormat.format(errTmpl,
650 p.getPackFile().getAbsolutePath(),
651 Integer.valueOf(transientErrorCount)), e);
652 }
653 }
654 }
655
656
657
658
659
660
661 private boolean doLogExponentialBackoff(int n) {
662 return (n & (n - 1)) == 0;
663 }
664
665 @Override
666 InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id,
667 boolean createDuplicate) throws IOException {
668
669
670 if (unpackedObjectCache.isUnpacked(id)) {
671 FileUtils.delete(tmp, FileUtils.RETRY);
672 return InsertLooseObjectResult.EXISTS_LOOSE;
673 }
674 if (!createDuplicate && has(id)) {
675 FileUtils.delete(tmp, FileUtils.RETRY);
676 return InsertLooseObjectResult.EXISTS_PACKED;
677 }
678
679 final File dst = fileFor(id);
680 if (dst.exists()) {
681
682
683
684
685 FileUtils.delete(tmp, FileUtils.RETRY);
686 return InsertLooseObjectResult.EXISTS_LOOSE;
687 }
688
689 try {
690 return tryMove(tmp, dst, id);
691 } catch (NoSuchFileException e) {
692
693
694
695
696
697
698 FileUtils.mkdir(dst.getParentFile(), true);
699 } catch (IOException e) {
700
701
702 LOG.error(e.getMessage(), e);
703 FileUtils.delete(tmp, FileUtils.RETRY);
704 return InsertLooseObjectResult.FAILURE;
705 }
706
707 try {
708 return tryMove(tmp, dst, id);
709 } catch (IOException e) {
710
711
712
713
714 LOG.error(e.getMessage(), e);
715 FileUtils.delete(tmp, FileUtils.RETRY);
716 return InsertLooseObjectResult.FAILURE;
717 }
718 }
719
720 private InsertLooseObjectResult tryMove(File tmp, File dst,
721 ObjectId id)
722 throws IOException {
723 Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
724 StandardCopyOption.ATOMIC_MOVE);
725 dst.setReadOnly();
726 unpackedObjectCache.add(id);
727 return InsertLooseObjectResult.INSERTED;
728 }
729
730 boolean searchPacksAgain(PackList old) {
731
732
733
734
735
736
737 boolean trustFolderStat = config.getBoolean(
738 ConfigConstants.CONFIG_CORE_SECTION,
739 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
740
741 return ((!trustFolderStat) || old.snapshot.isModified(packDirectory))
742 && old != scanPacks(old);
743 }
744
745 @Override
746 Config getConfig() {
747 return config;
748 }
749
750 @Override
751 FS getFS() {
752 return fs;
753 }
754
755 @Override
756 Set<ObjectId> getShallowCommits() throws IOException {
757 if (shallowFile == null || !shallowFile.isFile())
758 return Collections.emptySet();
759
760 if (shallowFileSnapshot == null
761 || shallowFileSnapshot.isModified(shallowFile)) {
762 shallowCommitsIds = new HashSet<>();
763
764 try (BufferedReader reader = open(shallowFile)) {
765 String line;
766 while ((line = reader.readLine()) != null) {
767 try {
768 shallowCommitsIds.add(ObjectId.fromString(line));
769 } catch (IllegalArgumentException ex) {
770 throw new IOException(MessageFormat
771 .format(JGitText.get().badShallowLine, line),
772 ex);
773 }
774 }
775 }
776
777 shallowFileSnapshot = FileSnapshot.save(shallowFile);
778 }
779
780 return shallowCommitsIds;
781 }
782
783 private void insertPack(PackFile pf) {
784 PackList o, n;
785 do {
786 o = packList.get();
787
788
789
790
791
792 final PackFile[] oldList = o.packs;
793 final String name = pf.getPackFile().getName();
794 for (PackFile p : oldList) {
795 if (name.equals(p.getPackFile().getName()))
796 return;
797 }
798
799 final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[1 + oldList.length];
800 newList[0] = pf;
801 System.arraycopy(oldList, 0, newList, 1, oldList.length);
802 n = new PackList(o.snapshot, newList);
803 } while (!packList.compareAndSet(o, n));
804 }
805
806 private void removePack(PackFile deadPack) {
807 PackList o, n;
808 do {
809 o = packList.get();
810
811 final PackFile[] oldList = o.packs;
812 final int j = indexOf(oldList, deadPack);
813 if (j < 0)
814 break;
815
816 final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[oldList.length - 1];
817 System.arraycopy(oldList, 0, newList, 0, j);
818 System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
819 n = new PackList(o.snapshot, newList);
820 } while (!packList.compareAndSet(o, n));
821 deadPack.close();
822 }
823
824 private static int indexOf(PackFile../../../../../../org/eclipse/jgit/internal/storage/file/PackFile.html#PackFile">PackFile[] list, PackFile pack) {
825 for (int i = 0; i < list.length; i++) {
826 if (list[i] == pack)
827 return i;
828 }
829 return -1;
830 }
831
832 private PackList scanPacks(PackList original) {
833 synchronized (packList) {
834 PackList o, n;
835 do {
836 o = packList.get();
837 if (o != original) {
838
839
840
841 return o;
842 }
843 n = scanPacksImpl(o);
844 if (n == o)
845 return n;
846 } while (!packList.compareAndSet(o, n));
847 return n;
848 }
849 }
850
851 private PackList scanPacksImpl(PackList old) {
852 final Map<String, PackFile> forReuse = reuseMap(old);
853 final FileSnapshot snapshot = FileSnapshot.save(packDirectory);
854 final Set<String> names = listPackDirectory();
855 final List<PackFile> list = new ArrayList<>(names.size() >> 2);
856 boolean foundNew = false;
857 for (String indexName : names) {
858
859
860 if (indexName.length() != 49 || !indexName.endsWith(".idx"))
861 continue;
862
863 final String base = indexName.substring(0, indexName.length() - 3);
864 int extensions = 0;
865 for (PackExt ext : PackExt.values()) {
866 if (names.contains(base + ext.getExtension()))
867 extensions |= ext.getBit();
868 }
869
870 if ((extensions & PACK.getBit()) == 0) {
871
872
873
874
875 continue;
876 }
877
878 final String packName = base + PACK.getExtension();
879 final File packFile = new File(packDirectory, packName);
880 final PackFile oldPack = forReuse.get(packName);
881 if (oldPack != null
882 && !oldPack.getFileSnapshot().isModified(packFile)) {
883 forReuse.remove(packName);
884 list.add(oldPack);
885 continue;
886 }
887
888 list.add(new PackFile(packFile, extensions));
889 foundNew = true;
890 }
891
892
893
894
895
896
897 if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
898 old.snapshot.setClean(snapshot);
899 return old;
900 }
901
902 for (PackFile p : forReuse.values()) {
903 p.close();
904 }
905
906 if (list.isEmpty())
907 return new PackList(snapshot, NO_PACKS.packs);
908
909 final PackFilefile/PackFile.html#PackFile">PackFile[] r = list.toArray(new PackFile[0]);
910 Arrays.sort(r, PackFile.SORT);
911 return new PackList(snapshot, r);
912 }
913
914 private static Map<String, PackFile> reuseMap(PackList old) {
915 final Map<String, PackFile> forReuse = new HashMap<>();
916 for (PackFile p : old.packs) {
917 if (p.invalid()) {
918
919
920
921 p.close();
922 continue;
923 }
924
925 final PackFile prior = forReuse.put(p.getPackFile().getName(), p);
926 if (prior != null) {
927
928
929
930
931
932
933 forReuse.put(prior.getPackFile().getName(), prior);
934 p.close();
935 }
936 }
937 return forReuse;
938 }
939
940 private Set<String> listPackDirectory() {
941 final String[] nameList = packDirectory.list();
942 if (nameList == null)
943 return Collections.emptySet();
944 final Set<String> nameSet = new HashSet<>(nameList.length << 1);
945 for (String name : nameList) {
946 if (name.startsWith("pack-"))
947 nameSet.add(name);
948 }
949 return nameSet;
950 }
951
952 void closeAllPackHandles(File packFile) {
953
954
955
956
957 if (packFile.exists()) {
958 for (PackFile p : getPacks()) {
959 if (packFile.getPath().equals(p.getPackFile().getPath())) {
960 p.close();
961 break;
962 }
963 }
964 }
965 }
966
967 AlternateHandle[] myAlternates() {
968 AlternateHandle[] alt = alternates.get();
969 if (alt == null) {
970 synchronized (alternates) {
971 alt = alternates.get();
972 if (alt == null) {
973 try {
974 alt = loadAlternates();
975 } catch (IOException e) {
976 alt = new AlternateHandle[0];
977 }
978 alternates.set(alt);
979 }
980 }
981 }
982 return alt;
983 }
984
985 Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) {
986 if (skips == null) {
987 skips = new HashSet<>();
988 }
989 skips.add(handle.getId());
990 return skips;
991 }
992
993 private AlternateHandle[] loadAlternates() throws IOException {
994 final List<AlternateHandle> l = new ArrayList<>(4);
995 try (BufferedReader br = open(alternatesFile)) {
996 String line;
997 while ((line = br.readLine()) != null) {
998 l.add(openAlternate(line));
999 }
1000 }
1001 return l.toArray(new AlternateHandle[0]);
1002 }
1003
1004 private static BufferedReader open(File f)
1005 throws IOException, FileNotFoundException {
1006 return Files.newBufferedReader(f.toPath(), UTF_8);
1007 }
1008
1009 private AlternateHandle openAlternate(String location)
1010 throws IOException {
1011 final File objdir = fs.resolve(objects, location);
1012 return openAlternate(objdir);
1013 }
1014
1015 private AlternateHandle openAlternate(File objdir) throws IOException {
1016 final File parent = objdir.getParentFile();
1017 if (FileKey.isGitRepository(parent, fs)) {
1018 FileKey key = FileKey.exact(parent, fs);
1019 FileRepository db = (FileRepository) RepositoryCache.open(key);
1020 return new AlternateRepository(db);
1021 }
1022
1023 ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs, null);
1024 return new AlternateHandle(db);
1025 }
1026
1027
1028
1029
1030
1031
1032 @Override
1033 public File fileFor(AnyObjectId objectId) {
1034 String n = objectId.name();
1035 String d = n.substring(0, 2);
1036 String f = n.substring(2);
1037 return new File(new File(getDirectory(), d), f);
1038 }
1039
1040 static final class PackList {
1041
1042 final FileSnapshot snapshot;
1043
1044
1045 final PackFile[] packs;
1046
1047 PackList(FileSnapshot monitor, PackFile[] packs) {
1048 this.snapshot = monitor;
1049 this.packs = packs;
1050 }
1051 }
1052
1053 static class AlternateHandle {
1054 static class Id {
1055 String alternateId;
1056
1057 public Id(File object) {
1058 try {
1059 this.alternateId = object.getCanonicalPath();
1060 } catch (Exception e) {
1061 alternateId = null;
1062 }
1063 }
1064
1065 @Override
1066 public boolean equals(Object o) {
1067 if (o == this) {
1068 return true;
1069 }
1070 if (o == null || !(o instanceof Id)) {
1071 return false;
1072 }
1073 Id aId = (Id) o;
1074 return Objects.equals(alternateId, aId.alternateId);
1075 }
1076
1077 @Override
1078 public int hashCode() {
1079 if (alternateId == null) {
1080 return 1;
1081 }
1082 return alternateId.hashCode();
1083 }
1084 }
1085
1086 final ObjectDirectory db;
1087
1088 AlternateHandle(ObjectDirectory db) {
1089 this.db = db;
1090 }
1091
1092 void close() {
1093 db.close();
1094 }
1095
1096 public Id getId(){
1097 return db.getAlternateId();
1098 }
1099 }
1100
1101 static class AlternateRepository extends AlternateHandle {
1102 final FileRepository repository;
1103
1104 AlternateRepository(FileRepository r) {
1105 super(r.getObjectDatabase());
1106 repository = r;
1107 }
1108
1109 @Override
1110 void close() {
1111 repository.close();
1112 }
1113 }
1114
1115
1116 @Override
1117 public ObjectDatabase newCachedDatabase() {
1118 return newCachedFileObjectDatabase();
1119 }
1120
1121 CachedObjectDirectory newCachedFileObjectDatabase() {
1122 return new CachedObjectDirectory(this);
1123 }
1124
1125 AlternateHandle.Id getAlternateId() {
1126 return new AlternateHandle.Id(objects);
1127 }
1128 }