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