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