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