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 java.io.BufferedInputStream;
49 import java.io.BufferedOutputStream;
50 import java.io.EOFException;
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.FileNotFoundException;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.OutputStream;
57 import java.io.UnsupportedEncodingException;
58 import java.security.DigestOutputStream;
59 import java.security.MessageDigest;
60 import java.text.MessageFormat;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Comparator;
64 import java.util.List;
65
66 import org.eclipse.jgit.errors.CorruptObjectException;
67 import org.eclipse.jgit.errors.IndexReadException;
68 import org.eclipse.jgit.errors.LockFailedException;
69 import org.eclipse.jgit.errors.UnmergedPathException;
70 import org.eclipse.jgit.events.IndexChangedEvent;
71 import org.eclipse.jgit.events.IndexChangedListener;
72 import org.eclipse.jgit.internal.JGitText;
73 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
74 import org.eclipse.jgit.internal.storage.file.LockFile;
75 import org.eclipse.jgit.lib.AnyObjectId;
76 import org.eclipse.jgit.lib.Constants;
77 import org.eclipse.jgit.lib.ObjectId;
78 import org.eclipse.jgit.lib.ObjectInserter;
79 import org.eclipse.jgit.lib.ObjectReader;
80 import org.eclipse.jgit.lib.Repository;
81 import org.eclipse.jgit.treewalk.FileTreeIterator;
82 import org.eclipse.jgit.treewalk.TreeWalk;
83 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
84 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
85 import org.eclipse.jgit.util.FS;
86 import org.eclipse.jgit.util.IO;
87 import org.eclipse.jgit.util.MutableInteger;
88 import org.eclipse.jgit.util.NB;
89 import org.eclipse.jgit.util.TemporaryBuffer;
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 public class DirCache {
105 private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
106
107 private static final int EXT_TREE = 0x54524545 ;
108
109 private static final DirCacheEntry[] NO_ENTRIES = {};
110
111 private static final byte[] NO_CHECKSUM = {};
112
113 static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
114 @Override
115 public int compare(final DirCacheEntry o1, final DirCacheEntry o2) {
116 final int cr = cmp(o1, o2);
117 if (cr != 0)
118 return cr;
119 return o1.getStage() - o2.getStage();
120 }
121 };
122
123 static int cmp(final DirCacheEntry a, final DirCacheEntry b) {
124 return cmp(a.path, a.path.length, b);
125 }
126
127 static int cmp(final byte[] aPath, final int aLen, final 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(final 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(final File indexLocation, final FS fs)
219 throws CorruptObjectException, IOException {
220 final 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(final File indexLocation, final FS fs)
248 throws CorruptObjectException, IOException {
249 final DirCache c = new DirCache(indexLocation, fs);
250 if (!c.lock())
251 throw new LockFailedException(indexLocation);
252
253 try {
254 c.read();
255 } catch (IOException e) {
256 c.unlock();
257 throw e;
258 } catch (RuntimeException e) {
259 c.unlock();
260 throw e;
261 } catch (Error e) {
262 c.unlock();
263 throw e;
264 }
265
266 return c;
267 }
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291 public static DirCache lock(final Repository repository,
292 final IndexChangedListener indexChangedListener)
293 throws CorruptObjectException, IOException {
294 DirCache c = lock(repository.getIndexFile(), repository.getFS(),
295 indexChangedListener);
296 c.repository = repository;
297 return c;
298 }
299
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 public static DirCache lock(final File indexLocation, final FS fs,
325 IndexChangedListener indexChangedListener)
326 throws CorruptObjectException,
327 IOException {
328 DirCache c = lock(indexLocation, fs);
329 c.registerIndexChangedListener(indexChangedListener);
330 return c;
331 }
332
333
334 private final File liveFile;
335
336
337 private DirCacheEntry[] sortedEntries;
338
339
340 private int entryCnt;
341
342
343 private DirCacheTree tree;
344
345
346 private LockFile myLock;
347
348
349 private FileSnapshot snapshot;
350
351
352 private byte[] readIndexChecksum;
353
354
355 private byte[] writeIndexChecksum;
356
357
358 private IndexChangedListener indexChangedListener;
359
360
361 private Repository repository;
362
363
364
365
366
367
368
369
370
371
372
373
374
375 public DirCache(final File indexLocation, final FS fs) {
376 liveFile = indexLocation;
377 clear();
378 }
379
380
381
382
383
384
385
386
387
388 public DirCacheBuilder builder() {
389 return new DirCacheBuilder(this, entryCnt + 16);
390 }
391
392
393
394
395
396
397
398
399
400 public DirCacheEditor editor() {
401 return new DirCacheEditor(this, entryCnt + 16);
402 }
403
404 void replace(final DirCacheEntry[] e, final int cnt) {
405 sortedEntries = e;
406 entryCnt = cnt;
407 tree = null;
408 }
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424 public void read() throws IOException, CorruptObjectException {
425 if (liveFile == null)
426 throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
427 if (!liveFile.exists())
428 clear();
429 else if (snapshot == null || snapshot.isModified(liveFile)) {
430 try {
431 final FileInputStream inStream = new FileInputStream(liveFile);
432 try {
433 clear();
434 readFrom(inStream);
435 } finally {
436 try {
437 inStream.close();
438 } catch (IOException err2) {
439
440 }
441 }
442 } catch (FileNotFoundException fnfe) {
443 if (liveFile.exists()) {
444
445 throw new IndexReadException(
446 MessageFormat.format(JGitText.get().cannotReadIndex,
447 liveFile.getAbsolutePath(), fnfe));
448 }
449
450
451
452 clear();
453 }
454 snapshot = FileSnapshot.save(liveFile);
455 }
456 }
457
458
459
460
461
462 public boolean isOutdated() throws IOException {
463 if (liveFile == null || !liveFile.exists())
464 return false;
465 return snapshot == null || snapshot.isModified(liveFile);
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(final 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 int smudge_s = (int) (snapshot.lastModified() / 1000);
502 int smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000;
503
504
505
506 final int infoLength = DirCacheEntry.getMaximumInfoLength(extended);
507 final byte[] infos = new byte[infoLength * entryCnt];
508 sortedEntries = new DirCacheEntry[entryCnt];
509
510 final MutableInteger infoAt = new MutableInteger();
511 for (int i = 0; i < entryCnt; i++)
512 sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge_s, smudge_ns);
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(final byte[] hdr)
586 throws UnsupportedEncodingException {
587 return "'" + new String(hdr, 0, 4, "ISO-8859-1") + "'";
588 }
589
590 private static boolean is_DIRC(final byte[] hdr) {
591 if (hdr.length < SIG_DIRC.length)
592 return false;
593 for (int i = 0; i < SIG_DIRC.length; i++)
594 if (hdr[i] != SIG_DIRC[i])
595 return false;
596 return true;
597 }
598
599
600
601
602
603
604
605
606
607
608 public boolean lock() throws IOException {
609 if (liveFile == null)
610 throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
611 final LockFile tmp = new LockFile(liveFile);
612 if (tmp.lock()) {
613 tmp.setNeedStatInformation(true);
614 myLock = tmp;
615 return true;
616 }
617 return false;
618 }
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635 public void write() throws IOException {
636 final LockFile tmp = myLock;
637 requireLocked(tmp);
638 try (OutputStream o = tmp.getOutputStream();
639 OutputStream bo = new BufferedOutputStream(o)) {
640 writeTo(liveFile.getParentFile(), bo);
641 } catch (IOException err) {
642 tmp.unlock();
643 throw err;
644 } catch (RuntimeException err) {
645 tmp.unlock();
646 throw err;
647 } catch (Error err) {
648 tmp.unlock();
649 throw err;
650 }
651 }
652
653 void writeTo(File dir, final OutputStream os) throws IOException {
654 final MessageDigest foot = Constants.newMessageDigest();
655 final DigestOutputStream dos = new DigestOutputStream(os, foot);
656
657 boolean extended = false;
658 for (int i = 0; i < entryCnt; i++)
659 extended |= sortedEntries[i].isExtended();
660
661
662
663 final byte[] tmp = new byte[128];
664 System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
665 NB.encodeInt32(tmp, 4, extended ? 3 : 2);
666 NB.encodeInt32(tmp, 8, entryCnt);
667 dos.write(tmp, 0, 12);
668
669
670
671 final int smudge_s;
672 final int smudge_ns;
673 if (myLock != null) {
674
675
676
677
678 myLock.createCommitSnapshot();
679 snapshot = myLock.getCommitSnapshot();
680 smudge_s = (int) (snapshot.lastModified() / 1000);
681 smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000;
682 } else {
683
684 smudge_ns = 0;
685 smudge_s = 0;
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_s, smudge_ns))
698 e.smudgeRacilyClean();
699 e.write(dos);
700 }
701
702 if (writeTree) {
703 TemporaryBuffer bb = new TemporaryBuffer.LocalFile(dir, 5 << 20);
704 try {
705 tree.write(tmp, bb);
706 bb.close();
707
708 NB.encodeInt32(tmp, 0, EXT_TREE);
709 NB.encodeInt32(tmp, 4, (int) bb.length());
710 dos.write(tmp, 0, 8);
711 bb.writeTo(dos, null);
712 } finally {
713 bb.destroy();
714 }
715 }
716 writeIndexChecksum = foot.digest();
717 os.write(writeIndexChecksum);
718 os.close();
719 }
720
721
722
723
724
725
726
727
728
729
730
731
732 public boolean commit() {
733 final LockFile tmp = myLock;
734 requireLocked(tmp);
735 myLock = null;
736 if (!tmp.commit())
737 return false;
738 snapshot = tmp.getCommitSnapshot();
739 if (indexChangedListener != null
740 && !Arrays.equals(readIndexChecksum, writeIndexChecksum))
741 indexChangedListener.onIndexChanged(new IndexChangedEvent());
742 return true;
743 }
744
745 private void requireLocked(final LockFile tmp) {
746 if (liveFile == null)
747 throw new IllegalStateException(JGitText.get().dirCacheIsNotLocked);
748 if (tmp == null)
749 throw new IllegalStateException(MessageFormat.format(JGitText.get().dirCacheFileIsNotLocked
750 , liveFile.getAbsolutePath()));
751 }
752
753
754
755
756
757
758 public void unlock() {
759 final LockFile tmp = myLock;
760 if (tmp != null) {
761 myLock = null;
762 tmp.unlock();
763 }
764 }
765
766
767
768
769
770
771
772
773
774
775
776 public int findEntry(final String path) {
777 final byte[] p = Constants.encode(path);
778 return findEntry(p, p.length);
779 }
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800 public int findEntry(byte[] p, int pLen) {
801 return findEntry(0, p, pLen);
802 }
803
804 int findEntry(int low, byte[] p, int pLen) {
805 int high = entryCnt;
806 while (low < high) {
807 int mid = (low + high) >>> 1;
808 final int cmp = cmp(p, pLen, sortedEntries[mid]);
809 if (cmp < 0)
810 high = mid;
811 else if (cmp == 0) {
812 while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
813 mid--;
814 return mid;
815 } else
816 low = mid + 1;
817 }
818 return -(low + 1);
819 }
820
821
822
823
824
825
826
827
828
829
830
831
832 public int nextEntry(final int position) {
833 DirCacheEntry last = sortedEntries[position];
834 int nextIdx = position + 1;
835 while (nextIdx < entryCnt) {
836 final DirCacheEntry next = sortedEntries[nextIdx];
837 if (cmp(last, next) != 0)
838 break;
839 last = next;
840 nextIdx++;
841 }
842 return nextIdx;
843 }
844
845 int nextEntry(final byte[] p, final int pLen, int nextIdx) {
846 while (nextIdx < entryCnt) {
847 final DirCacheEntry next = sortedEntries[nextIdx];
848 if (!DirCacheTree.peq(p, next.path, pLen))
849 break;
850 nextIdx++;
851 }
852 return nextIdx;
853 }
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868 public int getEntryCount() {
869 return entryCnt;
870 }
871
872
873
874
875
876
877
878
879 public DirCacheEntry getEntry(final int i) {
880 return sortedEntries[i];
881 }
882
883
884
885
886
887
888
889
890 public DirCacheEntry getEntry(final String path) {
891 final int i = findEntry(path);
892 return i < 0 ? null : sortedEntries[i];
893 }
894
895
896
897
898
899
900
901
902 public DirCacheEntry[] getEntriesWithin(String path) {
903 if (path.length() == 0) {
904 DirCacheEntry[] r = new DirCacheEntry[entryCnt];
905 System.arraycopy(sortedEntries, 0, r, 0, entryCnt);
906 return r;
907 }
908 if (!path.endsWith("/"))
909 path += "/";
910 final byte[] p = Constants.encode(path);
911 final int pLen = p.length;
912
913 int eIdx = findEntry(p, pLen);
914 if (eIdx < 0)
915 eIdx = -(eIdx + 1);
916 final int lastIdx = nextEntry(p, pLen, eIdx);
917 final DirCacheEntry[] r = new DirCacheEntry[lastIdx - eIdx];
918 System.arraycopy(sortedEntries, eIdx, r, 0, r.length);
919 return r;
920 }
921
922 void toArray(final int i, final DirCacheEntry[] dst, final int off,
923 final int cnt) {
924 System.arraycopy(sortedEntries, i, dst, off, cnt);
925 }
926
927
928
929
930
931
932
933
934
935
936
937
938
939 public DirCacheTree getCacheTree(final boolean build) {
940 if (build) {
941 if (tree == null)
942 tree = new DirCacheTree();
943 tree.validate(sortedEntries, entryCnt, 0, 0);
944 }
945 return tree;
946 }
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965 public ObjectId writeTree(final ObjectInserter ow)
966 throws UnmergedPathException, IOException {
967 return getCacheTree(true).writeTree(sortedEntries, 0, 0, ow);
968 }
969
970
971
972
973
974
975
976
977 public boolean hasUnmergedPaths() {
978 for (int i = 0; i < entryCnt; i++) {
979 if (sortedEntries[i].getStage() > 0) {
980 return true;
981 }
982 }
983 return false;
984 }
985
986 private void registerIndexChangedListener(IndexChangedListener listener) {
987 this.indexChangedListener = listener;
988 }
989
990
991
992
993
994
995 private void updateSmudgedEntries() throws IOException {
996 List<String> paths = new ArrayList<>(128);
997 try (TreeWalk walk = new TreeWalk(repository)) {
998 walk.setOperationType(OperationType.CHECKIN_OP);
999 for (int i = 0; i < entryCnt; i++)
1000 if (sortedEntries[i].isSmudged())
1001 paths.add(sortedEntries[i].getPathString());
1002 if (paths.isEmpty())
1003 return;
1004 walk.setFilter(PathFilterGroup.createFromStrings(paths));
1005
1006 DirCacheIterator iIter = new DirCacheIterator(this);
1007 FileTreeIterator fIter = new FileTreeIterator(repository);
1008 walk.addTree(iIter);
1009 walk.addTree(fIter);
1010 fIter.setDirCacheIterator(walk, 0);
1011 walk.setRecursive(true);
1012 while (walk.next()) {
1013 iIter = walk.getTree(0, DirCacheIterator.class);
1014 if (iIter == null)
1015 continue;
1016 fIter = walk.getTree(1, FileTreeIterator.class);
1017 if (fIter == null)
1018 continue;
1019 DirCacheEntry entry = iIter.getDirCacheEntry();
1020 if (entry.isSmudged() && iIter.idEqual(fIter)) {
1021 entry.setLength(fIter.getEntryLength());
1022 entry.setLastModified(fIter.getEntryLastModified());
1023 }
1024 }
1025 }
1026 }
1027 }