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