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 (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
1008 return wtMode;
1009 }
1010 if (!getOptions().isFileMode()) {
1011 if (FileMode.REGULAR_FILE == wtMode
1012 && FileMode.EXECUTABLE_FILE == iMode) {
1013 return iMode;
1014 }
1015 if (FileMode.EXECUTABLE_FILE == wtMode
1016 && FileMode.REGULAR_FILE == iMode) {
1017 return iMode;
1018 }
1019 }
1020 if (FileMode.GITLINK == iMode
1021 && FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
1022 return iMode;
1023 }
1024 if (FileMode.TREE == iMode
1025 && FileMode.GITLINK == wtMode) {
1026 return iMode;
1027 }
1028 return wtMode;
1029 }
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043 private boolean contentCheck(DirCacheEntry entry, ObjectReader reader)
1044 throws IOException {
1045 if (getEntryObjectId().equals(entry.getObjectId())) {
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056 entry.setLength((int) getEntryLength());
1057
1058 return false;
1059 }
1060 if (mode == FileMode.SYMLINK.getBits()) {
1061 return !new File(readSymlinkTarget(current())).equals(
1062 new File(readContentAsNormalizedString(entry, reader)));
1063 }
1064
1065 return true;
1066 }
1067
1068 private static String readContentAsNormalizedString(DirCacheEntry entry,
1069 ObjectReader reader) throws MissingObjectException, IOException {
1070 ObjectLoader open = reader.open(entry.getObjectId());
1071 byte[] cachedBytes = open.getCachedBytes();
1072 return FS.detect().normalize(RawParseUtils.decode(cachedBytes));
1073 }
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088 protected String readSymlinkTarget(Entry entry) throws IOException {
1089 if (!entry.getMode().equals(FileMode.SYMLINK)) {
1090 throw new java.nio.file.NotLinkException(entry.getName());
1091 }
1092 long length = entry.getLength();
1093 byte[] content = new byte[(int) length];
1094 try (InputStream is = entry.openInputStream()) {
1095 int bytesRead = IO.readFully(is, content, 0);
1096 return FS.detect()
1097 .normalize(RawParseUtils.decode(content, 0, bytesRead));
1098 }
1099 }
1100
1101 private static long computeLength(InputStream in) throws IOException {
1102
1103
1104
1105 long length = 0;
1106 for (;;) {
1107 long n = in.skip(1 << 20);
1108 if (n <= 0)
1109 break;
1110 length += n;
1111 }
1112 return length;
1113 }
1114
1115 private byte[] computeHash(InputStream in, long length) throws IOException {
1116 SHA1 contentDigest = SHA1.newInstance();
1117 final byte[] contentReadBuffer = state.contentReadBuffer;
1118
1119 contentDigest.update(hblob);
1120 contentDigest.update((byte) ' ');
1121
1122 long sz = length;
1123 if (sz == 0) {
1124 contentDigest.update((byte) '0');
1125 } else {
1126 final int bufn = contentReadBuffer.length;
1127 int p = bufn;
1128 do {
1129 contentReadBuffer[--p] = digits[(int) (sz % 10)];
1130 sz /= 10;
1131 } while (sz > 0);
1132 contentDigest.update(contentReadBuffer, p, bufn - p);
1133 }
1134 contentDigest.update((byte) 0);
1135
1136 for (;;) {
1137 final int r = in.read(contentReadBuffer);
1138 if (r <= 0)
1139 break;
1140 contentDigest.update(contentReadBuffer, 0, r);
1141 sz += r;
1142 }
1143 if (sz != length)
1144 return zeroid;
1145 return contentDigest.digest();
1146 }
1147
1148
1149
1150
1151
1152
1153 public abstract static class Entry {
1154 byte[] encodedName;
1155
1156 int encodedNameLen;
1157
1158 void encodeName(CharsetEncoder enc) {
1159 final ByteBuffer b;
1160 try {
1161 b = enc.encode(CharBuffer.wrap(getName()));
1162 } catch (CharacterCodingException e) {
1163
1164 throw new RuntimeException(MessageFormat.format(
1165 JGitText.get().unencodeableFile, getName()), e);
1166 }
1167
1168 encodedNameLen = b.limit();
1169 if (b.hasArray() && b.arrayOffset() == 0)
1170 encodedName = b.array();
1171 else
1172 b.get(encodedName = new byte[encodedNameLen]);
1173 }
1174
1175 @Override
1176 public String toString() {
1177 return getMode().toString() + " " + getName();
1178 }
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191 public abstract FileMode getMode();
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204 public abstract long getLength();
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218 @Deprecated
1219 public abstract long getLastModified();
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233 public abstract Instant getLastModifiedInstant();
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243 public abstract String getName();
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261 public abstract InputStream openInputStream() throws IOException;
1262 }
1263
1264
1265 private static class PerDirectoryIgnoreNode extends IgnoreNode {
1266 protected final Entry entry;
1267
1268 private final String name;
1269
1270 PerDirectoryIgnoreNode(String name, Entry entry) {
1271 super(Collections.<FastIgnoreRule> emptyList());
1272 this.name = name;
1273 this.entry = entry;
1274 }
1275
1276 IgnoreNode load() throws IOException {
1277 IgnoreNode r = new IgnoreNode();
1278 try (InputStream in = entry.openInputStream()) {
1279 r.parse(name, in);
1280 }
1281 return r.getRules().isEmpty() ? null : r;
1282 }
1283 }
1284
1285
1286 private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
1287 final Repository repository;
1288
1289 RootIgnoreNode(Entry entry, Repository repository) {
1290 super(entry != null ? entry.getName() : null, entry);
1291 this.repository = repository;
1292 }
1293
1294 @Override
1295 IgnoreNode load() throws IOException {
1296 IgnoreNode r;
1297 if (entry != null) {
1298 r = super.load();
1299 if (r == null)
1300 r = new IgnoreNode();
1301 } else {
1302 r = new IgnoreNode();
1303 }
1304
1305 FS fs = repository.getFS();
1306 Path path = repository.getConfig().getPath(
1307 ConfigConstants.CONFIG_CORE_SECTION, null,
1308 ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null);
1309 if (path != null) {
1310 loadRulesFromFile(r, path.toFile());
1311 }
1312
1313 File exclude = fs.resolve(repository.getDirectory(),
1314 Constants.INFO_EXCLUDE);
1315 loadRulesFromFile(r, exclude);
1316
1317 return r.getRules().isEmpty() ? null : r;
1318 }
1319
1320 private static void loadRulesFromFile(IgnoreNode r, File exclude)
1321 throws FileNotFoundException, IOException {
1322 if (FS.DETECTED.exists(exclude)) {
1323 try (FileInputStream in = new FileInputStream(exclude)) {
1324 r.parse(exclude.getAbsolutePath(), in);
1325 }
1326 }
1327 }
1328 }
1329
1330
1331 private static class PerDirectoryAttributesNode extends AttributesNode {
1332 final Entry entry;
1333
1334 PerDirectoryAttributesNode(Entry entry) {
1335 super(Collections.<AttributesRule> emptyList());
1336 this.entry = entry;
1337 }
1338
1339 AttributesNode load() throws IOException {
1340 AttributesNode r = new AttributesNode();
1341 try (InputStream in = entry.openInputStream()) {
1342 r.parse(in);
1343 }
1344 return r.getRules().isEmpty() ? null : r;
1345 }
1346 }
1347
1348
1349 private static final class IteratorState {
1350
1351 final WorkingTreeOptions options;
1352
1353
1354 final CharsetEncoder nameEncoder;
1355
1356
1357 byte[] contentReadBuffer;
1358
1359
1360 TreeWalk walk;
1361
1362
1363 int dirCacheTree = -1;
1364
1365
1366 boolean walkIgnored = false;
1367
1368 final Map<String, Boolean> directoryToIgnored = new HashMap<>();
1369
1370 IteratorState(WorkingTreeOptions options) {
1371 this.options = options;
1372 this.nameEncoder = UTF_8.newEncoder();
1373 }
1374
1375 void initializeReadBuffer() {
1376 if (contentReadBuffer == null) {
1377 contentReadBuffer = new byte[BUFFER_SIZE];
1378 }
1379 }
1380 }
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390 public String getCleanFilterCommand() throws IOException {
1391 if (cleanFilterCommandHolder == null) {
1392 String cmd = null;
1393 if (state.walk != null) {
1394 cmd = state.walk
1395 .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
1396 }
1397 cleanFilterCommandHolder = new Holder<>(cmd);
1398 }
1399 return cleanFilterCommandHolder.get();
1400 }
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412 public EolStreamType getEolStreamType() throws IOException {
1413 return getEolStreamType(null);
1414 }
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425 private EolStreamType getEolStreamType(OperationType opType)
1426 throws IOException {
1427 if (eolStreamTypeHolder == null) {
1428 EolStreamType type = null;
1429 if (state.walk != null) {
1430 type = state.walk.getEolStreamType(opType);
1431 OperationType operationType = opType != null ? opType
1432 : state.walk.getOperationType();
1433 if (OperationType.CHECKIN_OP.equals(operationType)
1434 && EolStreamType.AUTO_LF.equals(type)
1435 && hasCrLfInIndex(getDirCacheIterator())) {
1436
1437
1438 type = EolStreamType.DIRECT;
1439 }
1440 } else {
1441 switch (getOptions().getAutoCRLF()) {
1442 case FALSE:
1443 type = EolStreamType.DIRECT;
1444 break;
1445 case TRUE:
1446 case INPUT:
1447 type = EolStreamType.AUTO_LF;
1448 break;
1449 }
1450 }
1451 eolStreamTypeHolder = new Holder<>(type);
1452 }
1453 return eolStreamTypeHolder.get();
1454 }
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466 private boolean hasCrLfInIndex(DirCacheIterator dirCache) {
1467 if (dirCache == null) {
1468 return false;
1469 }
1470
1471 DirCacheEntry entry = dirCache.getDirCacheEntry();
1472 if ((entry.getRawMode() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
1473 ObjectId blobId = entry.getObjectId();
1474 if (entry.getStage() > 0
1475 && entry.getStage() != DirCacheEntry.STAGE_2) {
1476 blobId = null;
1477
1478 byte[] name = entry.getRawPath();
1479 int i = 0;
1480 while (!dirCache.eof()) {
1481 dirCache.next(1);
1482 i++;
1483 entry = dirCache.getDirCacheEntry();
1484 if (entry == null
1485 || !Arrays.equals(name, entry.getRawPath())) {
1486 break;
1487 }
1488 if (entry.getStage() == DirCacheEntry.STAGE_2) {
1489 if ((entry.getRawMode()
1490 & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
1491 blobId = entry.getObjectId();
1492 }
1493 break;
1494 }
1495 }
1496 dirCache.back(i);
1497 }
1498 if (blobId != null) {
1499 try (ObjectReader reader = repository.newObjectReader()) {
1500 ObjectLoader loader = reader.open(blobId,
1501 Constants.OBJ_BLOB);
1502 try {
1503 byte[] raw = loader.getCachedBytes();
1504 return RawText.isCrLfText(raw, raw.length, true);
1505 } catch (LargeObjectException e) {
1506 try (InputStream in = loader.openStream()) {
1507 return RawText.isCrLfText(in);
1508 }
1509 }
1510 } catch (IOException e) {
1511
1512 }
1513 }
1514 }
1515 return false;
1516 }
1517
1518 private boolean isDirectoryIgnored(String pathRel) throws IOException {
1519 final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
1520 final String base = TreeWalk.pathOf(this.path, 0, pOff);
1521 final String pathAbs = concatPath(base, pathRel);
1522 return isDirectoryIgnored(pathRel, pathAbs);
1523 }
1524
1525 private boolean isDirectoryIgnored(String pathRel, String pathAbs)
1526 throws IOException {
1527 assert pathRel.length() == 0 || (pathRel.charAt(0) != '/'
1528 && pathRel.charAt(pathRel.length() - 1) != '/');
1529 assert pathAbs.length() == 0 || (pathAbs.charAt(0) != '/'
1530 && pathAbs.charAt(pathAbs.length() - 1) != '/');
1531 assert pathAbs.endsWith(pathRel);
1532
1533 Boolean ignored = state.directoryToIgnored.get(pathAbs);
1534 if (ignored != null) {
1535 return ignored.booleanValue();
1536 }
1537
1538 final String parentRel = getParentPath(pathRel);
1539 if (parentRel != null && isDirectoryIgnored(parentRel)) {
1540 state.directoryToIgnored.put(pathAbs, Boolean.TRUE);
1541 return true;
1542 }
1543
1544 final IgnoreNode node = getIgnoreNode();
1545 for (String p = pathRel; node != null
1546 && !"".equals(p); p = getParentPath(p)) {
1547 ignored = node.checkIgnored(p, true);
1548 if (ignored != null) {
1549 state.directoryToIgnored.put(pathAbs, ignored);
1550 return ignored.booleanValue();
1551 }
1552 }
1553
1554 if (!(this.parent instanceof WorkingTreeIterator)) {
1555 state.directoryToIgnored.put(pathAbs, Boolean.FALSE);
1556 return false;
1557 }
1558
1559 final WorkingTreeIterator wtParent = (WorkingTreeIterator) this.parent;
1560 final String parentRelPath = concatPath(
1561 TreeWalk.pathOf(this.path, wtParent.pathOffset, pathOffset - 1),
1562 pathRel);
1563 assert concatPath(TreeWalk.pathOf(wtParent.path, 0,
1564 Math.max(0, wtParent.pathOffset - 1)), parentRelPath)
1565 .equals(pathAbs);
1566 return wtParent.isDirectoryIgnored(parentRelPath, pathAbs);
1567 }
1568
1569 private static String getParentPath(String path) {
1570 final int slashIndex = path.lastIndexOf('/', path.length() - 2);
1571 if (slashIndex > 0) {
1572 return path.substring(path.charAt(0) == '/' ? 1 : 0, slashIndex);
1573 }
1574 return path.length() > 0 ? "" : null;
1575 }
1576
1577 private static String concatPath(String p1, String p2) {
1578 return p1 + (p1.length() > 0 && p2.length() > 0 ? "/" : "") + p2;
1579 }
1580 }