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