1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jgit.dircache;
15
16 import static java.nio.charset.StandardCharsets.UTF_8;
17
18 import java.io.ByteArrayOutputStream;
19 import java.io.EOFException;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.nio.ByteBuffer;
24 import java.security.MessageDigest;
25 import java.text.MessageFormat;
26 import java.time.Instant;
27 import java.util.Arrays;
28
29 import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
30 import org.eclipse.jgit.errors.CorruptObjectException;
31 import org.eclipse.jgit.internal.JGitText;
32 import org.eclipse.jgit.lib.AnyObjectId;
33 import org.eclipse.jgit.lib.Constants;
34 import org.eclipse.jgit.lib.FileMode;
35 import org.eclipse.jgit.lib.ObjectId;
36 import org.eclipse.jgit.util.IO;
37 import org.eclipse.jgit.util.MutableInteger;
38 import org.eclipse.jgit.util.NB;
39 import org.eclipse.jgit.util.SystemReader;
40
41
42
43
44
45
46
47
48 public class DirCacheEntry {
49 private static final byte[] nullpad = new byte[8];
50
51
52 public static final int STAGE_0 = 0;
53
54
55 public static final int STAGE_1 = 1;
56
57
58 public static final int STAGE_2 = 2;
59
60
61 public static final int STAGE_3 = 3;
62
63 private static final int P_CTIME = 0;
64
65
66
67 private static final int P_MTIME = 8;
68
69
70
71
72
73
74
75 private static final int P_MODE = 24;
76
77
78
79
80
81 private static final int P_SIZE = 36;
82
83 private static final int P_OBJECTID = 40;
84
85 private static final int P_FLAGS = 60;
86 private static final int P_FLAGS2 = 62;
87
88
89 private static final int NAME_MASK = 0xfff;
90
91 private static final int INTENT_TO_ADD = 0x20000000;
92 private static final int SKIP_WORKTREE = 0x40000000;
93 private static final int EXTENDED_FLAGS = (INTENT_TO_ADD | SKIP_WORKTREE);
94
95 private static final int INFO_LEN = 62;
96 private static final int INFO_LEN_EXTENDED = 64;
97
98 private static final int EXTENDED = 0x40;
99 private static final int ASSUME_VALID = 0x80;
100
101
102 private static final int UPDATE_NEEDED = 0x1;
103
104
105 private final byte[] info;
106
107
108 private final int infoOffset;
109
110
111 final byte[] path;
112
113
114 private byte inCoreFlags;
115
116 DirCacheEntry(byte[] sharedInfo, MutableInteger infoAt, InputStream in,
117 MessageDigest md, Instant smudge, DirCacheVersion version,
118 DirCacheEntry previous)
119 throws IOException {
120 info = sharedInfo;
121 infoOffset = infoAt.value;
122
123 IO.readFully(in, info, infoOffset, INFO_LEN);
124
125 int len;
126 if (isExtended()) {
127 len = INFO_LEN_EXTENDED;
128 IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN);
129
130 if ((getExtendedFlags() & ~EXTENDED_FLAGS) != 0)
131 throw new IOException(MessageFormat.format(JGitText.get()
132 .DIRCUnrecognizedExtendedFlags, String.valueOf(getExtendedFlags())));
133 } else
134 len = INFO_LEN;
135
136 infoAt.value += len;
137 md.update(info, infoOffset, len);
138
139 int toRemove = 0;
140 if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
141
142 int b = in.read();
143 md.update((byte) b);
144 toRemove = b & 0x7F;
145 while ((b & 0x80) != 0) {
146 toRemove++;
147 b = in.read();
148 md.update((byte) b);
149 toRemove = (toRemove << 7) | (b & 0x7F);
150 }
151 if (toRemove < 0
152 || (previous != null && toRemove > previous.path.length)) {
153 if (previous == null) {
154 throw new IOException(MessageFormat.format(
155 JGitText.get().DIRCCorruptLengthFirst,
156 Integer.valueOf(toRemove)));
157 }
158 throw new IOException(MessageFormat.format(
159 JGitText.get().DIRCCorruptLength,
160 Integer.valueOf(toRemove), previous.getPathString()));
161 }
162 }
163 int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
164 int skipped = 0;
165 if (pathLen < NAME_MASK) {
166 path = new byte[pathLen];
167 if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
168 && previous != null) {
169 System.arraycopy(previous.path, 0, path, 0,
170 previous.path.length - toRemove);
171 IO.readFully(in, path, previous.path.length - toRemove,
172 pathLen - (previous.path.length - toRemove));
173 md.update(path, previous.path.length - toRemove,
174 pathLen - (previous.path.length - toRemove));
175 pathLen = pathLen - (previous.path.length - toRemove);
176 } else {
177 IO.readFully(in, path, 0, pathLen);
178 md.update(path, 0, pathLen);
179 }
180 } else if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
181 || previous == null || toRemove == previous.path.length) {
182 ByteArrayOutputStream tmp = new ByteArrayOutputStream();
183 byte[] buf = new byte[NAME_MASK];
184 IO.readFully(in, buf, 0, NAME_MASK);
185 tmp.write(buf);
186 readNulTerminatedString(in, tmp);
187 path = tmp.toByteArray();
188 pathLen = path.length;
189 md.update(path, 0, pathLen);
190 skipped = 1;
191 md.update((byte) 0);
192 } else {
193 ByteArrayOutputStream tmp = new ByteArrayOutputStream();
194 tmp.write(previous.path, 0, previous.path.length - toRemove);
195 pathLen = readNulTerminatedString(in, tmp);
196 path = tmp.toByteArray();
197 md.update(path, previous.path.length - toRemove, pathLen);
198 skipped = 1;
199 md.update((byte) 0);
200 }
201
202 try {
203 checkPath(path);
204 } catch (InvalidPathException e) {
205 CorruptObjectException p =
206 new CorruptObjectException(e.getMessage());
207 if (e.getCause() != null)
208 p.initCause(e.getCause());
209 throw p;
210 }
211
212 if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
213 if (skipped == 0) {
214 int b = in.read();
215 if (b < 0) {
216 throw new EOFException(JGitText.get().shortReadOfBlock);
217 }
218 md.update((byte) b);
219 }
220 } else {
221
222
223
224 final int actLen = len + pathLen;
225 final int expLen = (actLen + 8) & ~7;
226 final int padLen = expLen - actLen - skipped;
227 if (padLen > 0) {
228 IO.skipFully(in, padLen);
229 md.update(nullpad, 0, padLen);
230 }
231 }
232 if (mightBeRacilyClean(smudge)) {
233 smudgeRacilyClean();
234 }
235 }
236
237
238
239
240
241
242
243
244
245
246
247 public DirCacheEntry(String newPath) {
248 this(Constants.encode(newPath), STAGE_0);
249 }
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264 public DirCacheEntry(String newPath, int stage) {
265 this(Constants.encode(newPath), stage);
266 }
267
268
269
270
271
272
273
274
275
276
277
278 public DirCacheEntry(byte[] newPath) {
279 this(newPath, STAGE_0);
280 }
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 @SuppressWarnings("boxing")
296 public DirCacheEntry(byte[] path, int stage) {
297 checkPath(path);
298 if (stage < 0 || 3 < stage)
299 throw new IllegalArgumentException(MessageFormat.format(
300 JGitText.get().invalidStageForPath,
301 stage, toString(path)));
302
303 info = new byte[INFO_LEN];
304 infoOffset = 0;
305 this.path = path;
306
307 int flags = ((stage & 0x3) << 12);
308 if (path.length < NAME_MASK)
309 flags |= path.length;
310 else
311 flags |= NAME_MASK;
312 NB.encodeInt16(info, infoOffset + P_FLAGS, flags);
313 }
314
315
316
317
318
319
320
321
322
323
324
325 public DirCacheEntryf="../../../../org/eclipse/jgit/dircache/DirCacheEntry.html#DirCacheEntry">DirCacheEntry(DirCacheEntry src) {
326 path = src.path;
327 info = new byte[INFO_LEN];
328 infoOffset = 0;
329 System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN);
330 }
331
332 private int readNulTerminatedString(InputStream in, OutputStream out)
333 throws IOException {
334 int n = 0;
335 for (;;) {
336 int c = in.read();
337 if (c < 0) {
338 throw new EOFException(JGitText.get().shortReadOfBlock);
339 }
340 if (c == 0) {
341 break;
342 }
343 out.write(c);
344 n++;
345 }
346 return n;
347 }
348
349 void write(OutputStream os, DirCacheVersion version, DirCacheEntry previous)
350 throws IOException {
351 final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
352 if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
353 os.write(info, infoOffset, len);
354 os.write(path, 0, path.length);
355
356
357
358 int entryLen = len + path.length;
359 int expLen = (entryLen + 8) & ~7;
360 if (entryLen != expLen)
361 os.write(nullpad, 0, expLen - entryLen);
362 } else {
363 int pathCommon = 0;
364 int toRemove;
365 if (previous != null) {
366
367 int pathLen = Math.min(path.length, previous.path.length);
368 while (pathCommon < pathLen
369 && path[pathCommon] == previous.path[pathCommon]) {
370 pathCommon++;
371 }
372 toRemove = previous.path.length - pathCommon;
373 } else {
374 toRemove = 0;
375 }
376 byte[] tmp = new byte[16];
377 int n = tmp.length;
378 tmp[--n] = (byte) (toRemove & 0x7F);
379 while ((toRemove >>>= 7) != 0) {
380 tmp[--n] = (byte) (0x80 | (--toRemove & 0x7F));
381 }
382 os.write(info, infoOffset, len);
383 os.write(tmp, n, tmp.length - n);
384 os.write(path, pathCommon, path.length - pathCommon);
385 os.write(0);
386 }
387 }
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406 @Deprecated
407 public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) {
408 return mightBeRacilyClean(Instant.ofEpochSecond(smudge_s, smudge_ns));
409 }
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426 public final boolean mightBeRacilyClean(Instant smudge) {
427
428
429
430
431
432
433
434 final int base = infoOffset + P_MTIME;
435 final int mtime = NB.decodeInt32(info, base);
436 if ((int) smudge.getEpochSecond() == mtime) {
437 return smudge.getNano() <= NB.decodeInt32(info, base + 4);
438 }
439 return false;
440 }
441
442
443
444
445
446
447
448
449 public final void smudgeRacilyClean() {
450
451
452
453
454
455
456 final int base = infoOffset + P_SIZE;
457 Arrays.fill(info, base, base + 4, (byte) 0);
458 }
459
460
461
462
463
464
465
466
467
468
469
470
471 public final boolean isSmudged() {
472 final int base = infoOffset + P_OBJECTID;
473 return (getLength() == 0) && (Constants.EMPTY_BLOB_ID.compareTo(info, base) != 0);
474 }
475
476 final byte[] idBuffer() {
477 return info;
478 }
479
480 final int idOffset() {
481 return infoOffset + P_OBJECTID;
482 }
483
484
485
486
487
488
489
490
491
492
493 public boolean isAssumeValid() {
494 return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0;
495 }
496
497
498
499
500
501
502
503
504 public void setAssumeValid(boolean assume) {
505 if (assume)
506 info[infoOffset + P_FLAGS] |= (byte) ASSUME_VALID;
507 else
508 info[infoOffset + P_FLAGS] &= (byte) ~ASSUME_VALID;
509 }
510
511
512
513
514
515
516 public boolean isUpdateNeeded() {
517 return (inCoreFlags & UPDATE_NEEDED) != 0;
518 }
519
520
521
522
523
524
525
526 public void setUpdateNeeded(boolean updateNeeded) {
527 if (updateNeeded)
528 inCoreFlags |= (byte) UPDATE_NEEDED;
529 else
530 inCoreFlags &= (byte) ~UPDATE_NEEDED;
531 }
532
533
534
535
536
537
538
539
540 public int getStage() {
541 return (info[infoOffset + P_FLAGS] >>> 4) & 0x3;
542 }
543
544
545
546
547
548
549
550
551
552
553 public void setStage(int stage) {
554 if ((stage & ~0x3) != 0) {
555 throw new IllegalArgumentException(
556 "Invalid stage, must be in range [0..3]");
557 }
558 byte flags = info[infoOffset + P_FLAGS];
559 info[infoOffset + P_FLAGS] = (byte) ((flags & 0xCF) | (stage << 4));
560 }
561
562
563
564
565
566
567 public boolean isSkipWorkTree() {
568 return (getExtendedFlags() & SKIP_WORKTREE) != 0;
569 }
570
571
572
573
574
575
576 public boolean isIntentToAdd() {
577 return (getExtendedFlags() & INTENT_TO_ADD) != 0;
578 }
579
580
581
582
583
584
585
586 public boolean isMerged() {
587 return getStage() == STAGE_0;
588 }
589
590
591
592
593
594
595
596 public int getRawMode() {
597 return NB.decodeInt32(info, infoOffset + P_MODE);
598 }
599
600
601
602
603
604
605 public FileMode getFileMode() {
606 return FileMode.fromBits(getRawMode());
607 }
608
609
610
611
612
613
614
615
616
617
618
619
620 public void setFileMode(FileMode mode) {
621 switch (mode.getBits() & FileMode.TYPE_MASK) {
622 case FileMode.TYPE_MISSING:
623 case FileMode.TYPE_TREE:
624 throw new IllegalArgumentException(MessageFormat.format(
625 JGitText.get().invalidModeForPath, mode, getPathString()));
626 }
627 NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
628 }
629
630 void setFileMode(int mode) {
631 NB.encodeInt32(info, infoOffset + P_MODE, mode);
632 }
633
634
635
636
637
638
639
640 public long getCreationTime() {
641 return decodeTS(P_CTIME);
642 }
643
644
645
646
647
648
649
650 public void setCreationTime(long when) {
651 encodeTS(P_CTIME, when);
652 }
653
654
655
656
657
658
659
660
661
662
663
664
665 @Deprecated
666 public long getLastModified() {
667 return decodeTS(P_MTIME);
668 }
669
670
671
672
673
674
675
676
677
678
679
680 public Instant getLastModifiedInstant() {
681 return decodeTSInstant(P_MTIME);
682 }
683
684
685
686
687
688
689
690
691 @Deprecated
692 public void setLastModified(long when) {
693 encodeTS(P_MTIME, when);
694 }
695
696
697
698
699
700
701
702
703 public void setLastModified(Instant when) {
704 encodeTS(P_MTIME, when);
705 }
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724 public int getLength() {
725 return NB.decodeInt32(info, infoOffset + P_SIZE);
726 }
727
728
729
730
731
732
733
734
735 public void setLength(int sz) {
736 NB.encodeInt32(info, infoOffset + P_SIZE, sz);
737 }
738
739
740
741
742
743
744
745 public void setLength(long sz) {
746 setLength((int) sz);
747 }
748
749
750
751
752
753
754
755
756
757 public ObjectId getObjectId() {
758 return ObjectId.fromRaw(idBuffer(), idOffset());
759 }
760
761
762
763
764
765
766
767
768
769 public void setObjectId(AnyObjectId id) {
770 id.copyRawTo(idBuffer(), idOffset());
771 }
772
773
774
775
776
777
778
779
780
781
782 public void setObjectIdFromRaw(byte[] bs, int p) {
783 final int n = Constants.OBJECT_ID_LENGTH;
784 System.arraycopy(bs, p, idBuffer(), idOffset(), n);
785 }
786
787
788
789
790
791
792
793
794
795
796
797
798
799 public String getPathString() {
800 return toString(path);
801 }
802
803
804
805
806
807
808
809 public byte[] getRawPath() {
810 return path.clone();
811 }
812
813
814
815
816
817
818 @SuppressWarnings("nls")
819 @Override
820 public String toString() {
821 return getFileMode() + " " + getLength() + " "
822 + getLastModifiedInstant()
823 + " " + getObjectId() + " " + getStage() + " "
824 + getPathString() + "\n";
825 }
826
827
828
829
830
831
832
833
834
835
836 public void copyMetaData(DirCacheEntry src) {
837 copyMetaData(src, false);
838 }
839
840
841
842
843
844
845
846
847
848
849
850
851 void copyMetaData(DirCacheEntry src, boolean keepStage) {
852 int origflags = NB.decodeUInt16(info, infoOffset + P_FLAGS);
853 int newflags = NB.decodeUInt16(src.info, src.infoOffset + P_FLAGS);
854 System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
855 final int pLen = origflags & NAME_MASK;
856 final int SHIFTED_STAGE_MASK = 0x3 << 12;
857 final int pStageShifted;
858 if (keepStage)
859 pStageShifted = origflags & SHIFTED_STAGE_MASK;
860 else
861 pStageShifted = newflags & SHIFTED_STAGE_MASK;
862 NB.encodeInt16(info, infoOffset + P_FLAGS, pStageShifted | pLen
863 | (newflags & ~NAME_MASK & ~SHIFTED_STAGE_MASK));
864 }
865
866
867
868
869 boolean isExtended() {
870 return (info[infoOffset + P_FLAGS] & EXTENDED) != 0;
871 }
872
873 private long decodeTS(int pIdx) {
874 final int base = infoOffset + pIdx;
875 final int sec = NB.decodeInt32(info, base);
876 final int ms = NB.decodeInt32(info, base + 4) / 1000000;
877 return 1000L * sec + ms;
878 }
879
880 private Instant decodeTSInstant(int pIdx) {
881 final int base = infoOffset + pIdx;
882 final int sec = NB.decodeInt32(info, base);
883 final int nano = NB.decodeInt32(info, base + 4);
884 return Instant.ofEpochSecond(sec, nano);
885 }
886
887 private void encodeTS(int pIdx, long when) {
888 final int base = infoOffset + pIdx;
889 NB.encodeInt32(info, base, (int) (when / 1000));
890 NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
891 }
892
893 private void encodeTS(int pIdx, Instant when) {
894 final int base = infoOffset + pIdx;
895 NB.encodeInt32(info, base, (int) when.getEpochSecond());
896 NB.encodeInt32(info, base + 4, when.getNano());
897 }
898
899 private int getExtendedFlags() {
900 if (isExtended()) {
901 return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
902 }
903 return 0;
904 }
905
906 private static void checkPath(byte[] path) {
907 try {
908 SystemReader.getInstance().checkPath(path);
909 } catch (CorruptObjectException e) {
910 InvalidPathException p = new InvalidPathException(toString(path));
911 p.initCause(e);
912 throw p;
913 }
914 }
915
916 static String toString(byte[] path) {
917 return UTF_8.decode(ByteBuffer.wrap(path)).toString();
918 }
919
920 static int getMaximumInfoLength(boolean extended) {
921 return extended ? INFO_LEN_EXTENDED : INFO_LEN;
922 }
923 }