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