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