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 || force) {
950 update(name, mId, mMode);
951 } else {
952 keep(name, dce, f);
953 }
954 } else {
955 conflict(name, dce, h, m);
956 }
957 }
958 } else {
959
960 if (h == null) {
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977 if (m == null
978 || !isModified_IndexTree(name, iId, iMode, mId, mMode,
979 mergeCommitTree)) {
980
981
982
983 if (m==null && walk.isDirectoryFileConflict()) {
984
985
986
987
988 if (dce != null
989 && (f == null || f.isModified(dce, true,
990 this.walk.getObjectReader())))
991
992
993
994
995
996
997
998
999 conflict(name, dce, h, m);
1000 else
1001
1002
1003
1004
1005
1006
1007
1008
1009 remove(name);
1010 } else
1011
1012
1013
1014
1015
1016
1017 keep(name, dce, f);
1018 } else
1019
1020
1021
1022
1023
1024 conflict(name, dce, h, m);
1025 } else if (m == null) {
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041 if (iMode == FileMode.GITLINK) {
1042
1043
1044
1045
1046
1047 remove(name);
1048 } else {
1049
1050
1051
1052 if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
1053 headCommitTree)) {
1054
1055
1056
1057
1058 if (f != null
1059 && f.isModified(dce, true,
1060 this.walk.getObjectReader())) {
1061
1062
1063
1064
1065
1066
1067 if (!FileMode.TREE.equals(f.getEntryFileMode())
1068 && FileMode.TREE.equals(iMode)) {
1069
1070
1071 return;
1072 }
1073
1074
1075 conflict(name, dce, h, m);
1076 } else {
1077
1078
1079
1080
1081
1082
1083 remove(name);
1084 }
1085 } else {
1086
1087
1088
1089
1090
1091
1092
1093 conflict(name, dce, h, m);
1094 }
1095 }
1096 } else {
1097
1098
1099
1100 if (!equalIdAndMode(hId, hMode, mId, mMode)
1101 && isModified_IndexTree(name, iId, iMode, hId, hMode,
1102 headCommitTree)
1103 && isModified_IndexTree(name, iId, iMode, mId, mMode,
1104 mergeCommitTree))
1105
1106
1107
1108 conflict(name, dce, h, m);
1109 else
1110
1111
1112
1113
1114
1115
1116 if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
1117 headCommitTree)
1118 && isModified_IndexTree(name, iId, iMode, mId, mMode,
1119 mergeCommitTree)) {
1120
1121
1122
1123
1124 if (dce != null
1125 && FileMode.GITLINK.equals(dce.getFileMode())) {
1126
1127
1128
1129
1130
1131
1132
1133
1134 update(name, mId, mMode);
1135 } else if (dce != null
1136 && (f != null && f.isModified(dce, true,
1137 this.walk.getObjectReader()))) {
1138
1139
1140
1141
1142
1143
1144 conflict(name, dce, h, m);
1145 } else {
1146
1147
1148
1149
1150
1151
1152
1153 update(name, mId, mMode);
1154 }
1155 } else {
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168 keep(name, dce, f);
1169 }
1170 }
1171 }
1172 }
1173
1174 private static boolean idEqual(AbstractTreeIterator a,
1175 AbstractTreeIterator b) {
1176 if (a == b) {
1177 return true;
1178 }
1179 if (a == null || b == null) {
1180 return false;
1181 }
1182 return a.getEntryObjectId().equals(b.getEntryObjectId());
1183 }
1184
1185
1186
1187
1188
1189
1190
1191
1192 private void conflict(String path, DirCacheEntry e, AbstractTreeIterator./../org/eclipse/jgit/treewalk/AbstractTreeIterator.html#AbstractTreeIterator">AbstractTreeIterator h, AbstractTreeIterator m) {
1193 conflicts.add(path);
1194
1195 DirCacheEntry entry;
1196 if (e != null) {
1197 entry = new DirCacheEntry(e.getPathString(), DirCacheEntry.STAGE_1);
1198 entry.copyMetaData(e, true);
1199 builder.add(entry);
1200 }
1201
1202 if (h != null && !FileMode.TREE.equals(h.getEntryFileMode())) {
1203 entry = new DirCacheEntry(h.getEntryPathString(), DirCacheEntry.STAGE_2);
1204 entry.setFileMode(h.getEntryFileMode());
1205 entry.setObjectId(h.getEntryObjectId());
1206 builder.add(entry);
1207 }
1208
1209 if (m != null && !FileMode.TREE.equals(m.getEntryFileMode())) {
1210 entry = new DirCacheEntry(m.getEntryPathString(), DirCacheEntry.STAGE_3);
1211 entry.setFileMode(m.getEntryFileMode());
1212 entry.setObjectId(m.getEntryObjectId());
1213 builder.add(entry);
1214 }
1215 }
1216
1217 private void keep(String path, DirCacheEntry e, WorkingTreeIterator f)
1218 throws IOException {
1219 if (e == null) {
1220 return;
1221 }
1222 if (!FileMode.TREE.equals(e.getFileMode())) {
1223 builder.add(e);
1224 }
1225 if (force) {
1226 if (f == null || f.isModified(e, true, walk.getObjectReader())) {
1227 kept.add(path);
1228 checkoutEntry(repo, e, walk.getObjectReader(), false,
1229 new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP),
1230 walk.getFilterCommand(
1231 Constants.ATTR_FILTER_TYPE_SMUDGE)));
1232 }
1233 }
1234 }
1235
1236 private void remove(String path) {
1237 removed.add(path);
1238 }
1239
1240 private void update(String path, ObjectId mId, FileMode mode)
1241 throws IOException {
1242 if (!FileMode.TREE.equals(mode)) {
1243 updated.put(path, new CheckoutMetadata(
1244 walk.getEolStreamType(CHECKOUT_OP),
1245 walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
1246
1247 DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
1248 entry.setObjectId(mId);
1249 entry.setFileMode(mode);
1250 builder.add(entry);
1251 }
1252 }
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263 public void setFailOnConflict(boolean failOnConflict) {
1264 this.failOnConflict = failOnConflict;
1265 }
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277 public void setForce(boolean force) {
1278 this.force = force;
1279 }
1280
1281
1282
1283
1284
1285
1286
1287 private void cleanUpConflicts() throws CheckoutConflictException {
1288
1289 for (String c : conflicts) {
1290 File conflict = new File(repo.getWorkTree(), c);
1291 if (!conflict.delete())
1292 throw new CheckoutConflictException(MessageFormat.format(
1293 JGitText.get().cannotDeleteFile, c));
1294 removeEmptyParents(conflict);
1295 }
1296 }
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307 private boolean isModifiedSubtree_IndexWorkingtree(String path)
1308 throws CorruptObjectException, IOException {
1309 try (NameConflictTreeWalkTreeWalk.html#NameConflictTreeWalk">NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
1310 int dciPos = tw.addTree(new DirCacheIterator(dc));
1311 FileTreeIterator fti = new FileTreeIterator(repo);
1312 tw.addTree(fti);
1313 fti.setDirCacheIterator(tw, dciPos);
1314 tw.setRecursive(true);
1315 tw.setFilter(PathFilter.create(path));
1316 DirCacheIterator dcIt;
1317 WorkingTreeIterator wtIt;
1318 while (tw.next()) {
1319 dcIt = tw.getTree(0, DirCacheIterator.class);
1320 wtIt = tw.getTree(1, WorkingTreeIterator.class);
1321 if (dcIt == null || wtIt == null)
1322 return true;
1323 if (wtIt.isModified(dcIt.getDirCacheEntry(), true,
1324 this.walk.getObjectReader())) {
1325 return true;
1326 }
1327 }
1328 return false;
1329 }
1330 }
1331
1332 private boolean isModified_IndexTree(String path, ObjectId iId,
1333 FileMode iMode, ObjectIdrg/eclipse/jgit/lib/ObjectId.html#ObjectId">ObjectId tId, FileMode tMode, ObjectId rootTree)
1334 throws CorruptObjectException, IOException {
1335 if (iMode != tMode) {
1336 return true;
1337 }
1338 if (FileMode.TREE.equals(iMode)
1339 && (iId == null || ObjectId.zeroId().equals(iId))) {
1340 return isModifiedSubtree_IndexTree(path, rootTree);
1341 }
1342 return !equalIdAndMode(iId, iMode, tId, tMode);
1343 }
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356 private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
1357 throws CorruptObjectException, IOException {
1358 try (NameConflictTreeWalkTreeWalk.html#NameConflictTreeWalk">NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
1359 tw.addTree(new DirCacheIterator(dc));
1360 tw.addTree(tree);
1361 tw.setRecursive(true);
1362 tw.setFilter(PathFilter.create(path));
1363 while (tw.next()) {
1364 AbstractTreeIterator dcIt = tw.getTree(0,
1365 DirCacheIterator.class);
1366 AbstractTreeIterator treeIt = tw.getTree(1,
1367 AbstractTreeIterator.class);
1368 if (dcIt == null || treeIt == null)
1369 return true;
1370 if (dcIt.getEntryRawMode() != treeIt.getEntryRawMode())
1371 return true;
1372 if (!dcIt.getEntryObjectId().equals(treeIt.getEntryObjectId()))
1373 return true;
1374 }
1375 return false;
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
1405
1406
1407
1408
1409
1410 @Deprecated
1411 public static void checkoutEntry(Repository repo, DirCacheEntry entry,
1412 ObjectReader or) throws IOException {
1413 checkoutEntry(repo, entry, or, false, null);
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
1448
1449
1450
1451
1452
1453 public static void checkoutEntry(Repository repo, DirCacheEntry entry,
1454 ObjectReader or, boolean deleteRecursive,
1455 CheckoutMetadata checkoutMetadata) throws IOException {
1456 if (checkoutMetadata == null)
1457 checkoutMetadata = CheckoutMetadata.EMPTY;
1458 ObjectLoader ol = or.open(entry.getObjectId());
1459 File f = new File(repo.getWorkTree(), entry.getPathString());
1460 File parentDir = f.getParentFile();
1461 if (parentDir.isFile()) {
1462 FileUtils.delete(parentDir);
1463 }
1464 FileUtils.mkdirs(parentDir, true);
1465 FS fs = repo.getFS();
1466 WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
1467 if (entry.getFileMode() == FileMode.SYMLINK
1468 && opt.getSymLinks() == SymLinks.TRUE) {
1469 byte[] bytes = ol.getBytes();
1470 String target = RawParseUtils.decode(bytes);
1471 if (deleteRecursive && f.isDirectory()) {
1472 FileUtils.delete(f, FileUtils.RECURSIVE);
1473 }
1474 fs.createSymLink(f, target);
1475 entry.setLength(bytes.length);
1476 entry.setLastModified(fs.lastModifiedInstant(f));
1477 return;
1478 }
1479
1480 String name = f.getName();
1481 if (name.length() > 200) {
1482 name = name.substring(0, 200);
1483 }
1484 File tmpFile = File.createTempFile(
1485 "._" + name, null, parentDir);
1486
1487 getContent(repo, entry.getPathString(), checkoutMetadata, ol, opt,
1488 new FileOutputStream(tmpFile));
1489
1490
1491
1492
1493
1494 if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT
1495 && checkoutMetadata.smudgeFilterCommand == null) {
1496 entry.setLength(ol.getSize());
1497 } else {
1498 entry.setLength(tmpFile.length());
1499 }
1500
1501 if (opt.isFileMode() && fs.supportsExecute()) {
1502 if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
1503 if (!fs.canExecute(tmpFile))
1504 fs.setExecute(tmpFile, true);
1505 } else {
1506 if (fs.canExecute(tmpFile))
1507 fs.setExecute(tmpFile, false);
1508 }
1509 }
1510 try {
1511 if (deleteRecursive && f.isDirectory()) {
1512 FileUtils.delete(f, FileUtils.RECURSIVE);
1513 }
1514 FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
1515 } catch (IOException e) {
1516 throw new IOException(
1517 MessageFormat.format(JGitText.get().renameFileFailed,
1518 tmpFile.getPath(), f.getPath()),
1519 e);
1520 } finally {
1521 if (tmpFile.exists()) {
1522 FileUtils.delete(tmpFile);
1523 }
1524 }
1525 entry.setLastModified(fs.lastModifiedInstant(f));
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
1552
1553
1554
1555
1556
1557 public static void getContent(Repository repo, String path,
1558 CheckoutMetadata checkoutMetadata, ObjectLoader ol,
1559 WorkingTreeOptions opt, OutputStream os)
1560 throws IOException {
1561 EolStreamType nonNullEolStreamType;
1562 if (checkoutMetadata.eolStreamType != null) {
1563 nonNullEolStreamType = checkoutMetadata.eolStreamType;
1564 } else if (opt.getAutoCRLF() == AutoCRLF.TRUE) {
1565 nonNullEolStreamType = EolStreamType.AUTO_CRLF;
1566 } else {
1567 nonNullEolStreamType = EolStreamType.DIRECT;
1568 }
1569 try (OutputStream channel = EolStreamTypeUtil.wrapOutputStream(
1570 os, nonNullEolStreamType)) {
1571 if (checkoutMetadata.smudgeFilterCommand != null) {
1572 if (FilterCommandRegistry
1573 .isRegistered(checkoutMetadata.smudgeFilterCommand)) {
1574 runBuiltinFilterCommand(repo, checkoutMetadata, ol,
1575 channel);
1576 } else {
1577 runExternalFilterCommand(repo, path, checkoutMetadata, ol,
1578 channel);
1579 }
1580 } else {
1581 ol.copyTo(channel);
1582 }
1583 }
1584 }
1585
1586
1587 private static void runExternalFilterCommand(Repository repo, String path,
1588 CheckoutMetadata checkoutMetadata, ObjectLoader ol,
1589 OutputStream channel) throws IOException {
1590 FS fs = repo.getFS();
1591 ProcessBuilder filterProcessBuilder = fs.runInShell(
1592 checkoutMetadata.smudgeFilterCommand, new String[0]);
1593 filterProcessBuilder.directory(repo.getWorkTree());
1594 filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
1595 repo.getDirectory().getAbsolutePath());
1596 ExecutionResult result;
1597 int rc;
1598 try {
1599
1600 result = fs.execute(filterProcessBuilder, ol.openStream());
1601 rc = result.getRc();
1602 if (rc == 0) {
1603 result.getStdout().writeTo(channel,
1604 NullProgressMonitor.INSTANCE);
1605 }
1606 } catch (IOException | InterruptedException e) {
1607 throw new IOException(new FilterFailedException(e,
1608 checkoutMetadata.smudgeFilterCommand,
1609 path));
1610 }
1611 if (rc != 0) {
1612 throw new IOException(new FilterFailedException(rc,
1613 checkoutMetadata.smudgeFilterCommand, path,
1614 result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
1615 result.getStderr().toString(MAX_EXCEPTION_TEXT_SIZE)));
1616 }
1617 }
1618
1619
1620 private static void runBuiltinFilterCommand(Repository repo,
1621 CheckoutMetadata checkoutMetadata, ObjectLoader ol,
1622 OutputStream channel) throws MissingObjectException, IOException {
1623 boolean isMandatory = repo.getConfig().getBoolean(
1624 ConfigConstants.CONFIG_FILTER_SECTION,
1625 ConfigConstants.CONFIG_SECTION_LFS,
1626 ConfigConstants.CONFIG_KEY_REQUIRED, false);
1627 FilterCommand command = null;
1628 try {
1629 command = FilterCommandRegistry.createFilterCommand(
1630 checkoutMetadata.smudgeFilterCommand, repo, ol.openStream(),
1631 channel);
1632 } catch (IOException e) {
1633 LOG.error(JGitText.get().failedToDetermineFilterDefinition, e);
1634 if (!isMandatory) {
1635
1636
1637
1638 ol.copyTo(channel);
1639 } else {
1640 throw e;
1641 }
1642 }
1643 if (command != null) {
1644 while (command.run() != -1) {
1645
1646 }
1647 }
1648 }
1649
1650 @SuppressWarnings("deprecation")
1651 private static void checkValidPath(CanonicalTreeParser t)
1652 throws InvalidPathException {
1653 ObjectChecker chk = new ObjectChecker()
1654 .setSafeForWindows(SystemReader.getInstance().isWindows())
1655 .setSafeForMacOS(SystemReader.getInstance().isMacOS());
1656 for (CanonicalTreeParser i = t; i != null; i = i.getParent())
1657 checkValidPathSegment(chk, i);
1658 }
1659
1660 private static void checkValidPathSegment(ObjectChecker chk,
1661 CanonicalTreeParser t) throws InvalidPathException {
1662 try {
1663 int ptr = t.getNameOffset();
1664 int end = ptr + t.getNameLength();
1665 chk.checkPathSegment(t.getEntryPathBuffer(), ptr, end);
1666 } catch (CorruptObjectException err) {
1667 String path = t.getEntryPathString();
1668 InvalidPathException i = new InvalidPathException(path);
1669 i.initCause(err);
1670 throw i;
1671 }
1672 }
1673 }