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