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,
391 len);
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)
404 throws IOException {
405 if (getCleanFilterCommand() == null
406 && getEolStreamType(
407 OperationType.CHECKIN_OP) == EolStreamType.DIRECT) {
408 canonLen = len;
409 return is;
410 }
411
412 if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
413 ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
414 rawbuf = filterClean(rawbuf.array(), rawbuf.limit());
415 canonLen = rawbuf.limit();
416 return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen);
417 }
418
419 if (getCleanFilterCommand() == null && isBinary(e)) {
420 canonLen = len;
421 return is;
422 }
423
424 final InputStream lenIs = filterClean(e.openInputStream());
425 try {
426 canonLen = computeLength(lenIs);
427 } finally {
428 safeClose(lenIs);
429 }
430 return filterClean(is);
431 }
432
433 private static void safeClose(InputStream in) {
434 try {
435 in.close();
436 } catch (IOException err2) {
437
438
439
440 }
441 }
442
443 private static boolean isBinary(Entry entry) throws IOException {
444 InputStream in = entry.openInputStream();
445 try {
446 return RawText.isBinary(in);
447 } finally {
448 safeClose(in);
449 }
450 }
451
452 private ByteBuffer filterClean(byte[] src, int n)
453 throws IOException {
454 InputStream in = new ByteArrayInputStream(src);
455 try {
456 return IO.readWholeStream(filterClean(in), n);
457 } finally {
458 safeClose(in);
459 }
460 }
461
462 private InputStream filterClean(InputStream in)
463 throws IOException {
464 in = EolStreamTypeUtil.wrapInputStream(in,
465 getEolStreamType(OperationType.CHECKIN_OP));
466 String filterCommand = getCleanFilterCommand();
467 if (filterCommand != null) {
468 if (FilterCommandRegistry.isRegistered(filterCommand)) {
469 LocalFile buffer = new TemporaryBuffer.LocalFile(null);
470 FilterCommand command = FilterCommandRegistry
471 .createFilterCommand(filterCommand, repository, in,
472 buffer);
473 while (command.run() != -1) {
474
475 }
476 return buffer.openInputStreamWithAutoDestroy();
477 }
478 FS fs = repository.getFS();
479 ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
480 new String[0]);
481 filterProcessBuilder.directory(repository.getWorkTree());
482 filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
483 repository.getDirectory().getAbsolutePath());
484 ExecutionResult result;
485 try {
486 result = fs.execute(filterProcessBuilder, in);
487 } catch (IOException | InterruptedException e) {
488 throw new IOException(new FilterFailedException(e,
489 filterCommand, getEntryPathString()));
490 }
491 int rc = result.getRc();
492 if (rc != 0) {
493 throw new IOException(new FilterFailedException(rc,
494 filterCommand, getEntryPathString(),
495 result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
496 result.getStderr().toString(MAX_EXCEPTION_TEXT_SIZE)));
497 }
498 return result.getStdout().openInputStreamWithAutoDestroy();
499 }
500 return in;
501 }
502
503
504
505
506
507
508 public WorkingTreeOptions getOptions() {
509 return state.options;
510 }
511
512
513
514
515
516
517
518
519 public Repository getRepository() {
520 return repository;
521 }
522
523
524 @Override
525 public int idOffset() {
526 return contentIdOffset;
527 }
528
529
530 @Override
531 public void reset() {
532 if (!first()) {
533 ptr = 0;
534 if (!eof())
535 parseEntry();
536 }
537 }
538
539
540 @Override
541 public boolean first() {
542 return ptr == 0;
543 }
544
545
546 @Override
547 public boolean eof() {
548 return ptr == entryCnt;
549 }
550
551
552 @Override
553 public void next(int delta) throws CorruptObjectException {
554 ptr += delta;
555 if (!eof()) {
556 parseEntry();
557 }
558 }
559
560
561 @Override
562 public void back(int delta) throws CorruptObjectException {
563 ptr -= delta;
564 parseEntry();
565 }
566
567 private void parseEntry() {
568 final Entry e = entries[ptr];
569 mode = e.getMode().getBits();
570
571 final int nameLen = e.encodedNameLen;
572 ensurePathCapacity(pathOffset + nameLen, pathOffset);
573 System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
574 pathLen = pathOffset + nameLen;
575 canonLen = -1;
576 cleanFilterCommandHolder = null;
577 eolStreamTypeHolder = null;
578 }
579
580
581
582
583
584
585 public long getEntryLength() {
586 return current().getLength();
587 }
588
589
590
591
592
593
594
595 public long getEntryContentLength() throws IOException {
596 if (canonLen == -1) {
597 long rawLen = getEntryLength();
598 if (rawLen == 0)
599 canonLen = 0;
600 InputStream is = current().openInputStream();
601 try {
602
603 possiblyFilteredInputStream(current(), is, current()
604 .getLength());
605 } finally {
606 safeClose(is);
607 }
608 }
609 return canonLen;
610 }
611
612
613
614
615
616
617
618
619 @Deprecated
620 public long getEntryLastModified() {
621 return current().getLastModified();
622 }
623
624
625
626
627
628
629
630 public Instant getEntryLastModifiedInstant() {
631 return current().getLastModifiedInstant();
632 }
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650 public InputStream openEntryStream() throws IOException {
651 InputStream rawis = current().openInputStream();
652 if (getCleanFilterCommand() == null
653 && getEolStreamType(
654 OperationType.CHECKIN_OP) == EolStreamType.DIRECT) {
655 return rawis;
656 }
657 return filterClean(rawis);
658 }
659
660
661
662
663
664
665
666
667 public boolean isEntryIgnored() throws IOException {
668 return isEntryIgnored(pathLen);
669 }
670
671
672
673
674
675
676
677
678
679
680 protected boolean isEntryIgnored(int pLen) throws IOException {
681 return isEntryIgnored(pLen, mode);
682 }
683
684
685
686
687
688
689
690
691
692
693
694
695 private boolean isEntryIgnored(int pLen, int fileMode)
696 throws IOException {
697
698
699
700
701 final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
702 String pathRel = TreeWalk.pathOf(this.path, pOff, pLen);
703 String parentRel = getParentPath(pathRel);
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721 if (isDirectoryIgnored(parentRel)) {
722 return true;
723 }
724
725 IgnoreNode rules = getIgnoreNode();
726 final Boolean ignored = rules != null
727 ? rules.checkIgnored(pathRel, FileMode.TREE.equals(fileMode))
728 : null;
729 if (ignored != null) {
730 return ignored.booleanValue();
731 }
732 return parent instanceof WorkingTreeIterator
733 && ((WorkingTreeIterator) parent).isEntryIgnored(pLen,
734 fileMode);
735 }
736
737 private IgnoreNode getIgnoreNode() throws IOException {
738 if (ignoreNode instanceof PerDirectoryIgnoreNode)
739 ignoreNode = ((PerDirectoryIgnoreNode) ignoreNode).load();
740 return ignoreNode;
741 }
742
743
744
745
746
747
748
749
750
751 public AttributesNode getEntryAttributesNode() throws IOException {
752 if (attributesNode instanceof PerDirectoryAttributesNode)
753 attributesNode = ((PerDirectoryAttributesNode) attributesNode)
754 .load();
755 return attributesNode;
756 }
757
758 private static final Comparator<Entry> ENTRY_CMP = (Entry a,
759 Entry b) -> Paths.compare(a.encodedName, 0, a.encodedNameLen,
760 a.getMode().getBits(), b.encodedName, 0, b.encodedNameLen,
761 b.getMode().getBits());
762
763
764
765
766
767
768
769
770 protected void init(Entry[] list) {
771
772
773
774
775 entries = list;
776 int i, o;
777
778 final CharsetEncoder nameEncoder = state.nameEncoder;
779 for (i = 0, o = 0; i < entries.length; i++) {
780 final Entry e = entries[i];
781 if (e == null)
782 continue;
783 final String name = e.getName();
784 if (".".equals(name) || "..".equals(name))
785 continue;
786 if (Constants.DOT_GIT.equals(name))
787 continue;
788 if (Constants.DOT_GIT_IGNORE.equals(name))
789 ignoreNode = new PerDirectoryIgnoreNode(
790 TreeWalk.pathOf(path, 0, pathOffset)
791 + Constants.DOT_GIT_IGNORE,
792 e);
793 if (Constants.DOT_GIT_ATTRIBUTES.equals(name))
794 attributesNode = new PerDirectoryAttributesNode(e);
795 if (i != o)
796 entries[o] = e;
797 e.encodeName(nameEncoder);
798 o++;
799 }
800 entryCnt = o;
801 Arrays.sort(entries, 0, entryCnt, ENTRY_CMP);
802
803 contentIdFromPtr = -1;
804 ptr = 0;
805 if (!eof())
806 parseEntry();
807 else if (pathLen == 0)
808 pathLen = pathOffset;
809 }
810
811
812
813
814
815
816 protected Entry current() {
817 return entries[ptr];
818 }
819
820
821
822
823
824 public enum MetadataDiff {
825
826
827
828
829
830 EQUAL,
831
832
833
834
835
836 DIFFER_BY_METADATA,
837
838
839 SMUDGED,
840
841
842
843
844
845 DIFFER_BY_TIMESTAMP
846 }
847
848
849
850
851
852
853
854
855 public boolean isModeDifferent(int rawMode) {
856
857
858
859 int modeDiff = getEntryRawMode() ^ rawMode;
860
861 if (modeDiff == 0)
862 return false;
863
864
865 if (getOptions().getSymLinks() == SymLinks.FALSE)
866 if (FileMode.SYMLINK.equals(rawMode))
867 return false;
868
869
870
871
872 if (!state.options.isFileMode())
873 modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
874 return modeDiff != 0;
875 }
876
877
878
879
880
881
882
883
884
885
886
887
888 public MetadataDiff compareMetadata(DirCacheEntry entry) {
889 if (entry.isAssumeValid())
890 return MetadataDiff.EQUAL;
891
892 if (entry.isUpdateNeeded())
893 return MetadataDiff.DIFFER_BY_METADATA;
894
895 if (isModeDifferent(entry.getRawMode()))
896 return MetadataDiff.DIFFER_BY_METADATA;
897
898
899 int type = mode & FileMode.TYPE_MASK;
900 if (type == FileMode.TYPE_TREE || type == FileMode.TYPE_GITLINK)
901 return MetadataDiff.EQUAL;
902
903 if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
904 return MetadataDiff.DIFFER_BY_METADATA;
905
906
907
908
909
910
911 Instant cacheLastModified = entry.getLastModifiedInstant();
912 Instant fileLastModified = getEntryLastModifiedInstant();
913 if (timestampComparator.compare(cacheLastModified, fileLastModified,
914 getOptions().getCheckStat() == CheckStat.MINIMAL) != 0) {
915 return MetadataDiff.DIFFER_BY_TIMESTAMP;
916 }
917
918 if (entry.isSmudged()) {
919 return MetadataDiff.SMUDGED;
920 }
921
922 return MetadataDiff.EQUAL;
923 }
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944 public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
945 ObjectReader reader) throws IOException {
946 if (entry == null)
947 return !FileMode.MISSING.equals(getEntryFileMode());
948 MetadataDiff diff = compareMetadata(entry);
949 switch (diff) {
950 case DIFFER_BY_TIMESTAMP:
951 if (forceContentCheck) {
952
953
954 return contentCheck(entry, reader);
955 }
956
957 return true;
958 case SMUDGED:
959
960
961 return contentCheck(entry, reader);
962 case EQUAL:
963 if (mode == FileMode.SYMLINK.getBits()) {
964 return contentCheck(entry, reader);
965 }
966 return false;
967 case DIFFER_BY_METADATA:
968 if (mode == FileMode.TREE.getBits()
969 && entry.getFileMode().equals(FileMode.GITLINK)) {
970 byte[] idBuffer = idBuffer();
971 int idOffset = idOffset();
972 if (entry.getObjectId().compareTo(idBuffer, idOffset) == 0) {
973 return true;
974 } else if (ObjectId.zeroId().compareTo(idBuffer,
975 idOffset) == 0) {
976 Path p = repository.getWorkTree().toPath()
977 .resolve(entry.getPathString());
978 return FileUtils.hasFiles(p);
979 }
980 return false;
981 } else if (mode == FileMode.SYMLINK.getBits())
982 return contentCheck(entry, reader);
983 return true;
984 default:
985 throw new IllegalStateException(MessageFormat.format(
986 JGitText.get().unexpectedCompareResult, diff.name()));
987 }
988 }
989
990
991
992
993
994
995
996
997
998
999
1000
1001 public FileMode getIndexFileMode(DirCacheIterator indexIter) {
1002 final FileMode wtMode = getEntryFileMode();
1003 if (indexIter == null) {
1004 return wtMode;
1005 }
1006 final FileMode iMode = indexIter.getEntryFileMode();
1007 if (iMode == FileMode.SYMLINK
1008 && getOptions().getSymLinks() == SymLinks.FALSE
1009 && (wtMode == FileMode.REGULAR_FILE
1010 || wtMode == FileMode.EXECUTABLE_FILE)) {
1011 return iMode;
1012 }
1013 if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
1014 return wtMode;
1015 }
1016 if (!getOptions().isFileMode()) {
1017 if (FileMode.REGULAR_FILE == wtMode
1018 && FileMode.EXECUTABLE_FILE == iMode) {
1019 return iMode;
1020 }
1021 if (FileMode.EXECUTABLE_FILE == wtMode
1022 && FileMode.REGULAR_FILE == iMode) {
1023 return iMode;
1024 }
1025 }
1026 if (FileMode.GITLINK == iMode
1027 && FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
1028 return iMode;
1029 }
1030 if (FileMode.TREE == iMode
1031 && FileMode.GITLINK == wtMode) {
1032 return iMode;
1033 }
1034 return wtMode;
1035 }
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 private boolean contentCheck(DirCacheEntry entry, ObjectReader reader)
1050 throws IOException {
1051 if (getEntryObjectId().equals(entry.getObjectId())) {
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062 entry.setLength((int) getEntryLength());
1063
1064 return false;
1065 }
1066 if (mode == FileMode.SYMLINK.getBits()) {
1067 return !new File(readSymlinkTarget(current())).equals(
1068 new File(readContentAsNormalizedString(entry, reader)));
1069 }
1070
1071 return true;
1072 }
1073
1074 private static String readContentAsNormalizedString(DirCacheEntry entry,
1075 ObjectReader reader) throws MissingObjectException, IOException {
1076 ObjectLoader open = reader.open(entry.getObjectId());
1077 byte[] cachedBytes = open.getCachedBytes();
1078 return FS.detect().normalize(RawParseUtils.decode(cachedBytes));
1079 }
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094 protected String readSymlinkTarget(Entry entry) throws IOException {
1095 if (!entry.getMode().equals(FileMode.SYMLINK)) {
1096 throw new java.nio.file.NotLinkException(entry.getName());
1097 }
1098 long length = entry.getLength();
1099 byte[] content = new byte[(int) length];
1100 try (InputStream is = entry.openInputStream()) {
1101 int bytesRead = IO.readFully(is, content, 0);
1102 return FS.detect()
1103 .normalize(RawParseUtils.decode(content, 0, bytesRead));
1104 }
1105 }
1106
1107 private static long computeLength(InputStream in) throws IOException {
1108
1109
1110
1111 long length = 0;
1112 for (;;) {
1113 long n = in.skip(1 << 20);
1114 if (n <= 0)
1115 break;
1116 length += n;
1117 }
1118 return length;
1119 }
1120
1121 private byte[] computeHash(InputStream in, long length) throws IOException {
1122 SHA1 contentDigest = SHA1.newInstance();
1123 final byte[] contentReadBuffer = state.contentReadBuffer;
1124
1125 contentDigest.update(hblob);
1126 contentDigest.update((byte) ' ');
1127
1128 long sz = length;
1129 if (sz == 0) {
1130 contentDigest.update((byte) '0');
1131 } else {
1132 final int bufn = contentReadBuffer.length;
1133 int p = bufn;
1134 do {
1135 contentReadBuffer[--p] = digits[(int) (sz % 10)];
1136 sz /= 10;
1137 } while (sz > 0);
1138 contentDigest.update(contentReadBuffer, p, bufn - p);
1139 }
1140 contentDigest.update((byte) 0);
1141
1142 for (;;) {
1143 final int r = in.read(contentReadBuffer);
1144 if (r <= 0)
1145 break;
1146 contentDigest.update(contentReadBuffer, 0, r);
1147 sz += r;
1148 }
1149 if (sz != length)
1150 return zeroid;
1151 return contentDigest.digest();
1152 }
1153
1154
1155
1156
1157
1158
1159 public abstract static class Entry {
1160 byte[] encodedName;
1161
1162 int encodedNameLen;
1163
1164 void encodeName(CharsetEncoder enc) {
1165 final ByteBuffer b;
1166 try {
1167 b = enc.encode(CharBuffer.wrap(getName()));
1168 } catch (CharacterCodingException e) {
1169
1170 throw new RuntimeException(MessageFormat.format(
1171 JGitText.get().unencodeableFile, getName()), e);
1172 }
1173
1174 encodedNameLen = b.limit();
1175 if (b.hasArray() && b.arrayOffset() == 0)
1176 encodedName = b.array();
1177 else
1178 b.get(encodedName = new byte[encodedNameLen]);
1179 }
1180
1181 @Override
1182 public String toString() {
1183 return getMode().toString() + " " + getName();
1184 }
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197 public abstract FileMode getMode();
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210 public abstract long getLength();
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224 @Deprecated
1225 public abstract long getLastModified();
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239 public abstract Instant getLastModifiedInstant();
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249 public abstract String getName();
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267 public abstract InputStream openInputStream() throws IOException;
1268 }
1269
1270
1271 private static class PerDirectoryIgnoreNode extends IgnoreNode {
1272 protected final Entry entry;
1273
1274 private final String name;
1275
1276 PerDirectoryIgnoreNode(String name, Entry entry) {
1277 super(Collections.<FastIgnoreRule> emptyList());
1278 this.name = name;
1279 this.entry = entry;
1280 }
1281
1282 IgnoreNode load() throws IOException {
1283 return load(null);
1284 }
1285
1286 IgnoreNode load(IgnoreNode parent) throws IOException {
1287 IgnoreNodeWithParent r = new IgnoreNodeWithParent(parent);
1288 try (InputStream in = entry.openInputStream()) {
1289 r.parse(name, in);
1290 }
1291 return r.getRules().isEmpty() && parent == null ? null : r;
1292 }
1293 }
1294
1295
1296 private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
1297 final Repository repository;
1298
1299 RootIgnoreNode(Entry entry, Repository repository) {
1300 super(entry != null ? entry.getName() : null, entry);
1301 this.repository = repository;
1302 }
1303
1304 @Override
1305 IgnoreNode load(IgnoreNode parent) throws IOException {
1306 IgnoreNode coreExclude = new IgnoreNodeWithParent(parent);
1307 FS fs = repository.getFS();
1308 Path path = repository.getConfig().getPath(
1309 ConfigConstants.CONFIG_CORE_SECTION, null,
1310 ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null);
1311 if (path != null) {
1312 loadRulesFromFile(coreExclude, path.toFile());
1313 }
1314 if (coreExclude.getRules().isEmpty()) {
1315 coreExclude = parent;
1316 }
1317
1318 IgnoreNode infoExclude = new IgnoreNodeWithParent(
1319 coreExclude);
1320 File exclude = fs.resolve(repository.getDirectory(),
1321 Constants.INFO_EXCLUDE);
1322 loadRulesFromFile(infoExclude, exclude);
1323 if (infoExclude.getRules().isEmpty()) {
1324 infoExclude = null;
1325 }
1326
1327 IgnoreNode parentNode = infoExclude != null ? infoExclude
1328 : coreExclude;
1329
1330 IgnoreNode r;
1331 if (entry != null) {
1332 r = super.load(parentNode);
1333 if (r == null) {
1334 return null;
1335 }
1336 } else {
1337 return parentNode;
1338 }
1339 return r.getRules().isEmpty() ? parentNode : r;
1340 }
1341
1342 private static void loadRulesFromFile(IgnoreNode r, File exclude)
1343 throws FileNotFoundException, IOException {
1344 if (FS.DETECTED.exists(exclude)) {
1345 try (FileInputStream in = new FileInputStream(exclude)) {
1346 r.parse(exclude.getAbsolutePath(), in);
1347 }
1348 }
1349 }
1350 }
1351
1352 private static class IgnoreNodeWithParent extends IgnoreNode {
1353
1354 private final IgnoreNode parent;
1355
1356 IgnoreNodeWithParent(IgnoreNode parent) {
1357 this.parent = parent;
1358 }
1359
1360 @Override
1361 public Boolean checkIgnored(String path, boolean isDirectory) {
1362 Boolean result = super.checkIgnored(path, isDirectory);
1363 if (result == null && parent != null) {
1364 return parent.checkIgnored(path, isDirectory);
1365 }
1366 return result;
1367 }
1368 }
1369
1370
1371 private static class PerDirectoryAttributesNode extends AttributesNode {
1372 final Entry entry;
1373
1374 PerDirectoryAttributesNode(Entry entry) {
1375 super(Collections.<AttributesRule> emptyList());
1376 this.entry = entry;
1377 }
1378
1379 AttributesNode load() throws IOException {
1380 AttributesNode r = new AttributesNode();
1381 try (InputStream in = entry.openInputStream()) {
1382 r.parse(in);
1383 }
1384 return r.getRules().isEmpty() ? null : r;
1385 }
1386 }
1387
1388
1389 private static final class IteratorState {
1390
1391 final WorkingTreeOptions options;
1392
1393
1394 final CharsetEncoder nameEncoder;
1395
1396
1397 byte[] contentReadBuffer;
1398
1399
1400 TreeWalk walk;
1401
1402
1403 int dirCacheTree = -1;
1404
1405
1406 boolean walkIgnored = false;
1407
1408 final Map<String, Boolean> directoryToIgnored = new HashMap<>();
1409
1410 IteratorState(WorkingTreeOptions options) {
1411 this.options = options;
1412 this.nameEncoder = UTF_8.newEncoder();
1413 }
1414
1415 void initializeReadBuffer() {
1416 if (contentReadBuffer == null) {
1417 contentReadBuffer = new byte[BUFFER_SIZE];
1418 }
1419 }
1420 }
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430 public String getCleanFilterCommand() throws IOException {
1431 if (cleanFilterCommandHolder == null) {
1432 String cmd = null;
1433 if (state.walk != null) {
1434 cmd = state.walk
1435 .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
1436 }
1437 cleanFilterCommandHolder = new Holder<>(cmd);
1438 }
1439 return cleanFilterCommandHolder.get();
1440 }
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452 public EolStreamType getEolStreamType() throws IOException {
1453 return getEolStreamType(null);
1454 }
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465 private EolStreamType getEolStreamType(OperationType opType)
1466 throws IOException {
1467 if (eolStreamTypeHolder == null) {
1468 EolStreamType type = null;
1469 if (state.walk != null) {
1470 type = state.walk.getEolStreamType(opType);
1471 OperationType operationType = opType != null ? opType
1472 : state.walk.getOperationType();
1473 if (OperationType.CHECKIN_OP.equals(operationType)
1474 && EolStreamType.AUTO_LF.equals(type)
1475 && hasCrLfInIndex(getDirCacheIterator())) {
1476
1477
1478 type = EolStreamType.DIRECT;
1479 }
1480 } else {
1481 switch (getOptions().getAutoCRLF()) {
1482 case FALSE:
1483 type = EolStreamType.DIRECT;
1484 break;
1485 case TRUE:
1486 case INPUT:
1487 type = EolStreamType.AUTO_LF;
1488 break;
1489 }
1490 }
1491 eolStreamTypeHolder = new Holder<>(type);
1492 }
1493 return eolStreamTypeHolder.get();
1494 }
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506 private boolean hasCrLfInIndex(DirCacheIterator dirCache) {
1507 if (dirCache == null) {
1508 return false;
1509 }
1510
1511 DirCacheEntry entry = dirCache.getDirCacheEntry();
1512 if ((entry.getRawMode() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
1513 ObjectId blobId = entry.getObjectId();
1514 if (entry.getStage() > 0
1515 && entry.getStage() != DirCacheEntry.STAGE_2) {
1516 blobId = null;
1517
1518 byte[] name = entry.getRawPath();
1519 int i = 0;
1520 while (!dirCache.eof()) {
1521 dirCache.next(1);
1522 i++;
1523 entry = dirCache.getDirCacheEntry();
1524 if (entry == null
1525 || !Arrays.equals(name, entry.getRawPath())) {
1526 break;
1527 }
1528 if (entry.getStage() == DirCacheEntry.STAGE_2) {
1529 if ((entry.getRawMode()
1530 & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
1531 blobId = entry.getObjectId();
1532 }
1533 break;
1534 }
1535 }
1536 dirCache.back(i);
1537 }
1538 if (blobId != null) {
1539 try (ObjectReader reader = repository.newObjectReader()) {
1540 ObjectLoader loader = reader.open(blobId,
1541 Constants.OBJ_BLOB);
1542 try {
1543 byte[] raw = loader.getCachedBytes();
1544 return RawText.isCrLfText(raw, raw.length, true);
1545 } catch (LargeObjectException e) {
1546 try (InputStream in = loader.openStream()) {
1547 return RawText.isCrLfText(in);
1548 }
1549 }
1550 } catch (IOException e) {
1551
1552 }
1553 }
1554 }
1555 return false;
1556 }
1557
1558 private boolean isDirectoryIgnored(String pathRel) throws IOException {
1559 final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
1560 final String base = TreeWalk.pathOf(this.path, 0, pOff);
1561 final String pathAbs = concatPath(base, pathRel);
1562 return isDirectoryIgnored(pathRel, pathAbs);
1563 }
1564
1565 private boolean isDirectoryIgnored(String pathRel, String pathAbs)
1566 throws IOException {
1567 assert pathRel.length() == 0 || (pathRel.charAt(0) != '/'
1568 && pathRel.charAt(pathRel.length() - 1) != '/');
1569 assert pathAbs.length() == 0 || (pathAbs.charAt(0) != '/'
1570 && pathAbs.charAt(pathAbs.length() - 1) != '/');
1571 assert pathAbs.endsWith(pathRel);
1572
1573 Boolean ignored = state.directoryToIgnored.get(pathAbs);
1574 if (ignored != null) {
1575 return ignored.booleanValue();
1576 }
1577
1578 final String parentRel = getParentPath(pathRel);
1579 if (parentRel != null && isDirectoryIgnored(parentRel)) {
1580 state.directoryToIgnored.put(pathAbs, Boolean.TRUE);
1581 return true;
1582 }
1583
1584 final IgnoreNode node = getIgnoreNode();
1585 for (String p = pathRel; node != null
1586 && !"".equals(p); p = getParentPath(p)) {
1587 ignored = node.checkIgnored(p, true);
1588 if (ignored != null) {
1589 state.directoryToIgnored.put(pathAbs, ignored);
1590 return ignored.booleanValue();
1591 }
1592 }
1593
1594 if (!(this.parent instanceof WorkingTreeIterator)) {
1595 state.directoryToIgnored.put(pathAbs, Boolean.FALSE);
1596 return false;
1597 }
1598
1599 final WorkingTreeIterator wtParent = (WorkingTreeIterator) this.parent;
1600 final String parentRelPath = concatPath(
1601 TreeWalk.pathOf(this.path, wtParent.pathOffset, pathOffset - 1),
1602 pathRel);
1603 assert concatPath(TreeWalk.pathOf(wtParent.path, 0,
1604 Math.max(0, wtParent.pathOffset - 1)), parentRelPath)
1605 .equals(pathAbs);
1606 return wtParent.isDirectoryIgnored(parentRelPath, pathAbs);
1607 }
1608
1609 private static String getParentPath(String path) {
1610 final int slashIndex = path.lastIndexOf('/', path.length() - 2);
1611 if (slashIndex > 0) {
1612 return path.substring(path.charAt(0) == '/' ? 1 : 0, slashIndex);
1613 }
1614 return path.length() > 0 ? "" : null;
1615 }
1616
1617 private static String concatPath(String p1, String p2) {
1618 return p1 + (p1.length() > 0 && p2.length() > 0 ? "/" : "") + p2;
1619 }
1620 }