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