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 public boolean isSkipWorkTree() {
550 return (getExtendedFlags() & SKIP_WORKTREE) != 0;
551 }
552
553
554
555
556
557
558 public boolean isIntentToAdd() {
559 return (getExtendedFlags() & INTENT_TO_ADD) != 0;
560 }
561
562
563
564
565
566
567
568 public boolean isMerged() {
569 return getStage() == STAGE_0;
570 }
571
572
573
574
575
576
577
578 public int getRawMode() {
579 return NB.decodeInt32(info, infoOffset + P_MODE);
580 }
581
582
583
584
585
586
587 public FileMode getFileMode() {
588 return FileMode.fromBits(getRawMode());
589 }
590
591
592
593
594
595
596
597
598
599
600
601
602 public void setFileMode(FileMode mode) {
603 switch (mode.getBits() & FileMode.TYPE_MASK) {
604 case FileMode.TYPE_MISSING:
605 case FileMode.TYPE_TREE:
606 throw new IllegalArgumentException(MessageFormat.format(
607 JGitText.get().invalidModeForPath, mode, getPathString()));
608 }
609 NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
610 }
611
612 void setFileMode(int mode) {
613 NB.encodeInt32(info, infoOffset + P_MODE, mode);
614 }
615
616
617
618
619
620
621
622 public long getCreationTime() {
623 return decodeTS(P_CTIME);
624 }
625
626
627
628
629
630
631
632 public void setCreationTime(long when) {
633 encodeTS(P_CTIME, when);
634 }
635
636
637
638
639
640
641
642
643
644
645
646
647 @Deprecated
648 public long getLastModified() {
649 return decodeTS(P_MTIME);
650 }
651
652
653
654
655
656
657
658
659
660
661
662 public Instant getLastModifiedInstant() {
663 return decodeTSInstant(P_MTIME);
664 }
665
666
667
668
669
670
671
672
673 @Deprecated
674 public void setLastModified(long when) {
675 encodeTS(P_MTIME, when);
676 }
677
678
679
680
681
682
683
684
685 public void setLastModified(Instant when) {
686 encodeTS(P_MTIME, when);
687 }
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706 public int getLength() {
707 return NB.decodeInt32(info, infoOffset + P_SIZE);
708 }
709
710
711
712
713
714
715
716
717 public void setLength(int sz) {
718 NB.encodeInt32(info, infoOffset + P_SIZE, sz);
719 }
720
721
722
723
724
725
726
727 public void setLength(long sz) {
728 setLength((int) sz);
729 }
730
731
732
733
734
735
736
737
738
739 public ObjectId getObjectId() {
740 return ObjectId.fromRaw(idBuffer(), idOffset());
741 }
742
743
744
745
746
747
748
749
750
751 public void setObjectId(AnyObjectId id) {
752 id.copyRawTo(idBuffer(), idOffset());
753 }
754
755
756
757
758
759
760
761
762
763
764 public void setObjectIdFromRaw(byte[] bs, int p) {
765 final int n = Constants.OBJECT_ID_LENGTH;
766 System.arraycopy(bs, p, idBuffer(), idOffset(), n);
767 }
768
769
770
771
772
773
774
775
776
777
778
779
780
781 public String getPathString() {
782 return toString(path);
783 }
784
785
786
787
788
789
790
791 public byte[] getRawPath() {
792 return path.clone();
793 }
794
795
796
797
798
799
800 @SuppressWarnings("nls")
801 @Override
802 public String toString() {
803 return getFileMode() + " " + getLength() + " "
804 + getLastModifiedInstant()
805 + " " + getObjectId() + " " + getStage() + " "
806 + getPathString() + "\n";
807 }
808
809
810
811
812
813
814
815
816
817
818 public void copyMetaData(DirCacheEntry src) {
819 copyMetaData(src, false);
820 }
821
822
823
824
825
826
827
828
829
830
831
832
833 void copyMetaData(DirCacheEntry src, boolean keepStage) {
834 int origflags = NB.decodeUInt16(info, infoOffset + P_FLAGS);
835 int newflags = NB.decodeUInt16(src.info, src.infoOffset + P_FLAGS);
836 System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
837 final int pLen = origflags & NAME_MASK;
838 final int SHIFTED_STAGE_MASK = 0x3 << 12;
839 final int pStageShifted;
840 if (keepStage)
841 pStageShifted = origflags & SHIFTED_STAGE_MASK;
842 else
843 pStageShifted = newflags & SHIFTED_STAGE_MASK;
844 NB.encodeInt16(info, infoOffset + P_FLAGS, pStageShifted | pLen
845 | (newflags & ~NAME_MASK & ~SHIFTED_STAGE_MASK));
846 }
847
848
849
850
851 boolean isExtended() {
852 return (info[infoOffset + P_FLAGS] & EXTENDED) != 0;
853 }
854
855 private long decodeTS(int pIdx) {
856 final int base = infoOffset + pIdx;
857 final int sec = NB.decodeInt32(info, base);
858 final int ms = NB.decodeInt32(info, base + 4) / 1000000;
859 return 1000L * sec + ms;
860 }
861
862 private Instant decodeTSInstant(int pIdx) {
863 final int base = infoOffset + pIdx;
864 final int sec = NB.decodeInt32(info, base);
865 final int nano = NB.decodeInt32(info, base + 4);
866 return Instant.ofEpochSecond(sec, nano);
867 }
868
869 private void encodeTS(int pIdx, long when) {
870 final int base = infoOffset + pIdx;
871 NB.encodeInt32(info, base, (int) (when / 1000));
872 NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
873 }
874
875 private void encodeTS(int pIdx, Instant when) {
876 final int base = infoOffset + pIdx;
877 NB.encodeInt32(info, base, (int) when.getEpochSecond());
878 NB.encodeInt32(info, base + 4, when.getNano());
879 }
880
881 private int getExtendedFlags() {
882 if (isExtended()) {
883 return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
884 }
885 return 0;
886 }
887
888 private static void checkPath(byte[] path) {
889 try {
890 SystemReader.getInstance().checkPath(path);
891 } catch (CorruptObjectException e) {
892 InvalidPathException p = new InvalidPathException(toString(path));
893 p.initCause(e);
894 throw p;
895 }
896 }
897
898 static String toString(byte[] path) {
899 return UTF_8.decode(ByteBuffer.wrap(path)).toString();
900 }
901
902 static int getMaximumInfoLength(boolean extended) {
903 return extended ? INFO_LEN_EXTENDED : INFO_LEN;
904 }
905 }