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