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