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