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