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