1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jgit.dircache;
14
15 import static java.nio.charset.StandardCharsets.ISO_8859_1;
16
17 import java.io.BufferedInputStream;
18 import java.io.BufferedOutputStream;
19 import java.io.EOFException;
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.security.DigestOutputStream;
26 import java.security.MessageDigest;
27 import java.text.MessageFormat;
28 import java.time.Instant;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Comparator;
32 import java.util.List;
33
34 import org.eclipse.jgit.errors.CorruptObjectException;
35 import org.eclipse.jgit.errors.IndexReadException;
36 import org.eclipse.jgit.errors.LockFailedException;
37 import org.eclipse.jgit.errors.UnmergedPathException;
38 import org.eclipse.jgit.events.IndexChangedEvent;
39 import org.eclipse.jgit.events.IndexChangedListener;
40 import org.eclipse.jgit.internal.JGitText;
41 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
42 import org.eclipse.jgit.internal.storage.file.LockFile;
43 import org.eclipse.jgit.lib.AnyObjectId;
44 import org.eclipse.jgit.lib.Constants;
45 import org.eclipse.jgit.lib.ObjectId;
46 import org.eclipse.jgit.lib.ObjectInserter;
47 import org.eclipse.jgit.lib.ObjectReader;
48 import org.eclipse.jgit.lib.Repository;
49 import org.eclipse.jgit.treewalk.FileTreeIterator;
50 import org.eclipse.jgit.treewalk.TreeWalk;
51 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
52 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
53 import org.eclipse.jgit.util.FS;
54 import org.eclipse.jgit.util.IO;
55 import org.eclipse.jgit.util.MutableInteger;
56 import org.eclipse.jgit.util.NB;
57 import org.eclipse.jgit.util.TemporaryBuffer;
58 import org.eclipse.jgit.util.io.SilentFileInputStream;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public class DirCache {
74 private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
75
76 private static final int EXT_TREE = 0x54524545 ;
77
78 private static final DirCacheEntry[] NO_ENTRIES = {};
79
80 private static final byte[] NO_CHECKSUM = {};
81
82 static final Comparator<DirCacheEntry> ENT_CMP = (DirCacheEntry o1,
83 DirCacheEntry o2) -> {
84 final int cr = cmp(o1, o2);
85 if (cr != 0)
86 return cr;
87 return o1.getStage() - o2.getStage();
88 };
89
90 static int cmp(DirCacheEntry../../../../org/eclipse/jgit/dircache/DirCacheEntry.html#DirCacheEntry">DirCacheEntry a, DirCacheEntry b) {
91 return cmp(a.path, a.path.length, b);
92 }
93
94 static int cmp(byte[] aPath, int aLen, DirCacheEntry b) {
95 return cmp(aPath, aLen, b.path, b.path.length);
96 }
97
98 static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
99 final int bLen) {
100 for (int cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
101 final int cmp = (aPath[cPos] & 0xff) - (bPath[cPos] & 0xff);
102 if (cmp != 0)
103 return cmp;
104 }
105 return aLen - bLen;
106 }
107
108
109
110
111
112
113
114
115 public static DirCache newInCore() {
116 return new DirCache(null, null);
117 }
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 public static DirCache read(ObjectReader reader, AnyObjectId treeId)
133 throws IOException {
134 DirCache d = newInCore();
135 DirCacheBuilder b = d.builder();
136 b.addTree(null, DirCacheEntry.STAGE_0, reader, treeId);
137 b.finish();
138 return d;
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158 public static DirCache read(Repository repository)
159 throws CorruptObjectException, IOException {
160 final DirCache c = read(repository.getIndexFile(), repository.getFS());
161 c.repository = repository;
162 return c;
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185 public static DirCache read(File indexLocation, FS fs)
186 throws CorruptObjectException, IOException {
187 final DirCache/DirCache.html#DirCache">DirCache c = new DirCache(indexLocation, fs);
188 c.read();
189 return c;
190 }
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214 public static DirCache lock(File indexLocation, FS fs)
215 throws CorruptObjectException, IOException {
216 final DirCache/DirCache.html#DirCache">DirCache c = new DirCache(indexLocation, fs);
217 if (!c.lock())
218 throw new LockFailedException(indexLocation);
219
220 try {
221 c.read();
222 } catch (IOException | RuntimeException | Error e) {
223 c.unlock();
224 throw e;
225 }
226
227 return c;
228 }
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252 public static DirCache lock(final Repository repository,
253 final IndexChangedListener indexChangedListener)
254 throws CorruptObjectException, IOException {
255 DirCache c = lock(repository.getIndexFile(), repository.getFS(),
256 indexChangedListener);
257 c.repository = repository;
258 return c;
259 }
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 public static DirCache lock(final File indexLocation, final FS fs,
286 IndexChangedListener indexChangedListener)
287 throws CorruptObjectException,
288 IOException {
289 DirCache c = lock(indexLocation, fs);
290 c.registerIndexChangedListener(indexChangedListener);
291 return c;
292 }
293
294
295 private final File liveFile;
296
297
298 private DirCacheEntry[] sortedEntries;
299
300
301 private int entryCnt;
302
303
304 private DirCacheTree tree;
305
306
307 private LockFile myLock;
308
309
310 private FileSnapshot snapshot;
311
312
313 private byte[] readIndexChecksum;
314
315
316 private byte[] writeIndexChecksum;
317
318
319 private IndexChangedListener indexChangedListener;
320
321
322 private Repository repository;
323
324
325
326
327
328
329
330
331
332
333
334
335
336 public DirCache(File indexLocation, FS fs) {
337 liveFile = indexLocation;
338 clear();
339 }
340
341
342
343
344
345
346
347
348
349
350 public DirCacheBuilder builder() {
351 return new DirCacheBuilder(this, entryCnt + 16);
352 }
353
354
355
356
357
358
359
360
361
362
363 public DirCacheEditor editor() {
364 return new DirCacheEditor(this, entryCnt + 16);
365 }
366
367 void replace(DirCacheEntry[] e, int cnt) {
368 sortedEntries = e;
369 entryCnt = cnt;
370 tree = null;
371 }
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387 public void read() throws IOException, CorruptObjectException {
388 if (liveFile == null)
389 throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
390 if (!liveFile.exists())
391 clear();
392 else if (snapshot == null || snapshot.isModified(liveFile)) {
393 try (SilentFileInputStreamm.html#SilentFileInputStream">SilentFileInputStream inStream = new SilentFileInputStream(
394 liveFile)) {
395 clear();
396 readFrom(inStream);
397 } catch (FileNotFoundException fnfe) {
398 if (liveFile.exists()) {
399
400 throw new IndexReadException(
401 MessageFormat.format(JGitText.get().cannotReadIndex,
402 liveFile.getAbsolutePath(), fnfe));
403 }
404
405
406
407 clear();
408 }
409 snapshot = FileSnapshot.save(liveFile);
410 }
411 }
412
413
414
415
416
417
418
419 public boolean isOutdated() throws IOException {
420 if (liveFile == null || !liveFile.exists())
421 return false;
422 return snapshot == null || snapshot.isModified(liveFile);
423 }
424
425
426
427
428 public void clear() {
429 snapshot = null;
430 sortedEntries = NO_ENTRIES;
431 entryCnt = 0;
432 tree = null;
433 readIndexChecksum = NO_CHECKSUM;
434 }
435
436 private void readFrom(InputStream inStream) throws IOException,
437 CorruptObjectException {
438 final BufferedInputStream in = new BufferedInputStream(inStream);
439 final MessageDigest md = Constants.newMessageDigest();
440
441
442
443 final byte[] hdr = new byte[20];
444 IO.readFully(in, hdr, 0, 12);
445 md.update(hdr, 0, 12);
446 if (!is_DIRC(hdr))
447 throw new CorruptObjectException(JGitText.get().notADIRCFile);
448 final int ver = NB.decodeInt32(hdr, 4);
449 boolean extended = false;
450 if (ver == 3)
451 extended = true;
452 else if (ver != 2)
453 throw new CorruptObjectException(MessageFormat.format(
454 JGitText.get().unknownDIRCVersion, Integer.valueOf(ver)));
455 entryCnt = NB.decodeInt32(hdr, 8);
456 if (entryCnt < 0)
457 throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
458
459 snapshot = FileSnapshot.save(liveFile);
460 Instant smudge = snapshot.lastModifiedInstant();
461
462
463
464 final int infoLength = DirCacheEntry.getMaximumInfoLength(extended);
465 final byte[] infos = new byte[infoLength * entryCnt];
466 sortedEntries = new DirCacheEntry[entryCnt];
467
468 final MutableInteger.html#MutableInteger">MutableInteger infoAt = new MutableInteger();
469 for (int i = 0; i < entryCnt; i++) {
470 sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge);
471 }
472
473
474
475 for (;;) {
476 in.mark(21);
477 IO.readFully(in, hdr, 0, 20);
478 if (in.read() < 0) {
479
480
481 break;
482 }
483
484 in.reset();
485 md.update(hdr, 0, 8);
486 IO.skipFully(in, 8);
487
488 long sz = NB.decodeUInt32(hdr, 4);
489 switch (NB.decodeInt32(hdr, 0)) {
490 case EXT_TREE: {
491 if (Integer.MAX_VALUE < sz) {
492 throw new CorruptObjectException(MessageFormat.format(
493 JGitText.get().DIRCExtensionIsTooLargeAt,
494 formatExtensionName(hdr), Long.valueOf(sz)));
495 }
496 final byte[] raw = new byte[(int) sz];
497 IO.readFully(in, raw, 0, raw.length);
498 md.update(raw, 0, raw.length);
499 tree = new DirCacheTree(raw, new MutableInteger(), null);
500 break;
501 }
502 default:
503 if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
504
505
506
507
508
509 skipOptionalExtension(in, md, hdr, sz);
510 } else {
511
512
513
514
515 throw new CorruptObjectException(MessageFormat.format(JGitText.get().DIRCExtensionNotSupportedByThisVersion
516 , formatExtensionName(hdr)));
517 }
518 }
519 }
520
521 readIndexChecksum = md.digest();
522 if (!Arrays.equals(readIndexChecksum, hdr)) {
523 throw new CorruptObjectException(JGitText.get().DIRCChecksumMismatch);
524 }
525 }
526
527 private void skipOptionalExtension(final InputStream in,
528 final MessageDigest md, final byte[] hdr, long sz)
529 throws IOException {
530 final byte[] b = new byte[4096];
531 while (0 < sz) {
532 int n = in.read(b, 0, (int) Math.min(b.length, sz));
533 if (n < 0) {
534 throw new EOFException(
535 MessageFormat.format(
536 JGitText.get().shortReadOfOptionalDIRCExtensionExpectedAnotherBytes,
537 formatExtensionName(hdr), Long.valueOf(sz)));
538 }
539 md.update(b, 0, n);
540 sz -= n;
541 }
542 }
543
544 private static String formatExtensionName(byte[] hdr) {
545 return "'" + new String(hdr, 0, 4, ISO_8859_1) + "'";
546 }
547
548 private static boolean is_DIRC(byte[] hdr) {
549 if (hdr.length < SIG_DIRC.length)
550 return false;
551 for (int i = 0; i < SIG_DIRC.length; i++)
552 if (hdr[i] != SIG_DIRC[i])
553 return false;
554 return true;
555 }
556
557
558
559
560
561
562
563
564
565
566 public boolean lock() throws IOException {
567 if (liveFile == null)
568 throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
569 final LockFiletorage/file/LockFile.html#LockFile">LockFile tmp = new LockFile(liveFile);
570 if (tmp.lock()) {
571 tmp.setNeedStatInformation(true);
572 myLock = tmp;
573 return true;
574 }
575 return false;
576 }
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593 public void write() throws IOException {
594 final LockFile tmp = myLock;
595 requireLocked(tmp);
596 try (OutputStream o = tmp.getOutputStream();
597 OutputStream bo = new BufferedOutputStream(o)) {
598 writeTo(liveFile.getParentFile(), bo);
599 } catch (IOException | RuntimeException | Error err) {
600 tmp.unlock();
601 throw err;
602 }
603 }
604
605 void writeTo(File dir, OutputStream os) throws IOException {
606 final MessageDigest foot = Constants.newMessageDigest();
607 final DigestOutputStream dos = new DigestOutputStream(os, foot);
608
609 boolean extended = false;
610 for (int i = 0; i < entryCnt; i++) {
611 if (sortedEntries[i].isExtended()) {
612 extended = true;
613 break;
614 }
615 }
616
617
618
619 final byte[] tmp = new byte[128];
620 System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
621 NB.encodeInt32(tmp, 4, extended ? 3 : 2);
622 NB.encodeInt32(tmp, 8, entryCnt);
623 dos.write(tmp, 0, 12);
624
625
626
627 Instant smudge;
628 if (myLock != null) {
629
630
631
632
633 myLock.createCommitSnapshot();
634 snapshot = myLock.getCommitSnapshot();
635 smudge = snapshot.lastModifiedInstant();
636 } else {
637
638 smudge = Instant.EPOCH;
639 }
640
641
642
643 final boolean writeTree = tree != null;
644
645 if (repository != null && entryCnt > 0)
646 updateSmudgedEntries();
647
648 for (int i = 0; i < entryCnt; i++) {
649 final DirCacheEntry e = sortedEntries[i];
650 if (e.mightBeRacilyClean(smudge)) {
651 e.smudgeRacilyClean();
652 }
653 e.write(dos);
654 }
655
656 if (writeTree) {
657 @SuppressWarnings("resource")
658
659 TemporaryBuffer bb = new TemporaryBuffer.LocalFile(dir, 5 << 20);
660 try {
661 tree.write(tmp, bb);
662 bb.close();
663
664 NB.encodeInt32(tmp, 0, EXT_TREE);
665 NB.encodeInt32(tmp, 4, (int) bb.length());
666 dos.write(tmp, 0, 8);
667 bb.writeTo(dos, null);
668 } finally {
669 bb.destroy();
670 }
671 }
672 writeIndexChecksum = foot.digest();
673 os.write(writeIndexChecksum);
674 os.close();
675 }
676
677
678
679
680
681
682
683
684
685
686
687
688 public boolean commit() {
689 final LockFile tmp = myLock;
690 requireLocked(tmp);
691 myLock = null;
692 if (!tmp.commit()) {
693 return false;
694 }
695 snapshot = tmp.getCommitSnapshot();
696 if (indexChangedListener != null
697 && !Arrays.equals(readIndexChecksum, writeIndexChecksum)) {
698 indexChangedListener.onIndexChanged(new IndexChangedEvent(true));
699 }
700 return true;
701 }
702
703 private void requireLocked(LockFile tmp) {
704 if (liveFile == null)
705 throw new IllegalStateException(JGitText.get().dirCacheIsNotLocked);
706 if (tmp == null)
707 throw new IllegalStateException(MessageFormat.format(JGitText.get().dirCacheFileIsNotLocked
708 , liveFile.getAbsolutePath()));
709 }
710
711
712
713
714
715
716 public void unlock() {
717 final LockFile tmp = myLock;
718 if (tmp != null) {
719 myLock = null;
720 tmp.unlock();
721 }
722 }
723
724
725
726
727
728
729
730
731
732
733
734 public int findEntry(String path) {
735 final byte[] p = Constants.encode(path);
736 return findEntry(p, p.length);
737 }
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758 public int findEntry(byte[] p, int pLen) {
759 return findEntry(0, p, pLen);
760 }
761
762 int findEntry(int low, byte[] p, int pLen) {
763 int high = entryCnt;
764 while (low < high) {
765 int mid = (low + high) >>> 1;
766 final int cmp = cmp(p, pLen, sortedEntries[mid]);
767 if (cmp < 0)
768 high = mid;
769 else if (cmp == 0) {
770 while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
771 mid--;
772 return mid;
773 } else
774 low = mid + 1;
775 }
776 return -(low + 1);
777 }
778
779
780
781
782
783
784
785
786
787
788
789
790 public int nextEntry(int position) {
791 DirCacheEntry last = sortedEntries[position];
792 int nextIdx = position + 1;
793 while (nextIdx < entryCnt) {
794 final DirCacheEntry next = sortedEntries[nextIdx];
795 if (cmp(last, next) != 0)
796 break;
797 last = next;
798 nextIdx++;
799 }
800 return nextIdx;
801 }
802
803 int nextEntry(byte[] p, int pLen, int nextIdx) {
804 while (nextIdx < entryCnt) {
805 final DirCacheEntry next = sortedEntries[nextIdx];
806 if (!DirCacheTree.peq(p, next.path, pLen))
807 break;
808 nextIdx++;
809 }
810 return nextIdx;
811 }
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826 public int getEntryCount() {
827 return entryCnt;
828 }
829
830
831
832
833
834
835
836
837 public DirCacheEntry getEntry(int i) {
838 return sortedEntries[i];
839 }
840
841
842
843
844
845
846
847
848 public DirCacheEntry getEntry(String path) {
849 final int i = findEntry(path);
850 return i < 0 ? null : sortedEntries[i];
851 }
852
853
854
855
856
857
858
859
860 public DirCacheEntry[] getEntriesWithin(String path) {
861 if (path.length() == 0) {
862 DirCacheEntry[] r = new DirCacheEntry[entryCnt];
863 System.arraycopy(sortedEntries, 0, r, 0, entryCnt);
864 return r;
865 }
866 if (!path.endsWith("/"))
867 path += "/";
868 final byte[] p = Constants.encode(path);
869 final int pLen = p.length;
870
871 int eIdx = findEntry(p, pLen);
872 if (eIdx < 0)
873 eIdx = -(eIdx + 1);
874 final int lastIdx = nextEntry(p, pLen, eIdx);
875 final DirCacheEntryheEntry.html#DirCacheEntry">DirCacheEntry[] r = new DirCacheEntry[lastIdx - eIdx];
876 System.arraycopy(sortedEntries, eIdx, r, 0, r.length);
877 return r;
878 }
879
880 void toArray(final int i, final DirCacheEntry[] dst, final int off,
881 final int cnt) {
882 System.arraycopy(sortedEntries, i, dst, off, cnt);
883 }
884
885
886
887
888
889
890
891
892
893
894
895
896
897 public DirCacheTree getCacheTree(boolean build) {
898 if (build) {
899 if (tree == null)
900 tree = new DirCacheTree();
901 tree.validate(sortedEntries, entryCnt, 0, 0);
902 }
903 return tree;
904 }
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923 public ObjectId writeTree(ObjectInserter ow)
924 throws UnmergedPathException, IOException {
925 return getCacheTree(true).writeTree(sortedEntries, 0, 0, ow);
926 }
927
928
929
930
931
932
933
934
935 public boolean hasUnmergedPaths() {
936 for (int i = 0; i < entryCnt; i++) {
937 if (sortedEntries[i].getStage() > 0) {
938 return true;
939 }
940 }
941 return false;
942 }
943
944 private void registerIndexChangedListener(IndexChangedListener listener) {
945 this.indexChangedListener = listener;
946 }
947
948
949
950
951
952
953 private void updateSmudgedEntries() throws IOException {
954 List<String> paths = new ArrayList<>(128);
955 try (TreeWalkeeWalk.html#TreeWalk">TreeWalk walk = new TreeWalk(repository)) {
956 walk.setOperationType(OperationType.CHECKIN_OP);
957 for (int i = 0; i < entryCnt; i++)
958 if (sortedEntries[i].isSmudged())
959 paths.add(sortedEntries[i].getPathString());
960 if (paths.isEmpty())
961 return;
962 walk.setFilter(PathFilterGroup.createFromStrings(paths));
963
964 DirCacheIterator iIter = new DirCacheIterator(this);
965 FileTreeIterator fIter = new FileTreeIterator(repository);
966 walk.addTree(iIter);
967 walk.addTree(fIter);
968 fIter.setDirCacheIterator(walk, 0);
969 walk.setRecursive(true);
970 while (walk.next()) {
971 iIter = walk.getTree(0, DirCacheIterator.class);
972 if (iIter == null)
973 continue;
974 fIter = walk.getTree(1, FileTreeIterator.class);
975 if (fIter == null)
976 continue;
977 DirCacheEntry entry = iIter.getDirCacheEntry();
978 if (entry.isSmudged() && iIter.idEqual(fIter)) {
979 entry.setLength(fIter.getEntryLength());
980 entry.setLastModified(fIter.getEntryLastModifiedInstant());
981 }
982 }
983 }
984 }
985 }