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