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 = (Entry a,
778 Entry b) -> Paths.compare(a.encodedName, 0, a.encodedNameLen,
779 a.getMode().getBits(), b.encodedName, 0, b.encodedNameLen,
780 b.getMode().getBits());
781
782
783
784
785
786
787
788
789 protected void init(Entry[] list) {
790
791
792
793
794 entries = list;
795 int i, o;
796
797 final CharsetEncoder nameEncoder = state.nameEncoder;
798 for (i = 0, o = 0; i < entries.length; i++) {
799 final Entry e = entries[i];
800 if (e == null)
801 continue;
802 final String name = e.getName();
803 if (".".equals(name) || "..".equals(name))
804 continue;
805 if (Constants.DOT_GIT.equals(name))
806 continue;
807 if (Constants.DOT_GIT_IGNORE.equals(name))
808 ignoreNode = new PerDirectoryIgnoreNode(e);
809 if (Constants.DOT_GIT_ATTRIBUTES.equals(name))
810 attributesNode = new PerDirectoryAttributesNode(e);
811 if (i != o)
812 entries[o] = e;
813 e.encodeName(nameEncoder);
814 o++;
815 }
816 entryCnt = o;
817 Arrays.sort(entries, 0, entryCnt, ENTRY_CMP);
818
819 contentIdFromPtr = -1;
820 ptr = 0;
821 if (!eof())
822 parseEntry();
823 else if (pathLen == 0)
824 pathLen = pathOffset;
825 }
826
827
828
829
830
831
832 protected Entry current() {
833 return entries[ptr];
834 }
835
836
837
838
839
840 public enum MetadataDiff {
841
842
843
844
845
846 EQUAL,
847
848
849
850
851
852 DIFFER_BY_METADATA,
853
854
855 SMUDGED,
856
857
858
859
860
861 DIFFER_BY_TIMESTAMP
862 }
863
864
865
866
867
868
869
870
871 public boolean isModeDifferent(int rawMode) {
872
873
874
875 int modeDiff = getEntryRawMode() ^ rawMode;
876
877 if (modeDiff == 0)
878 return false;
879
880
881 if (getOptions().getSymLinks() == SymLinks.FALSE)
882 if (FileMode.SYMLINK.equals(rawMode))
883 return false;
884
885
886
887
888 if (!state.options.isFileMode())
889 modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
890 return modeDiff != 0;
891 }
892
893
894
895
896
897
898
899
900
901
902
903
904 public MetadataDiff compareMetadata(DirCacheEntry entry) {
905 if (entry.isAssumeValid())
906 return MetadataDiff.EQUAL;
907
908 if (entry.isUpdateNeeded())
909 return MetadataDiff.DIFFER_BY_METADATA;
910
911 if (isModeDifferent(entry.getRawMode()))
912 return MetadataDiff.DIFFER_BY_METADATA;
913
914
915 int type = mode & FileMode.TYPE_MASK;
916 if (type == FileMode.TYPE_TREE || type == FileMode.TYPE_GITLINK)
917 return MetadataDiff.EQUAL;
918
919 if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
920 return MetadataDiff.DIFFER_BY_METADATA;
921
922
923
924
925
926
927 long cacheLastModified = entry.getLastModified();
928 long fileLastModified = getEntryLastModified();
929 long lastModifiedMillis = fileLastModified % 1000;
930 long cacheMillis = cacheLastModified % 1000;
931 if (getOptions().getCheckStat() == CheckStat.MINIMAL) {
932 fileLastModified = fileLastModified - lastModifiedMillis;
933 cacheLastModified = cacheLastModified - cacheMillis;
934 } else if (cacheMillis == 0)
935 fileLastModified = fileLastModified - lastModifiedMillis;
936
937
938 else if (lastModifiedMillis == 0)
939 cacheLastModified = cacheLastModified - cacheMillis;
940
941 if (fileLastModified != cacheLastModified)
942 return MetadataDiff.DIFFER_BY_TIMESTAMP;
943 else if (!entry.isSmudged())
944
945 return MetadataDiff.EQUAL;
946 else
947 return MetadataDiff.SMUDGED;
948 }
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969 public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
970 ObjectReader reader) throws IOException {
971 if (entry == null)
972 return !FileMode.MISSING.equals(getEntryFileMode());
973 MetadataDiff diff = compareMetadata(entry);
974 switch (diff) {
975 case DIFFER_BY_TIMESTAMP:
976 if (forceContentCheck)
977
978
979 return contentCheck(entry, reader);
980 else
981
982 return true;
983 case SMUDGED:
984
985
986 return contentCheck(entry, reader);
987 case EQUAL:
988 if (mode == FileMode.SYMLINK.getBits()) {
989 return contentCheck(entry, reader);
990 }
991 return false;
992 case DIFFER_BY_METADATA:
993 if (mode == FileMode.TREE.getBits()
994 && entry.getFileMode().equals(FileMode.GITLINK)) {
995 byte[] idBuffer = idBuffer();
996 int idOffset = idOffset();
997 if (entry.getObjectId().compareTo(idBuffer, idOffset) == 0) {
998 return true;
999 } else if (ObjectId.zeroId().compareTo(idBuffer,
1000 idOffset) == 0) {
1001 return new File(repository.getWorkTree(),
1002 entry.getPathString()).list().length > 0;
1003 }
1004 return false;
1005 } else if (mode == FileMode.SYMLINK.getBits())
1006 return contentCheck(entry, reader);
1007 return true;
1008 default:
1009 throw new IllegalStateException(MessageFormat.format(
1010 JGitText.get().unexpectedCompareResult, diff.name()));
1011 }
1012 }
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025 public FileMode getIndexFileMode(DirCacheIterator indexIter) {
1026 final FileMode wtMode = getEntryFileMode();
1027 if (indexIter == null) {
1028 return wtMode;
1029 }
1030 final FileMode iMode = indexIter.getEntryFileMode();
1031 if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
1032 return wtMode;
1033 }
1034 if (!getOptions().isFileMode()) {
1035 if (FileMode.REGULAR_FILE == wtMode
1036 && FileMode.EXECUTABLE_FILE == iMode) {
1037 return iMode;
1038 }
1039 if (FileMode.EXECUTABLE_FILE == wtMode
1040 && FileMode.REGULAR_FILE == iMode) {
1041 return iMode;
1042 }
1043 }
1044 if (FileMode.GITLINK == iMode
1045 && FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
1046 return iMode;
1047 }
1048 if (FileMode.TREE == iMode
1049 && FileMode.GITLINK == wtMode) {
1050 return iMode;
1051 }
1052 return wtMode;
1053 }
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067 private boolean contentCheck(DirCacheEntry entry, ObjectReader reader)
1068 throws IOException {
1069 if (getEntryObjectId().equals(entry.getObjectId())) {
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080 entry.setLength((int) getEntryLength());
1081
1082 return false;
1083 } else {
1084 if (mode == FileMode.SYMLINK.getBits()) {
1085 return !new File(readSymlinkTarget(current())).equals(
1086 new File(readContentAsNormalizedString(entry, reader)));
1087 }
1088
1089 if (reader == null)
1090 return true;
1091
1092 switch (getEolStreamType()) {
1093 case DIRECT:
1094 return true;
1095 default:
1096 try {
1097 ObjectLoader loader = reader.open(entry.getObjectId());
1098 if (loader == null)
1099 return true;
1100
1101
1102
1103 long dcInLen;
1104 try (InputStream dcIn = new AutoLFInputStream(
1105 loader.openStream(), true,
1106 true )) {
1107 dcInLen = computeLength(dcIn);
1108 } catch (AutoLFInputStream.IsBinaryException e) {
1109 return true;
1110 }
1111
1112 try (InputStream dcIn = new AutoLFInputStream(
1113 loader.openStream(), true)) {
1114 byte[] autoCrLfHash = computeHash(dcIn, dcInLen);
1115 boolean changed = getEntryObjectId()
1116 .compareTo(autoCrLfHash, 0) != 0;
1117 return changed;
1118 }
1119 } catch (IOException e) {
1120 return true;
1121 }
1122 }
1123 }
1124 }
1125
1126 private static String readContentAsNormalizedString(DirCacheEntry entry,
1127 ObjectReader reader) throws MissingObjectException, IOException {
1128 ObjectLoader open = reader.open(entry.getObjectId());
1129 byte[] cachedBytes = open.getCachedBytes();
1130 return FS.detect().normalize(RawParseUtils.decode(cachedBytes));
1131 }
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146 protected String readSymlinkTarget(Entry entry) throws IOException {
1147 if (!entry.getMode().equals(FileMode.SYMLINK)) {
1148 throw new java.nio.file.NotLinkException(entry.getName());
1149 }
1150 long length = entry.getLength();
1151 byte[] content = new byte[(int) length];
1152 try (InputStream is = entry.openInputStream()) {
1153 int bytesRead = IO.readFully(is, content, 0);
1154 return FS.detect()
1155 .normalize(RawParseUtils.decode(content, 0, bytesRead));
1156 }
1157 }
1158
1159 private static long computeLength(InputStream in) throws IOException {
1160
1161
1162
1163 long length = 0;
1164 for (;;) {
1165 long n = in.skip(1 << 20);
1166 if (n <= 0)
1167 break;
1168 length += n;
1169 }
1170 return length;
1171 }
1172
1173 private byte[] computeHash(InputStream in, long length) throws IOException {
1174 SHA1 contentDigest = SHA1.newInstance();
1175 final byte[] contentReadBuffer = state.contentReadBuffer;
1176
1177 contentDigest.update(hblob);
1178 contentDigest.update((byte) ' ');
1179
1180 long sz = length;
1181 if (sz == 0) {
1182 contentDigest.update((byte) '0');
1183 } else {
1184 final int bufn = contentReadBuffer.length;
1185 int p = bufn;
1186 do {
1187 contentReadBuffer[--p] = digits[(int) (sz % 10)];
1188 sz /= 10;
1189 } while (sz > 0);
1190 contentDigest.update(contentReadBuffer, p, bufn - p);
1191 }
1192 contentDigest.update((byte) 0);
1193
1194 for (;;) {
1195 final int r = in.read(contentReadBuffer);
1196 if (r <= 0)
1197 break;
1198 contentDigest.update(contentReadBuffer, 0, r);
1199 sz += r;
1200 }
1201 if (sz != length)
1202 return zeroid;
1203 return contentDigest.digest();
1204 }
1205
1206
1207
1208
1209
1210
1211 public static abstract class Entry {
1212 byte[] encodedName;
1213
1214 int encodedNameLen;
1215
1216 void encodeName(CharsetEncoder enc) {
1217 final ByteBuffer b;
1218 try {
1219 b = enc.encode(CharBuffer.wrap(getName()));
1220 } catch (CharacterCodingException e) {
1221
1222 throw new RuntimeException(MessageFormat.format(
1223 JGitText.get().unencodeableFile, getName()));
1224 }
1225
1226 encodedNameLen = b.limit();
1227 if (b.hasArray() && b.arrayOffset() == 0)
1228 encodedName = b.array();
1229 else
1230 b.get(encodedName = new byte[encodedNameLen]);
1231 }
1232
1233 @Override
1234 public String toString() {
1235 return getMode().toString() + " " + getName();
1236 }
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249 public abstract FileMode getMode();
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262 public abstract long getLength();
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275 public abstract long getLastModified();
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285 public abstract String getName();
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303 public abstract InputStream openInputStream() throws IOException;
1304 }
1305
1306
1307 private static class PerDirectoryIgnoreNode extends IgnoreNode {
1308 final Entry entry;
1309
1310 PerDirectoryIgnoreNode(Entry entry) {
1311 super(Collections.<FastIgnoreRule> emptyList());
1312 this.entry = entry;
1313 }
1314
1315 IgnoreNode load() throws IOException {
1316 IgnoreNode r = new IgnoreNode();
1317 try (InputStream in = entry.openInputStream()) {
1318 r.parse(in);
1319 }
1320 return r.getRules().isEmpty() ? null : r;
1321 }
1322 }
1323
1324
1325 private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
1326 final Repository repository;
1327
1328 RootIgnoreNode(Entry entry, Repository repository) {
1329 super(entry);
1330 this.repository = repository;
1331 }
1332
1333 @Override
1334 IgnoreNode load() throws IOException {
1335 IgnoreNode r;
1336 if (entry != null) {
1337 r = super.load();
1338 if (r == null)
1339 r = new IgnoreNode();
1340 } else {
1341 r = new IgnoreNode();
1342 }
1343
1344 FS fs = repository.getFS();
1345 String path = repository.getConfig().get(CoreConfig.KEY)
1346 .getExcludesFile();
1347 if (path != null) {
1348 File excludesfile;
1349 if (path.startsWith("~/"))
1350 excludesfile = fs.resolve(fs.userHome(), path.substring(2));
1351 else
1352 excludesfile = fs.resolve(null, path);
1353 loadRulesFromFile(r, excludesfile);
1354 }
1355
1356 File exclude = fs.resolve(repository.getDirectory(),
1357 Constants.INFO_EXCLUDE);
1358 loadRulesFromFile(r, exclude);
1359
1360 return r.getRules().isEmpty() ? null : r;
1361 }
1362
1363 private static void loadRulesFromFile(IgnoreNode r, File exclude)
1364 throws FileNotFoundException, IOException {
1365 if (FS.DETECTED.exists(exclude)) {
1366 try (FileInputStream in = new FileInputStream(exclude)) {
1367 r.parse(in);
1368 }
1369 }
1370 }
1371 }
1372
1373
1374 private static class PerDirectoryAttributesNode extends AttributesNode {
1375 final Entry entry;
1376
1377 PerDirectoryAttributesNode(Entry entry) {
1378 super(Collections.<AttributesRule> emptyList());
1379 this.entry = entry;
1380 }
1381
1382 AttributesNode load() throws IOException {
1383 AttributesNode r = new AttributesNode();
1384 try (InputStream in = entry.openInputStream()) {
1385 r.parse(in);
1386 }
1387 return r.getRules().isEmpty() ? null : r;
1388 }
1389 }
1390
1391
1392 private static final class IteratorState {
1393
1394 final WorkingTreeOptions options;
1395
1396
1397 final CharsetEncoder nameEncoder;
1398
1399
1400 byte[] contentReadBuffer;
1401
1402
1403 TreeWalk walk;
1404
1405
1406 int dirCacheTree = -1;
1407
1408
1409 boolean walkIgnored = false;
1410
1411 final Map<String, Boolean> directoryToIgnored = new HashMap<>();
1412
1413 IteratorState(WorkingTreeOptions options) {
1414 this.options = options;
1415 this.nameEncoder = UTF_8.newEncoder();
1416 }
1417
1418 void initializeReadBuffer() {
1419 if (contentReadBuffer == null) {
1420 contentReadBuffer = new byte[BUFFER_SIZE];
1421 }
1422 }
1423 }
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433 public String getCleanFilterCommand() throws IOException {
1434 if (cleanFilterCommandHolder == null) {
1435 String cmd = null;
1436 if (state.walk != null) {
1437 cmd = state.walk
1438 .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
1439 }
1440 cleanFilterCommandHolder = new Holder<>(cmd);
1441 }
1442 return cleanFilterCommandHolder.get();
1443 }
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455 public EolStreamType getEolStreamType() throws IOException {
1456 return getEolStreamType(null);
1457 }
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468 private EolStreamType getEolStreamType(OperationType opType)
1469 throws IOException {
1470 if (eolStreamTypeHolder == null) {
1471 EolStreamType type = null;
1472 if (state.walk != null) {
1473 type = state.walk.getEolStreamType(opType);
1474 OperationType operationType = opType != null ? opType
1475 : state.walk.getOperationType();
1476 if (OperationType.CHECKIN_OP.equals(operationType)
1477 && EolStreamType.AUTO_LF.equals(type)
1478 && hasCrLfInIndex(getDirCacheIterator())) {
1479
1480
1481 type = EolStreamType.DIRECT;
1482 }
1483 } else {
1484 switch (getOptions().getAutoCRLF()) {
1485 case FALSE:
1486 type = EolStreamType.DIRECT;
1487 break;
1488 case TRUE:
1489 case INPUT:
1490 type = EolStreamType.AUTO_LF;
1491 break;
1492 }
1493 }
1494 eolStreamTypeHolder = new Holder<>(type);
1495 }
1496 return eolStreamTypeHolder.get();
1497 }
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509 private boolean hasCrLfInIndex(DirCacheIterator dirCache) {
1510 if (dirCache == null) {
1511 return false;
1512 }
1513
1514 DirCacheEntry entry = dirCache.getDirCacheEntry();
1515 if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) {
1516 ObjectId blobId = entry.getObjectId();
1517 if (entry.getStage() > 0
1518 && entry.getStage() != DirCacheEntry.STAGE_2) {
1519 blobId = null;
1520
1521 byte[] name = entry.getRawPath();
1522 int i = 0;
1523 while (!dirCache.eof()) {
1524 dirCache.next(1);
1525 i++;
1526 entry = dirCache.getDirCacheEntry();
1527 if (entry == null
1528 || !Arrays.equals(name, entry.getRawPath())) {
1529 break;
1530 }
1531 if (entry.getStage() == DirCacheEntry.STAGE_2) {
1532 blobId = entry.getObjectId();
1533 break;
1534 }
1535 }
1536 dirCache.back(i);
1537 }
1538 if (blobId != null) {
1539 try (ObjectReader reader = repository.newObjectReader()) {
1540 ObjectLoader loader = reader.open(blobId,
1541 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 }
1554 return false;
1555 }
1556
1557 private boolean isDirectoryIgnored(String pathRel) throws IOException {
1558 final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
1559 final String base = TreeWalk.pathOf(this.path, 0, pOff);
1560 final String pathAbs = concatPath(base, pathRel);
1561 return isDirectoryIgnored(pathRel, pathAbs);
1562 }
1563
1564 private boolean isDirectoryIgnored(String pathRel, String pathAbs)
1565 throws IOException {
1566 assert pathRel.length() == 0 || (pathRel.charAt(0) != '/'
1567 && pathRel.charAt(pathRel.length() - 1) != '/');
1568 assert pathAbs.length() == 0 || (pathAbs.charAt(0) != '/'
1569 && pathAbs.charAt(pathAbs.length() - 1) != '/');
1570 assert pathAbs.endsWith(pathRel);
1571
1572 Boolean ignored = state.directoryToIgnored.get(pathAbs);
1573 if (ignored != null) {
1574 return ignored.booleanValue();
1575 }
1576
1577 final String parentRel = getParentPath(pathRel);
1578 if (parentRel != null && isDirectoryIgnored(parentRel)) {
1579 state.directoryToIgnored.put(pathAbs, Boolean.TRUE);
1580 return true;
1581 }
1582
1583 final IgnoreNode node = getIgnoreNode();
1584 for (String p = pathRel; node != null
1585 && !"".equals(p); p = getParentPath(p)) {
1586 ignored = node.checkIgnored(p, true);
1587 if (ignored != null) {
1588 state.directoryToIgnored.put(pathAbs, ignored);
1589 return ignored.booleanValue();
1590 }
1591 }
1592
1593 if (!(this.parent instanceof WorkingTreeIterator)) {
1594 state.directoryToIgnored.put(pathAbs, Boolean.FALSE);
1595 return false;
1596 }
1597
1598 final WorkingTreeIterator/eclipse/jgit/treewalk/WorkingTreeIterator.html#WorkingTreeIterator">WorkingTreeIterator wtParent = (WorkingTreeIterator) this.parent;
1599 final String parentRelPath = concatPath(
1600 TreeWalk.pathOf(this.path, wtParent.pathOffset, pathOffset - 1),
1601 pathRel);
1602 assert concatPath(TreeWalk.pathOf(wtParent.path, 0,
1603 Math.max(0, wtParent.pathOffset - 1)), parentRelPath)
1604 .equals(pathAbs);
1605 return wtParent.isDirectoryIgnored(parentRelPath, pathAbs);
1606 }
1607
1608 private static String getParentPath(String path) {
1609 final int slashIndex = path.lastIndexOf('/', path.length() - 2);
1610 if (slashIndex > 0) {
1611 return path.substring(path.charAt(0) == '/' ? 1 : 0, slashIndex);
1612 }
1613 return path.length() > 0 ? "" : null;
1614 }
1615
1616 private static String concatPath(String p1, String p2) {
1617 return p1 + (p1.length() > 0 && p2.length() > 0 ? "/" : "") + p2;
1618 }
1619 }