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
47 package org.eclipse.jgit.treewalk;
48
49 import static java.nio.charset.StandardCharsets.UTF_8;
50
51 import java.io.ByteArrayInputStream;
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.FileNotFoundException;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.nio.ByteBuffer;
58 import java.nio.CharBuffer;
59 import java.nio.charset.CharacterCodingException;
60 import java.nio.charset.CharsetEncoder;
61 import java.text.MessageFormat;
62 import java.time.Instant;
63 import java.util.Arrays;
64 import java.util.Collections;
65 import java.util.Comparator;
66 import java.util.HashMap;
67 import java.util.Map;
68
69 import org.eclipse.jgit.api.errors.FilterFailedException;
70 import org.eclipse.jgit.attributes.AttributesNode;
71 import org.eclipse.jgit.attributes.AttributesRule;
72 import org.eclipse.jgit.attributes.FilterCommand;
73 import org.eclipse.jgit.attributes.FilterCommandRegistry;
74 import org.eclipse.jgit.diff.RawText;
75 import org.eclipse.jgit.dircache.DirCacheEntry;
76 import org.eclipse.jgit.dircache.DirCacheIterator;
77 import org.eclipse.jgit.errors.CorruptObjectException;
78 import org.eclipse.jgit.errors.LargeObjectException;
79 import org.eclipse.jgit.errors.MissingObjectException;
80 import org.eclipse.jgit.errors.NoWorkTreeException;
81 import org.eclipse.jgit.ignore.FastIgnoreRule;
82 import org.eclipse.jgit.ignore.IgnoreNode;
83 import org.eclipse.jgit.internal.JGitText;
84 import org.eclipse.jgit.lib.Constants;
85 import org.eclipse.jgit.lib.CoreConfig;
86 import org.eclipse.jgit.lib.CoreConfig.CheckStat;
87 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
88 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
89 import org.eclipse.jgit.lib.FileMode;
90 import org.eclipse.jgit.lib.ObjectId;
91 import org.eclipse.jgit.lib.ObjectLoader;
92 import org.eclipse.jgit.lib.ObjectReader;
93 import org.eclipse.jgit.lib.Repository;
94 import org.eclipse.jgit.submodule.SubmoduleWalk;
95 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
96 import org.eclipse.jgit.util.FS;
97 import org.eclipse.jgit.util.FS.ExecutionResult;
98 import org.eclipse.jgit.util.Holder;
99 import org.eclipse.jgit.util.IO;
100 import org.eclipse.jgit.util.Paths;
101 import org.eclipse.jgit.util.RawParseUtils;
102 import org.eclipse.jgit.util.TemporaryBuffer;
103 import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
104 import org.eclipse.jgit.util.io.EolStreamTypeUtil;
105 import org.eclipse.jgit.util.sha1.SHA1;
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public abstract class WorkingTreeIterator extends AbstractTreeIterator {
121 private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
122
123
124 protected static final Entry[] EOF = {};
125
126
127 static final int BUFFER_SIZE = 2048;
128
129
130
131
132
133 private static final long MAXIMUM_FILE_SIZE_TO_READ_FULLY = 65536;
134
135
136 private final IteratorState state;
137
138
139 private byte[] contentId;
140
141
142 private int contentIdFromPtr;
143
144
145 private Entry[] entries;
146
147
148 private int entryCnt;
149
150
151 private int ptr;
152
153
154 private IgnoreNode ignoreNode;
155
156
157
158
159
160 private Holder<String> cleanFilterCommandHolder;
161
162
163
164
165
166 private Holder<EolStreamType> eolStreamTypeHolder;
167
168
169 protected Repository repository;
170
171
172 private long canonLen = -1;
173
174
175 private int contentIdOffset;
176
177
178 private final InstantComparatortantComparator">InstantComparator timestampComparator = new InstantComparator();
179
180
181
182
183
184
185
186 protected WorkingTreeIterator(WorkingTreeOptions options) {
187 super();
188 state = new IteratorState(options);
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 protected WorkingTreeIterator(final String prefix,
209 WorkingTreeOptions options) {
210 super(prefix);
211 state = new IteratorState(options);
212 }
213
214
215
216
217
218
219
220 protected WorkingTreeIterator../../../org/eclipse/jgit/treewalk/WorkingTreeIterator.html#WorkingTreeIterator">WorkingTreeIterator(WorkingTreeIterator p) {
221 super(p);
222 state = p.state;
223 repository = p.repository;
224 }
225
226
227
228
229
230
231
232
233
234
235 protected void initRootIterator(Repository repo) {
236 repository = repo;
237 Entry entry;
238 if (ignoreNode instanceof PerDirectoryIgnoreNode)
239 entry = ((PerDirectoryIgnoreNode) ignoreNode).entry;
240 else
241 entry = null;
242 ignoreNode = new RootIgnoreNode(entry, repo);
243 }
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260 public void setDirCacheIterator(TreeWalk walk, int treeId) {
261 state.walk = walk;
262 state.dirCacheTree = treeId;
263 }
264
265
266
267
268
269
270
271
272
273 protected DirCacheIterator getDirCacheIterator() {
274 if (state.dirCacheTree >= 0 && state.walk != null) {
275 return state.walk.getTree(state.dirCacheTree,
276 DirCacheIterator.class);
277 }
278 return null;
279 }
280
281
282
283
284
285
286
287
288
289
290 public void setWalkIgnoredDirectories(boolean includeIgnored) {
291 state.walkIgnored = includeIgnored;
292 }
293
294
295
296
297
298
299
300 public boolean walksIgnoredDirectories() {
301 return state.walkIgnored;
302 }
303
304
305 @Override
306 public boolean hasId() {
307 if (contentIdFromPtr == ptr)
308 return true;
309 return (mode & FileMode.TYPE_MASK) == FileMode.TYPE_FILE;
310 }
311
312
313 @Override
314 public byte[] idBuffer() {
315 if (contentIdFromPtr == ptr)
316 return contentId;
317
318 if (state.walk != null) {
319
320
321
322 DirCacheIterator i = state.walk.getTree(state.dirCacheTree,
323 DirCacheIterator.class);
324 if (i != null) {
325 DirCacheEntry ent = i.getDirCacheEntry();
326 if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL
327 && ((ent.getFileMode().getBits()
328 & FileMode.TYPE_MASK) != FileMode.TYPE_GITLINK)) {
329 contentIdOffset = i.idOffset();
330 contentIdFromPtr = ptr;
331 return contentId = i.idBuffer();
332 }
333 contentIdOffset = 0;
334 } else {
335 contentIdOffset = 0;
336 }
337 }
338 switch (mode & FileMode.TYPE_MASK) {
339 case FileMode.TYPE_SYMLINK:
340 case FileMode.TYPE_FILE:
341 contentIdFromPtr = ptr;
342 return contentId = idBufferBlob(entries[ptr]);
343 case FileMode.TYPE_GITLINK:
344 contentIdFromPtr = ptr;
345 return contentId = idSubmodule(entries[ptr]);
346 }
347 return zeroid;
348 }
349
350
351 @Override
352 public boolean isWorkTree() {
353 return true;
354 }
355
356
357
358
359
360
361
362
363
364 protected byte[] idSubmodule(Entry e) {
365 if (repository == null)
366 return zeroid;
367 File directory;
368 try {
369 directory = repository.getWorkTree();
370 } catch (NoWorkTreeException nwte) {
371 return zeroid;
372 }
373 return idSubmodule(directory, e);
374 }
375
376
377
378
379
380
381
382
383
384
385
386
387 protected byte[] idSubmodule(File directory, Entry e) {
388 try (Repository submoduleRepo = SubmoduleWalk.getSubmoduleRepository(
389 directory, e.getName(),
390 repository != null ? repository.getFS() : FS.DETECTED)) {
391 if (submoduleRepo == null) {
392 return zeroid;
393 }
394 ObjectId head = submoduleRepo.resolve(Constants.HEAD);
395 if (head == null) {
396 return zeroid;
397 }
398 byte[] id = new byte[Constants.OBJECT_ID_LENGTH];
399 head.copyRawTo(id, 0);
400 return id;
401 } catch (IOException exception) {
402 return zeroid;
403 }
404 }
405
406 private static final byte[] digits = { '0', '1', '2', '3', '4', '5', '6',
407 '7', '8', '9' };
408
409 private static final byte[] hblob = Constants
410 .encodedTypeString(Constants.OBJ_BLOB);
411
412 private byte[] idBufferBlob(Entry e) {
413 try {
414 final InputStream is = e.openInputStream();
415 if (is == null)
416 return zeroid;
417 try {
418 state.initializeReadBuffer();
419
420 final long len = e.getLength();
421 InputStream filteredIs = possiblyFilteredInputStream(e, is, len,
422 OperationType.CHECKIN_OP);
423 return computeHash(filteredIs, canonLen);
424 } finally {
425 safeClose(is);
426 }
427 } catch (IOException err) {
428
429 return zeroid;
430 }
431 }
432
433 private InputStream possiblyFilteredInputStream(final Entry e,
434 final InputStream is, final long len) throws IOException {
435 return possiblyFilteredInputStream(e, is, len, null);
436
437 }
438
439 private InputStream possiblyFilteredInputStream(final Entry e,
440 final InputStream is, final long len, OperationType opType)
441 throws IOException {
442 if (getCleanFilterCommand() == null
443 && getEolStreamType(opType) == EolStreamType.DIRECT) {
444 canonLen = len;
445 return is;
446 }
447
448 if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
449 ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
450 rawbuf = filterClean(rawbuf.array(), rawbuf.limit(), opType);
451 canonLen = rawbuf.limit();
452 return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen);
453 }
454
455 if (getCleanFilterCommand() == null && isBinary(e)) {
456 canonLen = len;
457 return is;
458 }
459
460 final InputStream lenIs = filterClean(e.openInputStream(),
461 opType);
462 try {
463 canonLen = computeLength(lenIs);
464 } finally {
465 safeClose(lenIs);
466 }
467 return filterClean(is, opType);
468 }
469
470 private static void safeClose(InputStream in) {
471 try {
472 in.close();
473 } catch (IOException err2) {
474
475
476
477 }
478 }
479
480 private static boolean isBinary(Entry entry) throws IOException {
481 InputStream in = entry.openInputStream();
482 try {
483 return RawText.isBinary(in);
484 } finally {
485 safeClose(in);
486 }
487 }
488
489 private ByteBuffer filterClean(byte[] src, int n, OperationType opType)
490 throws IOException {
491 InputStream in = new ByteArrayInputStream(src);
492 try {
493 return IO.readWholeStream(filterClean(in, opType), n);
494 } finally {
495 safeClose(in);
496 }
497 }
498
499 private InputStream filterClean(InputStream in) throws IOException {
500 return filterClean(in, null);
501 }
502
503 private InputStream filterClean(InputStream in, OperationType opType)
504 throws IOException {
505 in = handleAutoCRLF(in, opType);
506 String filterCommand = getCleanFilterCommand();
507 if (filterCommand != null) {
508 if (FilterCommandRegistry.isRegistered(filterCommand)) {
509 LocalFile buffer = new TemporaryBuffer.LocalFile(null);
510 FilterCommand command = FilterCommandRegistry
511 .createFilterCommand(filterCommand, repository, in,
512 buffer);
513 while (command.run() != -1) {
514
515 }
516 return buffer.openInputStreamWithAutoDestroy();
517 }
518 FS fs = repository.getFS();
519 ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
520 new String[0]);
521 filterProcessBuilder.directory(repository.getWorkTree());
522 filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
523 repository.getDirectory().getAbsolutePath());
524 ExecutionResult result;
525 try {
526 result = fs.execute(filterProcessBuilder, in);
527 } catch (IOException | InterruptedException e) {
528 throw new IOException(new FilterFailedException(e,
529 filterCommand, getEntryPathString()));
530 }
531 int rc = result.getRc();
532 if (rc != 0) {
533 throw new IOException(new FilterFailedException(rc,
534 filterCommand, getEntryPathString(),
535 result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
536 RawParseUtils.decode(result.getStderr()
537 .toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
538 }
539 return result.getStdout().openInputStreamWithAutoDestroy();
540 }
541 return in;
542 }
543
544 private InputStream handleAutoCRLF(InputStream in, OperationType opType)
545 throws IOException {
546 return EolStreamTypeUtil.wrapInputStream(in, getEolStreamType(opType));
547 }
548
549
550
551
552
553
554 public WorkingTreeOptions getOptions() {
555 return state.options;
556 }
557
558
559 @Override
560 public int idOffset() {
561 return contentIdOffset;
562 }
563
564
565 @Override
566 public void reset() {
567 if (!first()) {
568 ptr = 0;
569 if (!eof())
570 parseEntry();
571 }
572 }
573
574
575 @Override
576 public boolean first() {
577 return ptr == 0;
578 }
579
580
581 @Override
582 public boolean eof() {
583 return ptr == entryCnt;
584 }
585
586
587 @Override
588 public void next(int delta) throws CorruptObjectException {
589 ptr += delta;
590 if (!eof()) {
591 parseEntry();
592 }
593 }
594
595
596 @Override
597 public void back(int delta) throws CorruptObjectException {
598 ptr -= delta;
599 parseEntry();
600 }
601
602 private void parseEntry() {
603 final Entry e = entries[ptr];
604 mode = e.getMode().getBits();
605
606 final int nameLen = e.encodedNameLen;
607 ensurePathCapacity(pathOffset + nameLen, pathOffset);
608 System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
609 pathLen = pathOffset + nameLen;
610 canonLen = -1;
611 cleanFilterCommandHolder = null;
612 eolStreamTypeHolder = null;
613 }
614
615
616
617
618
619
620 public long getEntryLength() {
621 return current().getLength();
622 }
623
624
625
626
627
628
629
630 public long getEntryContentLength() throws IOException {
631 if (canonLen == -1) {
632 long rawLen = getEntryLength();
633 if (rawLen == 0)
634 canonLen = 0;
635 InputStream is = current().openInputStream();
636 try {
637
638 possiblyFilteredInputStream(current(), is, current()
639 .getLength());
640 } finally {
641 safeClose(is);
642 }
643 }
644 return canonLen;
645 }
646
647
648
649
650
651
652
653
654 @Deprecated
655 public long getEntryLastModified() {
656 return current().getLastModified();
657 }
658
659
660
661
662
663
664
665 public Instant getEntryLastModifiedInstant() {
666 return current().getLastModifiedInstant();
667 }
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685 public InputStream openEntryStream() throws IOException {
686 InputStream rawis = current().openInputStream();
687 if (getCleanFilterCommand() == null
688 && getEolStreamType() == EolStreamType.DIRECT) {
689 return rawis;
690 }
691 return filterClean(rawis);
692 }
693
694
695
696
697
698
699
700
701 public boolean isEntryIgnored() throws IOException {
702 return isEntryIgnored(pathLen);
703 }
704
705
706
707
708
709
710
711
712
713
714 protected boolean isEntryIgnored(int pLen) throws IOException {
715 return isEntryIgnored(pLen, mode);
716 }
717
718
719
720
721
722
723
724
725
726
727
728
729 private boolean isEntryIgnored(int pLen, int fileMode)
730 throws IOException {
731
732
733
734
735 final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
736 String pathRel = TreeWalk.pathOf(this.path, pOff, pLen);
737 String parentRel = getParentPath(pathRel);
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755 if (isDirectoryIgnored(parentRel)) {
756 return true;
757 }
758
759 IgnoreNode rules = getIgnoreNode();
760 final Boolean ignored = rules != null
761 ? rules.checkIgnored(pathRel, FileMode.TREE.equals(fileMode))
762 : null;
763 if (ignored != null) {
764 return ignored.booleanValue();
765 }
766 return parent instanceof WorkingTreeIterator
767 && ((WorkingTreeIterator) parent).isEntryIgnored(pLen,
768 fileMode);
769 }
770
771 private IgnoreNode getIgnoreNode() throws IOException {
772 if (ignoreNode instanceof PerDirectoryIgnoreNode)
773 ignoreNode = ((PerDirectoryIgnoreNode) ignoreNode).load();
774 return ignoreNode;
775 }
776
777
778
779
780
781
782
783
784
785 public AttributesNode getEntryAttributesNode() throws IOException {
786 if (attributesNode instanceof PerDirectoryAttributesNode)
787 attributesNode = ((PerDirectoryAttributesNode) attributesNode)
788 .load();
789 return attributesNode;
790 }
791
792 private static final Comparator<Entry> ENTRY_CMP = (Entry a,
793 Entry b) -> Paths.compare(a.encodedName, 0, a.encodedNameLen,
794 a.getMode().getBits(), b.encodedName, 0, b.encodedNameLen,
795 b.getMode().getBits());
796
797
798
799
800
801
802
803
804 protected void init(Entry[] list) {
805
806
807
808
809 entries = list;
810 int i, o;
811
812 final CharsetEncoder nameEncoder = state.nameEncoder;
813 for (i = 0, o = 0; i < entries.length; i++) {
814 final Entry e = entries[i];
815 if (e == null)
816 continue;
817 final String name = e.getName();
818 if (".".equals(name) || "..".equals(name))
819 continue;
820 if (Constants.DOT_GIT.equals(name))
821 continue;
822 if (Constants.DOT_GIT_IGNORE.equals(name))
823 ignoreNode = new PerDirectoryIgnoreNode(e);
824 if (Constants.DOT_GIT_ATTRIBUTES.equals(name))
825 attributesNode = new PerDirectoryAttributesNode(e);
826 if (i != o)
827 entries[o] = e;
828 e.encodeName(nameEncoder);
829 o++;
830 }
831 entryCnt = o;
832 Arrays.sort(entries, 0, entryCnt, ENTRY_CMP);
833
834 contentIdFromPtr = -1;
835 ptr = 0;
836 if (!eof())
837 parseEntry();
838 else if (pathLen == 0)
839 pathLen = pathOffset;
840 }
841
842
843
844
845
846
847 protected Entry current() {
848 return entries[ptr];
849 }
850
851
852
853
854
855 public enum MetadataDiff {
856
857
858
859
860
861 EQUAL,
862
863
864
865
866
867 DIFFER_BY_METADATA,
868
869
870 SMUDGED,
871
872
873
874
875
876 DIFFER_BY_TIMESTAMP
877 }
878
879
880
881
882
883
884
885
886 public boolean isModeDifferent(int rawMode) {
887
888
889
890 int modeDiff = getEntryRawMode() ^ rawMode;
891
892 if (modeDiff == 0)
893 return false;
894
895
896 if (getOptions().getSymLinks() == SymLinks.FALSE)
897 if (FileMode.SYMLINK.equals(rawMode))
898 return false;
899
900
901
902
903 if (!state.options.isFileMode())
904 modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
905 return modeDiff != 0;
906 }
907
908
909
910
911
912
913
914
915
916
917
918
919 public MetadataDiff compareMetadata(DirCacheEntry entry) {
920 if (entry.isAssumeValid())
921 return MetadataDiff.EQUAL;
922
923 if (entry.isUpdateNeeded())
924 return MetadataDiff.DIFFER_BY_METADATA;
925
926 if (isModeDifferent(entry.getRawMode()))
927 return MetadataDiff.DIFFER_BY_METADATA;
928
929
930 int type = mode & FileMode.TYPE_MASK;
931 if (type == FileMode.TYPE_TREE || type == FileMode.TYPE_GITLINK)
932 return MetadataDiff.EQUAL;
933
934 if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
935 return MetadataDiff.DIFFER_BY_METADATA;
936
937
938
939
940
941
942 Instant cacheLastModified = entry.getLastModifiedInstant();
943 Instant fileLastModified = getEntryLastModifiedInstant();
944 if (timestampComparator.compare(cacheLastModified, fileLastModified,
945 getOptions().getCheckStat() == CheckStat.MINIMAL) != 0) {
946 return MetadataDiff.DIFFER_BY_TIMESTAMP;
947 }
948
949 if (entry.isSmudged()) {
950 return MetadataDiff.SMUDGED;
951 }
952
953 return MetadataDiff.EQUAL;
954 }
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975 public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
976 ObjectReader reader) throws IOException {
977 if (entry == null)
978 return !FileMode.MISSING.equals(getEntryFileMode());
979 MetadataDiff diff = compareMetadata(entry);
980 switch (diff) {
981 case DIFFER_BY_TIMESTAMP:
982 if (forceContentCheck) {
983
984
985 return contentCheck(entry, reader);
986 }
987
988 return true;
989 case SMUDGED:
990
991
992 return contentCheck(entry, reader);
993 case EQUAL:
994 if (mode == FileMode.SYMLINK.getBits()) {
995 return contentCheck(entry, reader);
996 }
997 return false;
998 case DIFFER_BY_METADATA:
999 if (mode == FileMode.TREE.getBits()
1000 && entry.getFileMode().equals(FileMode.GITLINK)) {
1001 byte[] idBuffer = idBuffer();
1002 int idOffset = idOffset();
1003 if (entry.getObjectId().compareTo(idBuffer, idOffset) == 0) {
1004 return true;
1005 } else if (ObjectId.zeroId().compareTo(idBuffer,
1006 idOffset) == 0) {
1007 return new File(repository.getWorkTree(),
1008 entry.getPathString()).list().length > 0;
1009 }
1010 return false;
1011 } else if (mode == FileMode.SYMLINK.getBits())
1012 return contentCheck(entry, reader);
1013 return true;
1014 default:
1015 throw new IllegalStateException(MessageFormat.format(
1016 JGitText.get().unexpectedCompareResult, diff.name()));
1017 }
1018 }
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031 public FileMode getIndexFileMode(DirCacheIterator indexIter) {
1032 final FileMode wtMode = getEntryFileMode();
1033 if (indexIter == null) {
1034 return wtMode;
1035 }
1036 final FileMode iMode = indexIter.getEntryFileMode();
1037 if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
1038 return wtMode;
1039 }
1040 if (!getOptions().isFileMode()) {
1041 if (FileMode.REGULAR_FILE == wtMode
1042 && FileMode.EXECUTABLE_FILE == iMode) {
1043 return iMode;
1044 }
1045 if (FileMode.EXECUTABLE_FILE == wtMode
1046 && FileMode.REGULAR_FILE == iMode) {
1047 return iMode;
1048 }
1049 }
1050 if (FileMode.GITLINK == iMode
1051 && FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
1052 return iMode;
1053 }
1054 if (FileMode.TREE == iMode
1055 && FileMode.GITLINK == wtMode) {
1056 return iMode;
1057 }
1058 return wtMode;
1059 }
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073 private boolean contentCheck(DirCacheEntry entry, ObjectReader reader)
1074 throws IOException {
1075 if (getEntryObjectId().equals(entry.getObjectId())) {
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086 entry.setLength((int) getEntryLength());
1087
1088 return false;
1089 }
1090 if (mode == FileMode.SYMLINK.getBits()) {
1091 return !new File(readSymlinkTarget(current())).equals(
1092 new File(readContentAsNormalizedString(entry, reader)));
1093 }
1094
1095 return true;
1096 }
1097
1098 private static String readContentAsNormalizedString(DirCacheEntry entry,
1099 ObjectReader reader) throws MissingObjectException, IOException {
1100 ObjectLoader open = reader.open(entry.getObjectId());
1101 byte[] cachedBytes = open.getCachedBytes();
1102 return FS.detect().normalize(RawParseUtils.decode(cachedBytes));
1103 }
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118 protected String readSymlinkTarget(Entry entry) throws IOException {
1119 if (!entry.getMode().equals(FileMode.SYMLINK)) {
1120 throw new java.nio.file.NotLinkException(entry.getName());
1121 }
1122 long length = entry.getLength();
1123 byte[] content = new byte[(int) length];
1124 try (InputStream is = entry.openInputStream()) {
1125 int bytesRead = IO.readFully(is, content, 0);
1126 return FS.detect()
1127 .normalize(RawParseUtils.decode(content, 0, bytesRead));
1128 }
1129 }
1130
1131 private static long computeLength(InputStream in) throws IOException {
1132
1133
1134
1135 long length = 0;
1136 for (;;) {
1137 long n = in.skip(1 << 20);
1138 if (n <= 0)
1139 break;
1140 length += n;
1141 }
1142 return length;
1143 }
1144
1145 private byte[] computeHash(InputStream in, long length) throws IOException {
1146 SHA1 contentDigest = SHA1.newInstance();
1147 final byte[] contentReadBuffer = state.contentReadBuffer;
1148
1149 contentDigest.update(hblob);
1150 contentDigest.update((byte) ' ');
1151
1152 long sz = length;
1153 if (sz == 0) {
1154 contentDigest.update((byte) '0');
1155 } else {
1156 final int bufn = contentReadBuffer.length;
1157 int p = bufn;
1158 do {
1159 contentReadBuffer[--p] = digits[(int) (sz % 10)];
1160 sz /= 10;
1161 } while (sz > 0);
1162 contentDigest.update(contentReadBuffer, p, bufn - p);
1163 }
1164 contentDigest.update((byte) 0);
1165
1166 for (;;) {
1167 final int r = in.read(contentReadBuffer);
1168 if (r <= 0)
1169 break;
1170 contentDigest.update(contentReadBuffer, 0, r);
1171 sz += r;
1172 }
1173 if (sz != length)
1174 return zeroid;
1175 return contentDigest.digest();
1176 }
1177
1178
1179
1180
1181
1182
1183 public static abstract class Entry {
1184 byte[] encodedName;
1185
1186 int encodedNameLen;
1187
1188 void encodeName(CharsetEncoder enc) {
1189 final ByteBuffer b;
1190 try {
1191 b = enc.encode(CharBuffer.wrap(getName()));
1192 } catch (CharacterCodingException e) {
1193
1194 throw new RuntimeException(MessageFormat.format(
1195 JGitText.get().unencodeableFile, getName()));
1196 }
1197
1198 encodedNameLen = b.limit();
1199 if (b.hasArray() && b.arrayOffset() == 0)
1200 encodedName = b.array();
1201 else
1202 b.get(encodedName = new byte[encodedNameLen]);
1203 }
1204
1205 @Override
1206 public String toString() {
1207 return getMode().toString() + " " + getName();
1208 }
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221 public abstract FileMode getMode();
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234 public abstract long getLength();
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248 @Deprecated
1249 public abstract long getLastModified();
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263 public abstract Instant getLastModifiedInstant();
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273 public abstract String getName();
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291 public abstract InputStream openInputStream() throws IOException;
1292 }
1293
1294
1295 private static class PerDirectoryIgnoreNode extends IgnoreNode {
1296 final Entry entry;
1297
1298 PerDirectoryIgnoreNode(Entry entry) {
1299 super(Collections.<FastIgnoreRule> emptyList());
1300 this.entry = entry;
1301 }
1302
1303 IgnoreNode load() throws IOException {
1304 IgnoreNode r = new IgnoreNode();
1305 try (InputStream in = entry.openInputStream()) {
1306 r.parse(in);
1307 }
1308 return r.getRules().isEmpty() ? null : r;
1309 }
1310 }
1311
1312
1313 private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
1314 final Repository repository;
1315
1316 RootIgnoreNode(Entry entry, Repository repository) {
1317 super(entry);
1318 this.repository = repository;
1319 }
1320
1321 @Override
1322 IgnoreNode load() throws IOException {
1323 IgnoreNode r;
1324 if (entry != null) {
1325 r = super.load();
1326 if (r == null)
1327 r = new IgnoreNode();
1328 } else {
1329 r = new IgnoreNode();
1330 }
1331
1332 FS fs = repository.getFS();
1333 String path = repository.getConfig().get(CoreConfig.KEY)
1334 .getExcludesFile();
1335 if (path != null) {
1336 File excludesfile;
1337 if (path.startsWith("~/"))
1338 excludesfile = fs.resolve(fs.userHome(), path.substring(2));
1339 else
1340 excludesfile = fs.resolve(null, path);
1341 loadRulesFromFile(r, excludesfile);
1342 }
1343
1344 File exclude = fs.resolve(repository.getDirectory(),
1345 Constants.INFO_EXCLUDE);
1346 loadRulesFromFile(r, exclude);
1347
1348 return r.getRules().isEmpty() ? null : r;
1349 }
1350
1351 private static void loadRulesFromFile(IgnoreNode r, File exclude)
1352 throws FileNotFoundException, IOException {
1353 if (FS.DETECTED.exists(exclude)) {
1354 try (FileInputStream in = new FileInputStream(exclude)) {
1355 r.parse(in);
1356 }
1357 }
1358 }
1359 }
1360
1361
1362 private static class PerDirectoryAttributesNode extends AttributesNode {
1363 final Entry entry;
1364
1365 PerDirectoryAttributesNode(Entry entry) {
1366 super(Collections.<AttributesRule> emptyList());
1367 this.entry = entry;
1368 }
1369
1370 AttributesNode load() throws IOException {
1371 AttributesNode r = new AttributesNode();
1372 try (InputStream in = entry.openInputStream()) {
1373 r.parse(in);
1374 }
1375 return r.getRules().isEmpty() ? null : r;
1376 }
1377 }
1378
1379
1380 private static final class IteratorState {
1381
1382 final WorkingTreeOptions options;
1383
1384
1385 final CharsetEncoder nameEncoder;
1386
1387
1388 byte[] contentReadBuffer;
1389
1390
1391 TreeWalk walk;
1392
1393
1394 int dirCacheTree = -1;
1395
1396
1397 boolean walkIgnored = false;
1398
1399 final Map<String, Boolean> directoryToIgnored = new HashMap<>();
1400
1401 IteratorState(WorkingTreeOptions options) {
1402 this.options = options;
1403 this.nameEncoder = UTF_8.newEncoder();
1404 }
1405
1406 void initializeReadBuffer() {
1407 if (contentReadBuffer == null) {
1408 contentReadBuffer = new byte[BUFFER_SIZE];
1409 }
1410 }
1411 }
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421 public String getCleanFilterCommand() throws IOException {
1422 if (cleanFilterCommandHolder == null) {
1423 String cmd = null;
1424 if (state.walk != null) {
1425 cmd = state.walk
1426 .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
1427 }
1428 cleanFilterCommandHolder = new Holder<>(cmd);
1429 }
1430 return cleanFilterCommandHolder.get();
1431 }
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443 public EolStreamType getEolStreamType() throws IOException {
1444 return getEolStreamType(null);
1445 }
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456 private EolStreamType getEolStreamType(OperationType opType)
1457 throws IOException {
1458 if (eolStreamTypeHolder == null) {
1459 EolStreamType type = null;
1460 if (state.walk != null) {
1461 type = state.walk.getEolStreamType(opType);
1462 OperationType operationType = opType != null ? opType
1463 : state.walk.getOperationType();
1464 if (OperationType.CHECKIN_OP.equals(operationType)
1465 && EolStreamType.AUTO_LF.equals(type)
1466 && hasCrLfInIndex(getDirCacheIterator())) {
1467
1468
1469 type = EolStreamType.DIRECT;
1470 }
1471 } else {
1472 switch (getOptions().getAutoCRLF()) {
1473 case FALSE:
1474 type = EolStreamType.DIRECT;
1475 break;
1476 case TRUE:
1477 case INPUT:
1478 type = EolStreamType.AUTO_LF;
1479 break;
1480 }
1481 }
1482 eolStreamTypeHolder = new Holder<>(type);
1483 }
1484 return eolStreamTypeHolder.get();
1485 }
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497 private boolean hasCrLfInIndex(DirCacheIterator dirCache) {
1498 if (dirCache == null) {
1499 return false;
1500 }
1501
1502 DirCacheEntry entry = dirCache.getDirCacheEntry();
1503 if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) {
1504 ObjectId blobId = entry.getObjectId();
1505 if (entry.getStage() > 0
1506 && entry.getStage() != DirCacheEntry.STAGE_2) {
1507 blobId = null;
1508
1509 byte[] name = entry.getRawPath();
1510 int i = 0;
1511 while (!dirCache.eof()) {
1512 dirCache.next(1);
1513 i++;
1514 entry = dirCache.getDirCacheEntry();
1515 if (entry == null
1516 || !Arrays.equals(name, entry.getRawPath())) {
1517 break;
1518 }
1519 if (entry.getStage() == DirCacheEntry.STAGE_2) {
1520 blobId = entry.getObjectId();
1521 break;
1522 }
1523 }
1524 dirCache.back(i);
1525 }
1526 if (blobId != null) {
1527 try (ObjectReader reader = repository.newObjectReader()) {
1528 ObjectLoader loader = reader.open(blobId,
1529 Constants.OBJ_BLOB);
1530 try {
1531 return RawText.isCrLfText(loader.getCachedBytes());
1532 } catch (LargeObjectException e) {
1533 try (InputStream in = loader.openStream()) {
1534 return RawText.isCrLfText(in);
1535 }
1536 }
1537 } catch (IOException e) {
1538
1539 }
1540 }
1541 }
1542 return false;
1543 }
1544
1545 private boolean isDirectoryIgnored(String pathRel) throws IOException {
1546 final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
1547 final String base = TreeWalk.pathOf(this.path, 0, pOff);
1548 final String pathAbs = concatPath(base, pathRel);
1549 return isDirectoryIgnored(pathRel, pathAbs);
1550 }
1551
1552 private boolean isDirectoryIgnored(String pathRel, String pathAbs)
1553 throws IOException {
1554 assert pathRel.length() == 0 || (pathRel.charAt(0) != '/'
1555 && pathRel.charAt(pathRel.length() - 1) != '/');
1556 assert pathAbs.length() == 0 || (pathAbs.charAt(0) != '/'
1557 && pathAbs.charAt(pathAbs.length() - 1) != '/');
1558 assert pathAbs.endsWith(pathRel);
1559
1560 Boolean ignored = state.directoryToIgnored.get(pathAbs);
1561 if (ignored != null) {
1562 return ignored.booleanValue();
1563 }
1564
1565 final String parentRel = getParentPath(pathRel);
1566 if (parentRel != null && isDirectoryIgnored(parentRel)) {
1567 state.directoryToIgnored.put(pathAbs, Boolean.TRUE);
1568 return true;
1569 }
1570
1571 final IgnoreNode node = getIgnoreNode();
1572 for (String p = pathRel; node != null
1573 && !"".equals(p); p = getParentPath(p)) {
1574 ignored = node.checkIgnored(p, true);
1575 if (ignored != null) {
1576 state.directoryToIgnored.put(pathAbs, ignored);
1577 return ignored.booleanValue();
1578 }
1579 }
1580
1581 if (!(this.parent instanceof WorkingTreeIterator)) {
1582 state.directoryToIgnored.put(pathAbs, Boolean.FALSE);
1583 return false;
1584 }
1585
1586 final WorkingTreeIterator/eclipse/jgit/treewalk/WorkingTreeIterator.html#WorkingTreeIterator">WorkingTreeIterator wtParent = (WorkingTreeIterator) this.parent;
1587 final String parentRelPath = concatPath(
1588 TreeWalk.pathOf(this.path, wtParent.pathOffset, pathOffset - 1),
1589 pathRel);
1590 assert concatPath(TreeWalk.pathOf(wtParent.path, 0,
1591 Math.max(0, wtParent.pathOffset - 1)), parentRelPath)
1592 .equals(pathAbs);
1593 return wtParent.isDirectoryIgnored(parentRelPath, pathAbs);
1594 }
1595
1596 private static String getParentPath(String path) {
1597 final int slashIndex = path.lastIndexOf('/', path.length() - 2);
1598 if (slashIndex > 0) {
1599 return path.substring(path.charAt(0) == '/' ? 1 : 0, slashIndex);
1600 }
1601 return path.length() > 0 ? "" : null;
1602 }
1603
1604 private static String concatPath(String p1, String p2) {
1605 return p1 + (p1.length() > 0 && p2.length() > 0 ? "/" : "") + p2;
1606 }
1607 }