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.AtomicMoveNotSupportedException;
23 import java.nio.file.Files;
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 try {
689 Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
690 StandardCopyOption.ATOMIC_MOVE);
691 dst.setReadOnly();
692 unpackedObjectCache.add(id);
693 return InsertLooseObjectResult.INSERTED;
694 } catch (AtomicMoveNotSupportedException e) {
695 LOG.error(e.getMessage(), e);
696 } catch (IOException e) {
697
698 }
699
700
701
702
703
704 FileUtils.mkdir(dst.getParentFile(), true);
705 try {
706 Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
707 StandardCopyOption.ATOMIC_MOVE);
708 dst.setReadOnly();
709 unpackedObjectCache.add(id);
710 return InsertLooseObjectResult.INSERTED;
711 } catch (AtomicMoveNotSupportedException e) {
712 LOG.error(e.getMessage(), e);
713 } catch (IOException e) {
714 LOG.debug(e.getMessage(), e);
715 }
716
717 if (!createDuplicate && has(id)) {
718 FileUtils.delete(tmp, FileUtils.RETRY);
719 return InsertLooseObjectResult.EXISTS_PACKED;
720 }
721
722
723
724
725
726
727 FileUtils.delete(tmp, FileUtils.RETRY);
728 return InsertLooseObjectResult.FAILURE;
729 }
730
731 boolean searchPacksAgain(PackList old) {
732
733
734
735
736
737
738 boolean trustFolderStat = config.getBoolean(
739 ConfigConstants.CONFIG_CORE_SECTION,
740 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
741
742 return ((!trustFolderStat) || old.snapshot.isModified(packDirectory))
743 && old != scanPacks(old);
744 }
745
746 @Override
747 Config getConfig() {
748 return config;
749 }
750
751 @Override
752 FS getFS() {
753 return fs;
754 }
755
756 @Override
757 Set<ObjectId> getShallowCommits() throws IOException {
758 if (shallowFile == null || !shallowFile.isFile())
759 return Collections.emptySet();
760
761 if (shallowFileSnapshot == null
762 || shallowFileSnapshot.isModified(shallowFile)) {
763 shallowCommitsIds = new HashSet<>();
764
765 try (BufferedReader reader = open(shallowFile)) {
766 String line;
767 while ((line = reader.readLine()) != null) {
768 try {
769 shallowCommitsIds.add(ObjectId.fromString(line));
770 } catch (IllegalArgumentException ex) {
771 throw new IOException(MessageFormat
772 .format(JGitText.get().badShallowLine, line),
773 ex);
774 }
775 }
776 }
777
778 shallowFileSnapshot = FileSnapshot.save(shallowFile);
779 }
780
781 return shallowCommitsIds;
782 }
783
784 private void insertPack(PackFile pf) {
785 PackList o, n;
786 do {
787 o = packList.get();
788
789
790
791
792
793 final PackFile[] oldList = o.packs;
794 final String name = pf.getPackFile().getName();
795 for (PackFile p : oldList) {
796 if (name.equals(p.getPackFile().getName()))
797 return;
798 }
799
800 final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[1 + oldList.length];
801 newList[0] = pf;
802 System.arraycopy(oldList, 0, newList, 1, oldList.length);
803 n = new PackList(o.snapshot, newList);
804 } while (!packList.compareAndSet(o, n));
805 }
806
807 private void removePack(PackFile deadPack) {
808 PackList o, n;
809 do {
810 o = packList.get();
811
812 final PackFile[] oldList = o.packs;
813 final int j = indexOf(oldList, deadPack);
814 if (j < 0)
815 break;
816
817 final PackFiletorage/file/PackFile.html#PackFile">PackFile[] newList = new PackFile[oldList.length - 1];
818 System.arraycopy(oldList, 0, newList, 0, j);
819 System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
820 n = new PackList(o.snapshot, newList);
821 } while (!packList.compareAndSet(o, n));
822 deadPack.close();
823 }
824
825 private static int indexOf(PackFile../../../../../../org/eclipse/jgit/internal/storage/file/PackFile.html#PackFile">PackFile[] list, PackFile pack) {
826 for (int i = 0; i < list.length; i++) {
827 if (list[i] == pack)
828 return i;
829 }
830 return -1;
831 }
832
833 private PackList scanPacks(PackList original) {
834 synchronized (packList) {
835 PackList o, n;
836 do {
837 o = packList.get();
838 if (o != original) {
839
840
841
842 return o;
843 }
844 n = scanPacksImpl(o);
845 if (n == o)
846 return n;
847 } while (!packList.compareAndSet(o, n));
848 return n;
849 }
850 }
851
852 private PackList scanPacksImpl(PackList old) {
853 final Map<String, PackFile> forReuse = reuseMap(old);
854 final FileSnapshot snapshot = FileSnapshot.save(packDirectory);
855 final Set<String> names = listPackDirectory();
856 final List<PackFile> list = new ArrayList<>(names.size() >> 2);
857 boolean foundNew = false;
858 for (String indexName : names) {
859
860
861 if (indexName.length() != 49 || !indexName.endsWith(".idx"))
862 continue;
863
864 final String base = indexName.substring(0, indexName.length() - 3);
865 int extensions = 0;
866 for (PackExt ext : PackExt.values()) {
867 if (names.contains(base + ext.getExtension()))
868 extensions |= ext.getBit();
869 }
870
871 if ((extensions & PACK.getBit()) == 0) {
872
873
874
875
876 continue;
877 }
878
879 final String packName = base + PACK.getExtension();
880 final File packFile = new File(packDirectory, packName);
881 final PackFile oldPack = forReuse.get(packName);
882 if (oldPack != null
883 && !oldPack.getFileSnapshot().isModified(packFile)) {
884 forReuse.remove(packName);
885 list.add(oldPack);
886 continue;
887 }
888
889 list.add(new PackFile(packFile, extensions));
890 foundNew = true;
891 }
892
893
894
895
896
897
898 if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
899 old.snapshot.setClean(snapshot);
900 return old;
901 }
902
903 for (PackFile p : forReuse.values()) {
904 p.close();
905 }
906
907 if (list.isEmpty())
908 return new PackList(snapshot, NO_PACKS.packs);
909
910 final PackFilefile/PackFile.html#PackFile">PackFile[] r = list.toArray(new PackFile[0]);
911 Arrays.sort(r, PackFile.SORT);
912 return new PackList(snapshot, r);
913 }
914
915 private static Map<String, PackFile> reuseMap(PackList old) {
916 final Map<String, PackFile> forReuse = new HashMap<>();
917 for (PackFile p : old.packs) {
918 if (p.invalid()) {
919
920
921
922 p.close();
923 continue;
924 }
925
926 final PackFile prior = forReuse.put(p.getPackFile().getName(), p);
927 if (prior != null) {
928
929
930
931
932
933
934 forReuse.put(prior.getPackFile().getName(), prior);
935 p.close();
936 }
937 }
938 return forReuse;
939 }
940
941 private Set<String> listPackDirectory() {
942 final String[] nameList = packDirectory.list();
943 if (nameList == null)
944 return Collections.emptySet();
945 final Set<String> nameSet = new HashSet<>(nameList.length << 1);
946 for (String name : nameList) {
947 if (name.startsWith("pack-"))
948 nameSet.add(name);
949 }
950 return nameSet;
951 }
952
953 void closeAllPackHandles(File packFile) {
954
955
956
957
958 if (packFile.exists()) {
959 for (PackFile p : getPacks()) {
960 if (packFile.getPath().equals(p.getPackFile().getPath())) {
961 p.close();
962 break;
963 }
964 }
965 }
966 }
967
968 AlternateHandle[] myAlternates() {
969 AlternateHandle[] alt = alternates.get();
970 if (alt == null) {
971 synchronized (alternates) {
972 alt = alternates.get();
973 if (alt == null) {
974 try {
975 alt = loadAlternates();
976 } catch (IOException e) {
977 alt = new AlternateHandle[0];
978 }
979 alternates.set(alt);
980 }
981 }
982 }
983 return alt;
984 }
985
986 Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) {
987 if (skips == null) {
988 skips = new HashSet<>();
989 }
990 skips.add(handle.getId());
991 return skips;
992 }
993
994 private AlternateHandle[] loadAlternates() throws IOException {
995 final List<AlternateHandle> l = new ArrayList<>(4);
996 try (BufferedReader br = open(alternatesFile)) {
997 String line;
998 while ((line = br.readLine()) != null) {
999 l.add(openAlternate(line));
1000 }
1001 }
1002 return l.toArray(new AlternateHandle[0]);
1003 }
1004
1005 private static BufferedReader open(File f)
1006 throws IOException, FileNotFoundException {
1007 return Files.newBufferedReader(f.toPath(), UTF_8);
1008 }
1009
1010 private AlternateHandle openAlternate(String location)
1011 throws IOException {
1012 final File objdir = fs.resolve(objects, location);
1013 return openAlternate(objdir);
1014 }
1015
1016 private AlternateHandle openAlternate(File objdir) throws IOException {
1017 final File parent = objdir.getParentFile();
1018 if (FileKey.isGitRepository(parent, fs)) {
1019 FileKey key = FileKey.exact(parent, fs);
1020 FileRepository db = (FileRepository) RepositoryCache.open(key);
1021 return new AlternateRepository(db);
1022 }
1023
1024 ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs, null);
1025 return new AlternateHandle(db);
1026 }
1027
1028
1029
1030
1031
1032
1033 @Override
1034 public File fileFor(AnyObjectId objectId) {
1035 String n = objectId.name();
1036 String d = n.substring(0, 2);
1037 String f = n.substring(2);
1038 return new File(new File(getDirectory(), d), f);
1039 }
1040
1041 static final class PackList {
1042
1043 final FileSnapshot snapshot;
1044
1045
1046 final PackFile[] packs;
1047
1048 PackList(FileSnapshot monitor, PackFile[] packs) {
1049 this.snapshot = monitor;
1050 this.packs = packs;
1051 }
1052 }
1053
1054 static class AlternateHandle {
1055 static class Id {
1056 String alternateId;
1057
1058 public Id(File object) {
1059 try {
1060 this.alternateId = object.getCanonicalPath();
1061 } catch (Exception e) {
1062 alternateId = null;
1063 }
1064 }
1065
1066 @Override
1067 public boolean equals(Object o) {
1068 if (o == this) {
1069 return true;
1070 }
1071 if (o == null || !(o instanceof Id)) {
1072 return false;
1073 }
1074 Id aId = (Id) o;
1075 return Objects.equals(alternateId, aId.alternateId);
1076 }
1077
1078 @Override
1079 public int hashCode() {
1080 if (alternateId == null) {
1081 return 1;
1082 }
1083 return alternateId.hashCode();
1084 }
1085 }
1086
1087 final ObjectDirectory db;
1088
1089 AlternateHandle(ObjectDirectory db) {
1090 this.db = db;
1091 }
1092
1093 void close() {
1094 db.close();
1095 }
1096
1097 public Id getId(){
1098 return db.getAlternateId();
1099 }
1100 }
1101
1102 static class AlternateRepository extends AlternateHandle {
1103 final FileRepository repository;
1104
1105 AlternateRepository(FileRepository r) {
1106 super(r.getObjectDatabase());
1107 repository = r;
1108 }
1109
1110 @Override
1111 void close() {
1112 repository.close();
1113 }
1114 }
1115
1116
1117 @Override
1118 public ObjectDatabase newCachedDatabase() {
1119 return newCachedFileObjectDatabase();
1120 }
1121
1122 CachedObjectDirectory newCachedFileObjectDatabase() {
1123 return new CachedObjectDirectory(this);
1124 }
1125
1126 AlternateHandle.Id getAlternateId() {
1127 return new AlternateHandle.Id(objects);
1128 }
1129 }