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