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