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