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.PACK;
15 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
16 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
17
18 import java.io.BufferedReader;
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.nio.file.Files;
23 import java.text.MessageFormat;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Objects;
30 import java.util.Set;
31 import java.util.concurrent.atomic.AtomicReference;
32
33 import org.eclipse.jgit.internal.JGitText;
34 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
35 import org.eclipse.jgit.internal.storage.pack.PackExt;
36 import org.eclipse.jgit.internal.storage.pack.PackWriter;
37 import org.eclipse.jgit.lib.AbbreviatedObjectId;
38 import org.eclipse.jgit.lib.AnyObjectId;
39 import org.eclipse.jgit.lib.Config;
40 import org.eclipse.jgit.lib.Constants;
41 import org.eclipse.jgit.lib.ObjectDatabase;
42 import org.eclipse.jgit.lib.ObjectId;
43 import org.eclipse.jgit.lib.ObjectLoader;
44 import org.eclipse.jgit.lib.RepositoryCache;
45 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
46 import org.eclipse.jgit.util.FS;
47 import org.eclipse.jgit.util.FileUtils;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public class ObjectDirectory extends FileObjectDatabase {
68
69 private static final int RESOLVE_ABBREV_LIMIT = 256;
70
71 private final AlternateHandle handle = new AlternateHandle(this);
72
73 private final Config config;
74
75 private final File objects;
76
77 private final File infoDirectory;
78
79 private final LooseObjects loose;
80
81 private final PackDirectory packed;
82
83 private final PackDirectory preserved;
84
85 private final File alternatesFile;
86
87 private final FS fs;
88
89 private final AtomicReference<AlternateHandle[]> alternates;
90
91 private final File shallowFile;
92
93 private FileSnapshot shallowFileSnapshot = FileSnapshot.DIRTY;
94
95 private Set<ObjectId> shallowCommitsIds;
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 public ObjectDirectory(final Config cfg, final File dir,
116 File[] alternatePaths, FS fs, File shallowFile) throws IOException {
117 config = cfg;
118 objects = dir;
119 infoDirectory = new File(objects, "info");
120 File packDirectory = new File(objects, "pack");
121 File preservedDirectory = new File(packDirectory, "preserved");
122 alternatesFile = new File(objects, Constants.INFO_ALTERNATES);
123 loose = new LooseObjects(objects);
124 packed = new PackDirectory(config, packDirectory);
125 preserved = new PackDirectory(config, preservedDirectory);
126 this.fs = fs;
127 this.shallowFile = shallowFile;
128
129 alternates = new AtomicReference<>();
130 if (alternatePaths != null) {
131 AlternateHandle[] alt;
132
133 alt = new AlternateHandle[alternatePaths.length];
134 for (int i = 0; i < alternatePaths.length; i++)
135 alt[i] = openAlternate(alternatePaths[i]);
136 alternates.set(alt);
137 }
138 }
139
140
141 @Override
142 public final File getDirectory() {
143 return loose.getDirectory();
144 }
145
146
147
148
149
150
151 public final File getPackDirectory() {
152 return packed.getDirectory();
153 }
154
155
156
157
158
159
160 public final File getPreservedDirectory() {
161 return preserved.getDirectory();
162 }
163
164
165 @Override
166 public boolean exists() {
167 return fs.exists(objects);
168 }
169
170
171 @Override
172 public void create() throws IOException {
173 loose.create();
174 FileUtils.mkdir(infoDirectory);
175 packed.create();
176 }
177
178
179 @Override
180 public ObjectDirectoryInserter newInserter() {
181 return new ObjectDirectoryInserter(this, config);
182 }
183
184
185
186
187
188
189
190 public PackInserter newPackInserter() {
191 return new PackInserter(this);
192 }
193
194
195 @Override
196 public void close() {
197 loose.close();
198
199 packed.close();
200
201
202 AlternateHandle[] alt = alternates.get();
203 if (alt != null && alternates.compareAndSet(alt, null)) {
204 for(AlternateHandle od : alt)
205 od.close();
206 }
207 }
208
209
210 @Override
211 public Collection<Pack> getPacks() {
212 return packed.getPacks();
213 }
214
215
216 @Override
217 public long getApproximateObjectCount() {
218 long count = 0;
219 for (Pack p : getPacks()) {
220 try {
221 count += p.getIndex().getObjectCount();
222 } catch (IOException e) {
223 return -1;
224 }
225 }
226 return count;
227 }
228
229
230
231
232
233
234 @Override
235 public Pack openPack(File pack) throws IOException {
236 PackFile pf;
237 try {
238 pf = new PackFile(pack);
239 } catch (IllegalArgumentException e) {
240 throw new IOException(
241 MessageFormat.format(JGitText.get().notAValidPack, pack),
242 e);
243 }
244
245 String p = pf.getName();
246
247 if (p.length() != 50 || !p.startsWith("pack-")
248 || !pf.getPackExt().equals(PACK)) {
249 throw new IOException(
250 MessageFormat.format(JGitText.get().notAValidPack, pack));
251 }
252
253 PackFile bitmapIdx = pf.create(BITMAP_INDEX);
254 Pack res = new Pack(pack, bitmapIdx.exists() ? bitmapIdx : null);
255 packed.insert(res);
256 return res;
257 }
258
259
260 @Override
261 public String toString() {
262 return "ObjectDirectory[" + getDirectory() + "]";
263 }
264
265
266 @Override
267 public boolean has(AnyObjectId objectId) {
268 return loose.hasCached(objectId)
269 || hasPackedOrLooseInSelfOrAlternate(objectId)
270 || (restoreFromSelfOrAlternate(objectId, null)
271 && hasPackedOrLooseInSelfOrAlternate(objectId));
272 }
273
274 private boolean hasPackedOrLooseInSelfOrAlternate(AnyObjectId objectId) {
275 return hasPackedInSelfOrAlternate(objectId, null)
276 || hasLooseInSelfOrAlternate(objectId, null);
277 }
278
279 private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId,
280 Set<AlternateHandle.Id> skips) {
281 if (hasPackedObject(objectId)) {
282 return true;
283 }
284 skips = addMe(skips);
285 for (AlternateHandle alt : myAlternates()) {
286 if (!skips.contains(alt.getId())) {
287 if (alt.db.hasPackedInSelfOrAlternate(objectId, skips)) {
288 return true;
289 }
290 }
291 }
292 return false;
293 }
294
295 private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId,
296 Set<AlternateHandle.Id> skips) {
297 if (loose.has(objectId)) {
298 return true;
299 }
300 skips = addMe(skips);
301 for (AlternateHandle alt : myAlternates()) {
302 if (!skips.contains(alt.getId())) {
303 if (alt.db.hasLooseInSelfOrAlternate(objectId, skips)) {
304 return true;
305 }
306 }
307 }
308 return false;
309 }
310
311 boolean hasPackedObject(AnyObjectId objectId) {
312 return packed.has(objectId);
313 }
314
315 @Override
316 void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
317 throws IOException {
318 resolve(matches, id, null);
319 }
320
321 private void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
322 Set<AlternateHandle.Id> skips)
323 throws IOException {
324 if (!packed.resolve(matches, id, RESOLVE_ABBREV_LIMIT))
325 return;
326
327 if (!loose.resolve(matches, id, RESOLVE_ABBREV_LIMIT))
328 return;
329
330 skips = addMe(skips);
331 for (AlternateHandle alt : myAlternates()) {
332 if (!skips.contains(alt.getId())) {
333 alt.db.resolve(matches, id, skips);
334 if (matches.size() > RESOLVE_ABBREV_LIMIT) {
335 return;
336 }
337 }
338 }
339 }
340
341 @Override
342 ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
343 throws IOException {
344 ObjectLoader ldr = openObjectWithoutRestoring(curs, objectId);
345 if (ldr == null && restoreFromSelfOrAlternate(objectId, null)) {
346 ldr = openObjectWithoutRestoring(curs, objectId);
347 }
348 return ldr;
349 }
350
351 private ObjectLoader openObjectWithoutRestoring(WindowCursor curs, AnyObjectId objectId)
352 throws IOException {
353 if (loose.hasCached(objectId)) {
354 ObjectLoader ldr = openLooseObject(curs, objectId);
355 if (ldr != null) {
356 return ldr;
357 }
358 }
359 ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId, null);
360 if (ldr != null) {
361 return ldr;
362 }
363 return openLooseFromSelfOrAlternate(curs, objectId, null);
364 }
365
366 private ObjectLoader openPackedFromSelfOrAlternate(WindowCursor curs,
367 AnyObjectId objectId, Set<AlternateHandle.Id> skips) {
368 ObjectLoader ldr = openPackedObject(curs, objectId);
369 if (ldr != null) {
370 return ldr;
371 }
372 skips = addMe(skips);
373 for (AlternateHandle alt : myAlternates()) {
374 if (!skips.contains(alt.getId())) {
375 ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId, skips);
376 if (ldr != null) {
377 return ldr;
378 }
379 }
380 }
381 return null;
382 }
383
384 private ObjectLoader openLooseFromSelfOrAlternate(WindowCursor curs,
385 AnyObjectId objectId, Set<AlternateHandle.Id> skips)
386 throws IOException {
387 ObjectLoader ldr = openLooseObject(curs, objectId);
388 if (ldr != null) {
389 return ldr;
390 }
391 skips = addMe(skips);
392 for (AlternateHandle alt : myAlternates()) {
393 if (!skips.contains(alt.getId())) {
394 ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId, skips);
395 if (ldr != null) {
396 return ldr;
397 }
398 }
399 }
400 return null;
401 }
402
403 ObjectLoader openPackedObject(WindowCursor curs, AnyObjectId objectId) {
404 return packed.open(curs, objectId);
405 }
406
407 @Override
408 ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
409 throws IOException {
410 return loose.open(curs, id);
411 }
412
413 @Override
414 long getObjectSize(WindowCursor curs, AnyObjectId id) throws IOException {
415 long sz = getObjectSizeWithoutRestoring(curs, id);
416 if (0 > sz && restoreFromSelfOrAlternate(id, null)) {
417 sz = getObjectSizeWithoutRestoring(curs, id);
418 }
419 return sz;
420 }
421
422 private long getObjectSizeWithoutRestoring(WindowCursor curs,
423 AnyObjectId id) throws IOException {
424 if (loose.hasCached(id)) {
425 long len = loose.getSize(curs, id);
426 if (0 <= len) {
427 return len;
428 }
429 }
430 long len = getPackedSizeFromSelfOrAlternate(curs, id, null);
431 if (0 <= len) {
432 return len;
433 }
434 return getLooseSizeFromSelfOrAlternate(curs, id, null);
435 }
436
437 private long getPackedSizeFromSelfOrAlternate(WindowCursor curs,
438 AnyObjectId id, Set<AlternateHandle.Id> skips) {
439 long len = packed.getSize(curs, id);
440 if (0 <= len) {
441 return len;
442 }
443 skips = addMe(skips);
444 for (AlternateHandle alt : myAlternates()) {
445 if (!skips.contains(alt.getId())) {
446 len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id, skips);
447 if (0 <= len) {
448 return len;
449 }
450 }
451 }
452 return -1;
453 }
454
455 private long getLooseSizeFromSelfOrAlternate(WindowCursor curs,
456 AnyObjectId id, Set<AlternateHandle.Id> skips) throws IOException {
457 long len = loose.getSize(curs, id);
458 if (0 <= len) {
459 return len;
460 }
461 skips = addMe(skips);
462 for (AlternateHandle alt : myAlternates()) {
463 if (!skips.contains(alt.getId())) {
464 len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id, skips);
465 if (0 <= len) {
466 return len;
467 }
468 }
469 }
470 return -1;
471 }
472
473 @Override
474 void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
475 WindowCursor curs) throws IOException {
476 selectObjectRepresentation(packer, otp, curs, null);
477 }
478
479 private void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
480 WindowCursor curs, Set<AlternateHandle.Id> skips) throws IOException {
481 packed.selectRepresentation(packer, otp, curs);
482
483 skips = addMe(skips);
484 for (AlternateHandle h : myAlternates()) {
485 if (!skips.contains(h.getId())) {
486 h.db.selectObjectRepresentation(packer, otp, curs, skips);
487 }
488 }
489 }
490
491 private boolean restoreFromSelfOrAlternate(AnyObjectId objectId,
492 Set<AlternateHandle.Id> skips) {
493 if (restoreFromSelf(objectId)) {
494 return true;
495 }
496
497 skips = addMe(skips);
498 for (AlternateHandle alt : myAlternates()) {
499 if (!skips.contains(alt.getId())) {
500 if (alt.db.restoreFromSelfOrAlternate(objectId, skips)) {
501 return true;
502 }
503 }
504 }
505 return false;
506 }
507
508 private boolean restoreFromSelf(AnyObjectId objectId) {
509 Pack preservedPack = preserved.getPack(objectId);
510 if (preservedPack == null) {
511 return false;
512 }
513 PackFile preservedFile = new PackFile(preservedPack.getPackFile());
514
515
516 for (PackExt ext : PackExt.values()) {
517 if (!INDEX.equals(ext)) {
518 restore(preservedFile.create(ext));
519 }
520 }
521 restore(preservedFile.create(INDEX));
522 return true;
523 }
524
525 private boolean restore(PackFile preservedPack) {
526 PackFile restored = preservedPack
527 .createForDirectory(packed.getDirectory());
528 try {
529 Files.createLink(restored.toPath(), preservedPack.toPath());
530 } catch (IOException e) {
531 return false;
532 }
533 return true;
534 }
535
536 @Override
537 InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id,
538 boolean createDuplicate) throws IOException {
539
540
541 if (loose.hasCached(id)) {
542 FileUtils.delete(tmp, FileUtils.RETRY);
543 return InsertLooseObjectResult.EXISTS_LOOSE;
544 }
545 if (!createDuplicate && has(id)) {
546 FileUtils.delete(tmp, FileUtils.RETRY);
547 return InsertLooseObjectResult.EXISTS_PACKED;
548 }
549 return loose.insert(tmp, id);
550 }
551
552 @Override
553 Config getConfig() {
554 return config;
555 }
556
557 @Override
558 FS getFS() {
559 return fs;
560 }
561
562 @Override
563 Set<ObjectId> getShallowCommits() throws IOException {
564 if (shallowFile == null || !shallowFile.isFile())
565 return Collections.emptySet();
566
567 if (shallowFileSnapshot == null
568 || shallowFileSnapshot.isModified(shallowFile)) {
569 shallowCommitsIds = new HashSet<>();
570
571 try (BufferedReader reader = open(shallowFile)) {
572 String line;
573 while ((line = reader.readLine()) != null) {
574 try {
575 shallowCommitsIds.add(ObjectId.fromString(line));
576 } catch (IllegalArgumentException ex) {
577 throw new IOException(MessageFormat
578 .format(JGitText.get().badShallowLine, line),
579 ex);
580 }
581 }
582 }
583
584 shallowFileSnapshot = FileSnapshot.save(shallowFile);
585 }
586
587 return shallowCommitsIds;
588 }
589
590 void closeAllPackHandles(File packFile) {
591
592
593
594
595 if (packFile.exists()) {
596 for (Pack p : packed.getPacks()) {
597 if (packFile.getPath().equals(p.getPackFile().getPath())) {
598 p.close();
599 break;
600 }
601 }
602 }
603 }
604
605 AlternateHandle[] myAlternates() {
606 AlternateHandle[] alt = alternates.get();
607 if (alt == null) {
608 synchronized (alternates) {
609 alt = alternates.get();
610 if (alt == null) {
611 try {
612 alt = loadAlternates();
613 } catch (IOException e) {
614 alt = new AlternateHandle[0];
615 }
616 alternates.set(alt);
617 }
618 }
619 }
620 return alt;
621 }
622
623 Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) {
624 if (skips == null) {
625 skips = new HashSet<>();
626 }
627 skips.add(handle.getId());
628 return skips;
629 }
630
631 private AlternateHandle[] loadAlternates() throws IOException {
632 final List<AlternateHandle> l = new ArrayList<>(4);
633 try (BufferedReader br = open(alternatesFile)) {
634 String line;
635 while ((line = br.readLine()) != null) {
636 l.add(openAlternate(line));
637 }
638 }
639 return l.toArray(new AlternateHandle[0]);
640 }
641
642 private static BufferedReader open(File f)
643 throws IOException, FileNotFoundException {
644 return Files.newBufferedReader(f.toPath(), UTF_8);
645 }
646
647 private AlternateHandle openAlternate(String location)
648 throws IOException {
649 final File objdir = fs.resolve(objects, location);
650 return openAlternate(objdir);
651 }
652
653 private AlternateHandle openAlternate(File objdir) throws IOException {
654 final File parent = objdir.getParentFile();
655 if (FileKey.isGitRepository(parent, fs)) {
656 FileKey key = FileKey.exact(parent, fs);
657 FileRepository db = (FileRepository) RepositoryCache.open(key);
658 return new AlternateRepository(db);
659 }
660
661 ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs, null);
662 return new AlternateHandle(db);
663 }
664
665
666
667
668 @Override
669 public File fileFor(AnyObjectId objectId) {
670 return loose.fileFor(objectId);
671 }
672
673 static class AlternateHandle {
674 static class Id {
675 String alternateId;
676
677 public Id(File object) {
678 try {
679 this.alternateId = object.getCanonicalPath();
680 } catch (Exception e) {
681 alternateId = null;
682 }
683 }
684
685 @Override
686 public boolean equals(Object o) {
687 if (o == this) {
688 return true;
689 }
690 if (o == null || !(o instanceof Id)) {
691 return false;
692 }
693 Id aId = (Id) o;
694 return Objects.equals(alternateId, aId.alternateId);
695 }
696
697 @Override
698 public int hashCode() {
699 if (alternateId == null) {
700 return 1;
701 }
702 return alternateId.hashCode();
703 }
704 }
705
706 final ObjectDirectory db;
707
708 AlternateHandle(ObjectDirectory db) {
709 this.db = db;
710 }
711
712 void close() {
713 db.close();
714 }
715
716 public Id getId(){
717 return db.getAlternateId();
718 }
719 }
720
721 static class AlternateRepository extends AlternateHandle {
722 final FileRepository repository;
723
724 AlternateRepository(FileRepository r) {
725 super(r.getObjectDatabase());
726 repository = r;
727 }
728
729 @Override
730 void close() {
731 repository.close();
732 }
733 }
734
735
736 @Override
737 public ObjectDatabase newCachedDatabase() {
738 return newCachedFileObjectDatabase();
739 }
740
741 CachedObjectDirectory newCachedFileObjectDatabase() {
742 return new CachedObjectDirectory(this);
743 }
744
745 AlternateHandle.Id getAlternateId() {
746 return new AlternateHandle.Id(objects);
747 }
748 }