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