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