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