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