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