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