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