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