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