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