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 package org.eclipse.jgit.dircache;
44
45 import java.io.File;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.io.OutputStream;
49 import java.nio.file.StandardCopyOption;
50 import java.text.MessageFormat;
51 import java.util.ArrayList;
52 import java.util.HashMap;
53 import java.util.Iterator;
54 import java.util.List;
55 import java.util.Map;
56
57 import org.eclipse.jgit.api.errors.FilterFailedException;
58 import org.eclipse.jgit.attributes.FilterCommand;
59 import org.eclipse.jgit.attributes.FilterCommandRegistry;
60 import org.eclipse.jgit.errors.CheckoutConflictException;
61 import org.eclipse.jgit.errors.CorruptObjectException;
62 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
63 import org.eclipse.jgit.errors.IndexWriteException;
64 import org.eclipse.jgit.errors.MissingObjectException;
65 import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
66 import org.eclipse.jgit.internal.JGitText;
67 import org.eclipse.jgit.lib.Constants;
68 import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
69 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
70 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
71 import org.eclipse.jgit.lib.FileMode;
72 import org.eclipse.jgit.lib.NullProgressMonitor;
73 import org.eclipse.jgit.lib.ObjectChecker;
74 import org.eclipse.jgit.lib.ObjectId;
75 import org.eclipse.jgit.lib.ObjectLoader;
76 import org.eclipse.jgit.lib.ObjectReader;
77 import org.eclipse.jgit.lib.Repository;
78 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
79 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
80 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
81 import org.eclipse.jgit.treewalk.FileTreeIterator;
82 import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
83 import org.eclipse.jgit.treewalk.TreeWalk;
84 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
85 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
86 import org.eclipse.jgit.treewalk.filter.PathFilter;
87 import org.eclipse.jgit.util.FS;
88 import org.eclipse.jgit.util.FS.ExecutionResult;
89 import org.eclipse.jgit.util.FileUtils;
90 import org.eclipse.jgit.util.IntList;
91 import org.eclipse.jgit.util.RawParseUtils;
92 import org.eclipse.jgit.util.SystemReader;
93 import org.eclipse.jgit.util.io.EolStreamTypeUtil;
94 import org.slf4j.Logger;
95 import org.slf4j.LoggerFactory;
96
97
98
99
100 public class DirCacheCheckout {
101 private static Logger LOG = LoggerFactory.getLogger(DirCacheCheckout.class);
102
103 private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
104
105
106
107
108
109
110 public static class CheckoutMetadata {
111
112 public final EolStreamType eolStreamType;
113
114
115 public final String smudgeFilterCommand;
116
117
118
119
120
121 public CheckoutMetadata(EolStreamType eolStreamType,
122 String smudgeFilterCommand) {
123 this.eolStreamType = eolStreamType;
124 this.smudgeFilterCommand = smudgeFilterCommand;
125 }
126
127 static CheckoutMetadata EMPTY = new CheckoutMetadata(
128 EolStreamType.DIRECT, null);
129 }
130
131 private Repository repo;
132
133 private HashMap<String, CheckoutMetadata> updated = new HashMap<>();
134
135 private ArrayList<String> conflicts = new ArrayList<>();
136
137 private ArrayList<String> removed = new ArrayList<>();
138
139 private ObjectId mergeCommitTree;
140
141 private DirCache dc;
142
143 private DirCacheBuilder builder;
144
145 private NameConflictTreeWalk walk;
146
147 private ObjectId headCommitTree;
148
149 private WorkingTreeIterator workingTree;
150
151 private boolean failOnConflict = true;
152
153 private ArrayList<String> toBeDeleted = new ArrayList<>();
154
155 private boolean emptyDirCache;
156
157 private boolean performingCheckout;
158
159
160
161
162 public Map<String, CheckoutMetadata> getUpdated() {
163 return updated;
164 }
165
166
167
168
169 public List<String> getConflicts() {
170 return conflicts;
171 }
172
173
174
175
176
177
178
179
180
181
182 public List<String> getToBeDeleted() {
183 return toBeDeleted;
184 }
185
186
187
188
189 public List<String> getRemoved() {
190 return removed;
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc,
210 ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
211 throws IOException {
212 this.repo = repo;
213 this.dc = dc;
214 this.headCommitTree = headCommitTree;
215 this.mergeCommitTree = mergeCommitTree;
216 this.workingTree = workingTree;
217 this.emptyDirCache = (dc == null) || (dc.getEntryCount() == 0);
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 public DirCacheCheckout(Repository repo, ObjectId headCommitTree,
236 DirCache dc, ObjectId mergeCommitTree) throws IOException {
237 this(repo, headCommitTree, dc, mergeCommitTree, new FileTreeIterator(repo));
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 public DirCacheCheckout(Repository repo, DirCache dc,
255 ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
256 throws IOException {
257 this(repo, null, dc, mergeCommitTree, workingTree);
258 }
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273 public DirCacheCheckout(Repository repo, DirCache dc,
274 ObjectId mergeCommitTree) throws IOException {
275 this(repo, null, dc, mergeCommitTree, new FileTreeIterator(repo));
276 }
277
278
279
280
281
282
283
284
285 public void preScanTwoTrees() throws CorruptObjectException, IOException {
286 removed.clear();
287 updated.clear();
288 conflicts.clear();
289 walk = new NameConflictTreeWalk(repo);
290 builder = dc.builder();
291
292 addTree(walk, headCommitTree);
293 addTree(walk, mergeCommitTree);
294 int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
295 walk.addTree(workingTree);
296 workingTree.setDirCacheIterator(walk, dciPos);
297
298 while (walk.next()) {
299 processEntry(walk.getTree(0, CanonicalTreeParser.class),
300 walk.getTree(1, CanonicalTreeParser.class),
301 walk.getTree(2, DirCacheBuildIterator.class),
302 walk.getTree(3, WorkingTreeIterator.class));
303 if (walk.isSubtree())
304 walk.enterSubtree();
305 }
306 }
307
308 private void addTree(TreeWalk tw, ObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException {
309 if (id == null)
310 tw.addTree(new EmptyTreeIterator());
311 else
312 tw.addTree(id);
313 }
314
315
316
317
318
319
320
321
322
323
324 public void prescanOneTree()
325 throws MissingObjectException, IncorrectObjectTypeException,
326 CorruptObjectException, IOException {
327 removed.clear();
328 updated.clear();
329 conflicts.clear();
330
331 builder = dc.builder();
332
333 walk = new NameConflictTreeWalk(repo);
334 addTree(walk, mergeCommitTree);
335 int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
336 walk.addTree(workingTree);
337 workingTree.setDirCacheIterator(walk, dciPos);
338
339 while (walk.next()) {
340 processEntry(walk.getTree(0, CanonicalTreeParser.class),
341 walk.getTree(1, DirCacheBuildIterator.class),
342 walk.getTree(2, WorkingTreeIterator.class));
343 if (walk.isSubtree())
344 walk.enterSubtree();
345 }
346 conflicts.removeAll(removed);
347 }
348
349
350
351
352
353
354
355
356
357
358 void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
359 WorkingTreeIterator f) throws IOException {
360 if (m != null) {
361 checkValidPath(m);
362
363
364 if (i == null) {
365
366 if (f != null && !FileMode.TREE.equals(f.getEntryFileMode())
367 && !f.isEntryIgnored()) {
368 if (failOnConflict) {
369
370 conflicts.add(walk.getPathString());
371 } else {
372
373
374
375 update(m.getEntryPathString(), m.getEntryObjectId(),
376 m.getEntryFileMode());
377 }
378 } else
379 update(m.getEntryPathString(), m.getEntryObjectId(),
380 m.getEntryFileMode());
381 } else if (f == null || !m.idEqual(i)) {
382
383
384 update(m.getEntryPathString(), m.getEntryObjectId(),
385 m.getEntryFileMode());
386 } else if (i.getDirCacheEntry() != null) {
387
388 if (f.isModified(i.getDirCacheEntry(), true,
389 this.walk.getObjectReader())
390 || i.getDirCacheEntry().getStage() != 0)
391
392
393 update(m.getEntryPathString(), m.getEntryObjectId(),
394 m.getEntryFileMode());
395 else {
396
397
398 DirCacheEntry entry = i.getDirCacheEntry();
399 if (entry.getLastModified() == 0)
400 entry.setLastModified(f.getEntryLastModified());
401 keep(entry);
402 }
403 } else
404
405 keep(i.getDirCacheEntry());
406 } else {
407
408
409 if (f != null) {
410
411 if (walk.isDirectoryFileConflict()) {
412
413
414
415 conflicts.add(walk.getPathString());
416 } else {
417
418
419 if (i != null) {
420
421
422
423 remove(i.getEntryPathString());
424 conflicts.remove(i.getEntryPathString());
425 } else {
426
427
428 }
429 }
430 } else {
431
432
433
434
435 }
436 }
437 }
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453 public boolean checkout() throws IOException {
454 try {
455 return doCheckout();
456 } finally {
457 try {
458 dc.unlock();
459 } finally {
460 if (performingCheckout) {
461 WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
462 getUpdated().keySet(), getRemoved());
463 if (!event.isEmpty()) {
464 repo.fireEvent(event);
465 }
466 }
467 }
468 }
469 }
470
471 private boolean doCheckout() throws CorruptObjectException, IOException,
472 MissingObjectException, IncorrectObjectTypeException,
473 CheckoutConflictException, IndexWriteException {
474 toBeDeleted.clear();
475 try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
476 if (headCommitTree != null)
477 preScanTwoTrees();
478 else
479 prescanOneTree();
480
481 if (!conflicts.isEmpty()) {
482 if (failOnConflict)
483 throw new CheckoutConflictException(conflicts.toArray(new String[conflicts.size()]));
484 else
485 cleanUpConflicts();
486 }
487
488
489 builder.finish();
490
491 performingCheckout = true;
492 File file = null;
493 String last = null;
494
495
496
497 IntList nonDeleted = new IntList();
498 for (int i = removed.size() - 1; i >= 0; i--) {
499 String r = removed.get(i);
500 file = new File(repo.getWorkTree(), r);
501 if (!file.delete() && repo.getFS().exists(file)) {
502
503
504
505
506
507 if (!repo.getFS().isDirectory(file)) {
508 nonDeleted.add(i);
509 toBeDeleted.add(r);
510 }
511 } else {
512 if (last != null && !isSamePrefix(r, last))
513 removeEmptyParents(new File(repo.getWorkTree(), last));
514 last = r;
515 }
516 }
517 if (file != null) {
518 removeEmptyParents(file);
519 }
520 removed = filterOut(removed, nonDeleted);
521 nonDeleted = null;
522 Iterator<Map.Entry<String, CheckoutMetadata>> toUpdate = updated
523 .entrySet().iterator();
524 Map.Entry<String, CheckoutMetadata> e = null;
525 try {
526 while (toUpdate.hasNext()) {
527 e = toUpdate.next();
528 String path = e.getKey();
529 CheckoutMetadata meta = e.getValue();
530 DirCacheEntry entry = dc.getEntry(path);
531 if (!FileMode.GITLINK.equals(entry.getRawMode())) {
532 checkoutEntry(repo, entry, objectReader, false, meta);
533 }
534 e = null;
535 }
536 } catch (Exception ex) {
537
538
539 if (e != null) {
540 toUpdate.remove();
541 }
542 while (toUpdate.hasNext()) {
543 e = toUpdate.next();
544 toUpdate.remove();
545 }
546 throw ex;
547 }
548
549 if (!builder.commit())
550 throw new IndexWriteException();
551 }
552 return toBeDeleted.size() == 0;
553 }
554
555 private static ArrayList<String> filterOut(ArrayList<String> strings,
556 IntList indicesToRemove) {
557 int n = indicesToRemove.size();
558 if (n == strings.size()) {
559 return new ArrayList<>(0);
560 }
561 switch (n) {
562 case 0:
563 return strings;
564 case 1:
565 strings.remove(indicesToRemove.get(0));
566 return strings;
567 default:
568 int length = strings.size();
569 ArrayList<String> result = new ArrayList<>(length - n);
570
571
572 int j = n - 1;
573 int idx = indicesToRemove.get(j);
574 for (int i = 0; i < length; i++) {
575 if (i == idx) {
576 idx = (--j >= 0) ? indicesToRemove.get(j) : -1;
577 } else {
578 result.add(strings.get(i));
579 }
580 }
581 return result;
582 }
583 }
584
585 private static boolean isSamePrefix(String a, String b) {
586 int as = a.lastIndexOf('/');
587 int bs = b.lastIndexOf('/');
588 return a.substring(0, as + 1).equals(b.substring(0, bs + 1));
589 }
590
591 private void removeEmptyParents(File f) {
592 File parentFile = f.getParentFile();
593
594 while (parentFile != null && !parentFile.equals(repo.getWorkTree())) {
595 if (!parentFile.delete())
596 break;
597 parentFile = parentFile.getParentFile();
598 }
599 }
600
601
602
603
604
605
606
607
608
609
610
611 private boolean equalIdAndMode(ObjectId id1, FileMode mode1, ObjectId id2,
612 FileMode mode2) {
613 if (!mode1.equals(mode2))
614 return false;
615 return id1 != null ? id1.equals(id2) : id2 == null;
616 }
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635 void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
636 DirCacheBuildIterator i, WorkingTreeIterator f) throws IOException {
637 DirCacheEntry dce = i != null ? i.getDirCacheEntry() : null;
638
639 String name = walk.getPathString();
640
641 if (m != null)
642 checkValidPath(m);
643
644 if (i == null && m == null && h == null) {
645
646 if (walk.isDirectoryFileConflict())
647
648 conflict(name, null, null, null);
649
650
651 return;
652 }
653
654 ObjectId iId = (i == null ? null : i.getEntryObjectId());
655 ObjectId mId = (m == null ? null : m.getEntryObjectId());
656 ObjectId hId = (h == null ? null : h.getEntryObjectId());
657 FileMode iMode = (i == null ? null : i.getEntryFileMode());
658 FileMode mMode = (m == null ? null : m.getEntryFileMode());
659 FileMode hMode = (h == null ? null : h.getEntryFileMode());
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708 int ffMask = 0;
709 if (h != null)
710 ffMask = FileMode.TREE.equals(hMode) ? 0xD00 : 0xF00;
711 if (i != null)
712 ffMask |= FileMode.TREE.equals(iMode) ? 0x0D0 : 0x0F0;
713 if (m != null)
714 ffMask |= FileMode.TREE.equals(mMode) ? 0x00D : 0x00F;
715
716
717
718 if (((ffMask & 0x222) != 0x000)
719 && (((ffMask & 0x00F) == 0x00D) || ((ffMask & 0x0F0) == 0x0D0) || ((ffMask & 0xF00) == 0xD00))) {
720
721
722
723
724
725 switch (ffMask) {
726 case 0xDDF:
727 if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
728 conflict(name, dce, h, m);
729 } else {
730 update(name, mId, mMode);
731 }
732
733 break;
734 case 0xDFD:
735 keep(dce);
736 break;
737 case 0xF0D:
738 remove(name);
739 break;
740 case 0xDFF:
741 if (equalIdAndMode(iId, iMode, mId, mMode))
742 keep(dce);
743 else
744 conflict(name, dce, h, m);
745 break;
746 case 0xFDD:
747
748
749
750
751
752
753
754 break;
755 case 0xD0F:
756 update(name, mId, mMode);
757 break;
758 case 0xDF0:
759 case 0x0FD:
760 conflict(name, dce, h, m);
761 break;
762 case 0xFDF:
763 if (equalIdAndMode(hId, hMode, mId, mMode)) {
764 if (isModifiedSubtree_IndexWorkingtree(name))
765 conflict(name, dce, h, m);
766 else
767 update(name, mId, mMode);
768 } else
769 conflict(name, dce, h, m);
770 break;
771 case 0xFD0:
772 keep(dce);
773 break;
774 case 0xFFD:
775 if (equalIdAndMode(hId, hMode, iId, iMode))
776 if (f != null
777 && f.isModified(dce, true,
778 this.walk.getObjectReader()))
779 conflict(name, dce, h, m);
780 else
781 remove(name);
782 else
783 conflict(name, dce, h, m);
784 break;
785 case 0x0DF:
786 if (!isModifiedSubtree_IndexWorkingtree(name))
787 update(name, mId, mMode);
788 else
789 conflict(name, dce, h, m);
790 break;
791 default:
792 keep(dce);
793 }
794 return;
795 }
796
797 if ((ffMask & 0x222) == 0) {
798
799
800 if (f == null || FileMode.TREE.equals(f.getEntryFileMode())) {
801
802
803 return;
804 } else {
805
806 if (!idEqual(h, m)) {
807
808
809 conflict(name, null, null, null);
810 }
811 return;
812 }
813 }
814
815 if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) {
816
817 conflict(name, null, h, m);
818 return;
819 }
820
821 if (i == null) {
822
823
824
825 if (f != null && !f.isEntryIgnored()) {
826
827 if (!FileMode.GITLINK.equals(mMode)) {
828
829
830 if (mId == null
831 || !equalIdAndMode(mId, mMode,
832 f.getEntryObjectId(), f.getEntryFileMode())) {
833 conflict(name, null, h, m);
834 return;
835 }
836 }
837 }
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852 if (h == null)
853
854
855
856
857
858 update(name, mId, mMode);
859 else if (m == null)
860
861
862
863
864
865 remove(name);
866 else {
867
868
869
870
871
872
873
874 if (equalIdAndMode(hId, hMode, mId, mMode)) {
875 if (emptyDirCache)
876 update(name, mId, mMode);
877 else
878 keep(dce);
879 } else
880 conflict(name, dce, h, m);
881 }
882 } else {
883
884 if (h == null) {
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901 if (m == null
902 || !isModified_IndexTree(name, iId, iMode, mId, mMode,
903 mergeCommitTree)) {
904
905
906
907 if (m==null && walk.isDirectoryFileConflict()) {
908
909
910
911
912 if (dce != null
913 && (f == null || f.isModified(dce, true,
914 this.walk.getObjectReader())))
915
916
917
918
919
920
921
922
923 conflict(name, dce, h, m);
924 else
925
926
927
928
929
930
931
932
933 remove(name);
934 } else
935
936
937
938
939
940
941 keep(dce);
942 } else
943
944
945
946
947
948 conflict(name, dce, h, m);
949 } else if (m == null) {
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965 if (iMode == FileMode.GITLINK) {
966
967
968
969
970
971 remove(name);
972 } else {
973
974
975
976 if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
977 headCommitTree)) {
978
979
980
981
982 if (f != null
983 && f.isModified(dce, true,
984 this.walk.getObjectReader())) {
985
986
987
988
989
990
991 if (!FileMode.TREE.equals(f.getEntryFileMode())
992 && FileMode.TREE.equals(iMode))
993
994
995 return;
996 else
997
998
999 conflict(name, dce, h, m);
1000 } else
1001
1002
1003
1004
1005
1006
1007 remove(name);
1008 } else
1009
1010
1011
1012
1013
1014
1015
1016 conflict(name, dce, h, m);
1017 }
1018 } else {
1019
1020
1021
1022 if (!equalIdAndMode(hId, hMode, mId, mMode)
1023 && isModified_IndexTree(name, iId, iMode, hId, hMode,
1024 headCommitTree)
1025 && isModified_IndexTree(name, iId, iMode, mId, mMode,
1026 mergeCommitTree))
1027
1028
1029
1030 conflict(name, dce, h, m);
1031 else
1032
1033
1034
1035
1036
1037
1038 if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
1039 headCommitTree)
1040 && isModified_IndexTree(name, iId, iMode, mId, mMode,
1041 mergeCommitTree)) {
1042
1043
1044
1045
1046 if (dce != null
1047 && FileMode.GITLINK.equals(dce.getFileMode())) {
1048
1049
1050
1051
1052
1053
1054
1055
1056 update(name, mId, mMode);
1057 } else if (dce != null
1058 && (f != null && f.isModified(dce, true,
1059 this.walk.getObjectReader()))) {
1060
1061
1062
1063
1064
1065
1066 conflict(name, dce, h, m);
1067 } else {
1068
1069
1070
1071
1072
1073
1074
1075 update(name, mId, mMode);
1076 }
1077 } else {
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090 keep(dce);
1091 }
1092 }
1093 }
1094 }
1095
1096 private static boolean idEqual(AbstractTreeIterator a,
1097 AbstractTreeIterator b) {
1098 if (a == b) {
1099 return true;
1100 }
1101 if (a == null || b == null) {
1102 return false;
1103 }
1104 return a.getEntryObjectId().equals(b.getEntryObjectId());
1105 }
1106
1107
1108
1109
1110
1111
1112
1113
1114 private void conflict(String path, DirCacheEntry e, AbstractTreeIterator h, AbstractTreeIterator m) {
1115 conflicts.add(path);
1116
1117 DirCacheEntry entry;
1118 if (e != null) {
1119 entry = new DirCacheEntry(e.getPathString(), DirCacheEntry.STAGE_1);
1120 entry.copyMetaData(e, true);
1121 builder.add(entry);
1122 }
1123
1124 if (h != null && !FileMode.TREE.equals(h.getEntryFileMode())) {
1125 entry = new DirCacheEntry(h.getEntryPathString(), DirCacheEntry.STAGE_2);
1126 entry.setFileMode(h.getEntryFileMode());
1127 entry.setObjectId(h.getEntryObjectId());
1128 builder.add(entry);
1129 }
1130
1131 if (m != null && !FileMode.TREE.equals(m.getEntryFileMode())) {
1132 entry = new DirCacheEntry(m.getEntryPathString(), DirCacheEntry.STAGE_3);
1133 entry.setFileMode(m.getEntryFileMode());
1134 entry.setObjectId(m.getEntryObjectId());
1135 builder.add(entry);
1136 }
1137 }
1138
1139 private void keep(DirCacheEntry e) {
1140 if (e != null && !FileMode.TREE.equals(e.getFileMode()))
1141 builder.add(e);
1142 }
1143
1144 private void remove(String path) {
1145 removed.add(path);
1146 }
1147
1148 private void update(String path, ObjectId mId, FileMode mode)
1149 throws IOException {
1150 if (!FileMode.TREE.equals(mode)) {
1151 updated.put(path, new CheckoutMetadata(walk.getEolStreamType(),
1152 walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
1153
1154 DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
1155 entry.setObjectId(mId);
1156 entry.setFileMode(mode);
1157 builder.add(entry);
1158 }
1159 }
1160
1161
1162
1163
1164
1165
1166
1167
1168 public void setFailOnConflict(boolean failOnConflict) {
1169 this.failOnConflict = failOnConflict;
1170 }
1171
1172
1173
1174
1175
1176
1177
1178 private void cleanUpConflicts() throws CheckoutConflictException {
1179
1180 for (String c : conflicts) {
1181 File conflict = new File(repo.getWorkTree(), c);
1182 if (!conflict.delete())
1183 throw new CheckoutConflictException(MessageFormat.format(
1184 JGitText.get().cannotDeleteFile, c));
1185 removeEmptyParents(conflict);
1186 }
1187 for (String r : removed) {
1188 File file = new File(repo.getWorkTree(), r);
1189 if (!file.delete())
1190 throw new CheckoutConflictException(
1191 MessageFormat.format(JGitText.get().cannotDeleteFile,
1192 file.getAbsolutePath()));
1193 removeEmptyParents(file);
1194 }
1195 }
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206 private boolean isModifiedSubtree_IndexWorkingtree(String path)
1207 throws CorruptObjectException, IOException {
1208 try (NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
1209 int dciPos = tw.addTree(new DirCacheIterator(dc));
1210 FileTreeIterator fti = new FileTreeIterator(repo);
1211 tw.addTree(fti);
1212 fti.setDirCacheIterator(tw, dciPos);
1213 tw.setRecursive(true);
1214 tw.setFilter(PathFilter.create(path));
1215 DirCacheIterator dcIt;
1216 WorkingTreeIterator wtIt;
1217 while (tw.next()) {
1218 dcIt = tw.getTree(0, DirCacheIterator.class);
1219 wtIt = tw.getTree(1, WorkingTreeIterator.class);
1220 if (dcIt == null || wtIt == null)
1221 return true;
1222 if (wtIt.isModified(dcIt.getDirCacheEntry(), true,
1223 this.walk.getObjectReader())) {
1224 return true;
1225 }
1226 }
1227 return false;
1228 }
1229 }
1230
1231 private boolean isModified_IndexTree(String path, ObjectId iId,
1232 FileMode iMode, ObjectId tId, FileMode tMode, ObjectId rootTree)
1233 throws CorruptObjectException, IOException {
1234 if (iMode != tMode)
1235 return true;
1236 if (FileMode.TREE.equals(iMode)
1237 && (iId == null || ObjectId.zeroId().equals(iId)))
1238 return isModifiedSubtree_IndexTree(path, rootTree);
1239 else
1240 return !equalIdAndMode(iId, iMode, tId, tMode);
1241 }
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254 private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
1255 throws CorruptObjectException, IOException {
1256 try (NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
1257 tw.addTree(new DirCacheIterator(dc));
1258 tw.addTree(tree);
1259 tw.setRecursive(true);
1260 tw.setFilter(PathFilter.create(path));
1261 while (tw.next()) {
1262 AbstractTreeIterator dcIt = tw.getTree(0,
1263 DirCacheIterator.class);
1264 AbstractTreeIterator treeIt = tw.getTree(1,
1265 AbstractTreeIterator.class);
1266 if (dcIt == null || treeIt == null)
1267 return true;
1268 if (dcIt.getEntryRawMode() != treeIt.getEntryRawMode())
1269 return true;
1270 if (!dcIt.getEntryObjectId().equals(treeIt.getEntryObjectId()))
1271 return true;
1272 }
1273 return false;
1274 }
1275 }
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305 public static void checkoutEntry(Repository repo, DirCacheEntry entry,
1306 ObjectReader or) throws IOException {
1307 checkoutEntry(repo, entry, or, false, null);
1308 }
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348 public static void checkoutEntry(Repository repo, DirCacheEntry entry,
1349 ObjectReader or, boolean deleteRecursive,
1350 CheckoutMetadata checkoutMetadata) throws IOException {
1351 if (checkoutMetadata == null)
1352 checkoutMetadata = CheckoutMetadata.EMPTY;
1353 ObjectLoader ol = or.open(entry.getObjectId());
1354 File f = new File(repo.getWorkTree(), entry.getPathString());
1355 File parentDir = f.getParentFile();
1356 FileUtils.mkdirs(parentDir, true);
1357 FS fs = repo.getFS();
1358 WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
1359 if (entry.getFileMode() == FileMode.SYMLINK
1360 && opt.getSymLinks() == SymLinks.TRUE) {
1361 byte[] bytes = ol.getBytes();
1362 String target = RawParseUtils.decode(bytes);
1363 if (deleteRecursive && f.isDirectory()) {
1364 FileUtils.delete(f, FileUtils.RECURSIVE);
1365 }
1366 fs.createSymLink(f, target);
1367 entry.setLength(bytes.length);
1368 entry.setLastModified(fs.lastModified(f));
1369 return;
1370 }
1371
1372 String name = f.getName();
1373 if (name.length() > 200) {
1374 name = name.substring(0, 200);
1375 }
1376 File tmpFile = File.createTempFile(
1377 "._" + name, null, parentDir);
1378
1379 EolStreamType nonNullEolStreamType;
1380 if (checkoutMetadata.eolStreamType != null) {
1381 nonNullEolStreamType = checkoutMetadata.eolStreamType;
1382 } else if (opt.getAutoCRLF() == AutoCRLF.TRUE) {
1383 nonNullEolStreamType = EolStreamType.AUTO_CRLF;
1384 } else {
1385 nonNullEolStreamType = EolStreamType.DIRECT;
1386 }
1387 try (OutputStream channel = EolStreamTypeUtil.wrapOutputStream(
1388 new FileOutputStream(tmpFile), nonNullEolStreamType)) {
1389 if (checkoutMetadata.smudgeFilterCommand != null) {
1390 if (FilterCommandRegistry
1391 .isRegistered(checkoutMetadata.smudgeFilterCommand)) {
1392 runBuiltinFilterCommand(repo, checkoutMetadata, ol,
1393 channel);
1394 } else {
1395 runExternalFilterCommand(repo, entry, checkoutMetadata, ol,
1396 fs, channel);
1397 }
1398 } else {
1399 ol.copyTo(channel);
1400 }
1401 }
1402
1403
1404
1405
1406 if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT
1407 && checkoutMetadata.smudgeFilterCommand == null) {
1408 entry.setLength(ol.getSize());
1409 } else {
1410 entry.setLength(tmpFile.length());
1411 }
1412
1413 if (opt.isFileMode() && fs.supportsExecute()) {
1414 if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
1415 if (!fs.canExecute(tmpFile))
1416 fs.setExecute(tmpFile, true);
1417 } else {
1418 if (fs.canExecute(tmpFile))
1419 fs.setExecute(tmpFile, false);
1420 }
1421 }
1422 try {
1423 if (deleteRecursive && f.isDirectory()) {
1424 FileUtils.delete(f, FileUtils.RECURSIVE);
1425 }
1426 FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
1427 } catch (IOException e) {
1428 throw new IOException(
1429 MessageFormat.format(JGitText.get().renameFileFailed,
1430 tmpFile.getPath(), f.getPath()),
1431 e);
1432 } finally {
1433 if (tmpFile.exists()) {
1434 FileUtils.delete(tmpFile);
1435 }
1436 }
1437 entry.setLastModified(fs.lastModified(f));
1438 }
1439
1440
1441 private static void runExternalFilterCommand(Repository repo,
1442 DirCacheEntry entry,
1443 CheckoutMetadata checkoutMetadata, ObjectLoader ol, FS fs,
1444 OutputStream channel) throws IOException {
1445 ProcessBuilder filterProcessBuilder = fs.runInShell(
1446 checkoutMetadata.smudgeFilterCommand, new String[0]);
1447 filterProcessBuilder.directory(repo.getWorkTree());
1448 filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
1449 repo.getDirectory().getAbsolutePath());
1450 ExecutionResult result;
1451 int rc;
1452 try {
1453
1454 result = fs.execute(filterProcessBuilder, ol.openStream());
1455 rc = result.getRc();
1456 if (rc == 0) {
1457 result.getStdout().writeTo(channel,
1458 NullProgressMonitor.INSTANCE);
1459 }
1460 } catch (IOException | InterruptedException e) {
1461 throw new IOException(new FilterFailedException(e,
1462 checkoutMetadata.smudgeFilterCommand,
1463 entry.getPathString()));
1464 }
1465 if (rc != 0) {
1466 throw new IOException(new FilterFailedException(rc,
1467 checkoutMetadata.smudgeFilterCommand,
1468 entry.getPathString(),
1469 result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
1470 RawParseUtils.decode(result.getStderr()
1471 .toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
1472 }
1473 }
1474
1475
1476 private static void runBuiltinFilterCommand(Repository repo,
1477 CheckoutMetadata checkoutMetadata, ObjectLoader ol,
1478 OutputStream channel) throws MissingObjectException, IOException {
1479 FilterCommand command = null;
1480 try {
1481 command = FilterCommandRegistry.createFilterCommand(
1482 checkoutMetadata.smudgeFilterCommand, repo, ol.openStream(),
1483 channel);
1484 } catch (IOException e) {
1485 LOG.error(JGitText.get().failedToDetermineFilterDefinition, e);
1486
1487
1488 ol.copyTo(channel);
1489 }
1490 if (command != null) {
1491 while (command.run() != -1) {
1492
1493 }
1494 }
1495 }
1496
1497 @SuppressWarnings("deprecation")
1498 private static void checkValidPath(CanonicalTreeParser t)
1499 throws InvalidPathException {
1500 ObjectChecker chk = new ObjectChecker()
1501 .setSafeForWindows(SystemReader.getInstance().isWindows())
1502 .setSafeForMacOS(SystemReader.getInstance().isMacOS());
1503 for (CanonicalTreeParser i = t; i != null; i = i.getParent())
1504 checkValidPathSegment(chk, i);
1505 }
1506
1507 private static void checkValidPathSegment(ObjectChecker chk,
1508 CanonicalTreeParser t) throws InvalidPathException {
1509 try {
1510 int ptr = t.getNameOffset();
1511 int end = ptr + t.getNameLength();
1512 chk.checkPathSegment(t.getEntryPathBuffer(), ptr, end);
1513 } catch (CorruptObjectException err) {
1514 String path = t.getEntryPathString();
1515 InvalidPathException i = new InvalidPathException(path);
1516 i.initCause(err);
1517 throw i;
1518 }
1519 }
1520 }