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