1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.eclipse.jgit.merge;
16
17 import static java.nio.charset.StandardCharsets.UTF_8;
18 import static java.time.Instant.EPOCH;
19 import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM;
20 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
21 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM;
22 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
23
24 import java.io.Closeable;
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.time.Instant;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Objects;
39 import java.util.TreeMap;
40
41 import org.eclipse.jgit.annotations.NonNull;
42 import org.eclipse.jgit.annotations.Nullable;
43 import org.eclipse.jgit.attributes.Attribute;
44 import org.eclipse.jgit.attributes.Attributes;
45 import org.eclipse.jgit.diff.DiffAlgorithm;
46 import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
47 import org.eclipse.jgit.diff.RawText;
48 import org.eclipse.jgit.diff.RawTextComparator;
49 import org.eclipse.jgit.diff.Sequence;
50 import org.eclipse.jgit.dircache.DirCache;
51 import org.eclipse.jgit.dircache.DirCacheBuildIterator;
52 import org.eclipse.jgit.dircache.DirCacheBuilder;
53 import org.eclipse.jgit.dircache.DirCacheCheckout;
54 import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
55 import org.eclipse.jgit.dircache.DirCacheCheckout.StreamSupplier;
56 import org.eclipse.jgit.dircache.DirCacheEntry;
57 import org.eclipse.jgit.errors.BinaryBlobException;
58 import org.eclipse.jgit.errors.IndexWriteException;
59 import org.eclipse.jgit.errors.NoWorkTreeException;
60 import org.eclipse.jgit.internal.JGitText;
61 import org.eclipse.jgit.lib.Config;
62 import org.eclipse.jgit.lib.ConfigConstants;
63 import org.eclipse.jgit.lib.Constants;
64 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
65 import org.eclipse.jgit.lib.FileMode;
66 import org.eclipse.jgit.lib.ObjectId;
67 import org.eclipse.jgit.lib.ObjectInserter;
68 import org.eclipse.jgit.lib.ObjectLoader;
69 import org.eclipse.jgit.lib.ObjectReader;
70 import org.eclipse.jgit.lib.Repository;
71 import org.eclipse.jgit.revwalk.RevTree;
72 import org.eclipse.jgit.storage.pack.PackConfig;
73 import org.eclipse.jgit.submodule.SubmoduleConflict;
74 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
75 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
76 import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
77 import org.eclipse.jgit.treewalk.TreeWalk;
78 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
79 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
80 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
81 import org.eclipse.jgit.treewalk.filter.TreeFilter;
82 import org.eclipse.jgit.util.FS;
83 import org.eclipse.jgit.util.LfsFactory;
84 import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
85 import org.eclipse.jgit.util.TemporaryBuffer;
86 import org.eclipse.jgit.util.io.EolStreamTypeUtil;
87
88
89
90
91 public class ResolveMerger extends ThreeWayMerger {
92
93
94
95
96
97
98
99
100
101
102 protected static class WorkTreeUpdater implements Closeable {
103
104
105
106
107 public static class Result {
108
109 private final List<String> modifiedFiles = new LinkedList<>();
110
111 private final List<String> failedToDelete = new LinkedList<>();
112
113 private ObjectId treeId = null;
114
115
116
117
118 public ObjectId getTreeId() {
119 return treeId;
120 }
121
122
123
124
125 public List<String> getFailedToDelete() {
126 return failedToDelete;
127 }
128
129
130
131
132 public List<String> getModifiedFiles() {
133 return modifiedFiles;
134 }
135 }
136
137 Result result = new Result();
138
139
140
141
142 @Nullable
143 private final Repository repo;
144
145
146
147
148
149
150
151
152
153 private final boolean inCore;
154
155 private final ObjectInserter inserter;
156
157 private final ObjectReader reader;
158
159 private DirCache dirCache;
160
161 private boolean implicitDirCache = false;
162
163
164
165
166 private DirCacheBuilder builder;
167
168
169
170
171
172 private WorkingTreeOptions workingTreeOptions;
173
174
175
176
177
178 private int inCoreFileSizeLimit;
179
180
181
182
183
184 private final Map<String, DirCacheEntry> toBeCheckedOut = new HashMap<>();
185
186
187
188
189
190 private final TreeMap<String, File> toBeDeleted = new TreeMap<>();
191
192
193
194
195 private Map<String, CheckoutMetadata> checkoutMetadataByPath;
196
197
198
199
200 private Map<String, CheckoutMetadata> cleanupMetadataByPath;
201
202
203
204
205 private boolean indexChangesWritten;
206
207
208
209
210
211
212
213
214 private WorkTreeUpdater(Repository repo, DirCache dirCache) {
215 this.repo = repo;
216 this.dirCache = dirCache;
217
218 this.inCore = false;
219 this.inserter = repo.newObjectInserter();
220 this.reader = inserter.newReader();
221 Config config = repo.getConfig();
222 this.workingTreeOptions = config.get(WorkingTreeOptions.KEY);
223 this.inCoreFileSizeLimit = getInCoreFileSizeLimit(config);
224 this.checkoutMetadataByPath = new HashMap<>();
225 this.cleanupMetadataByPath = new HashMap<>();
226 }
227
228
229
230
231
232
233
234
235
236
237
238 public static WorkTreeUpdater createWorkTreeUpdater(Repository repo,
239 DirCache dirCache) {
240 return new WorkTreeUpdater(repo, dirCache);
241 }
242
243
244
245
246
247
248
249
250
251
252 private WorkTreeUpdater(Repository repo, DirCache dirCache,
253 ObjectInserter oi) {
254 this.repo = repo;
255 this.dirCache = dirCache;
256 this.inserter = oi;
257
258 this.inCore = true;
259 this.reader = oi.newReader();
260 if (repo != null) {
261 this.inCoreFileSizeLimit = getInCoreFileSizeLimit(
262 repo.getConfig());
263 }
264 }
265
266
267
268
269
270
271
272
273
274
275
276
277
278 public static WorkTreeUpdater createInCoreWorkTreeUpdater(
279 Repository repo, DirCache dirCache, ObjectInserter oi) {
280 return new WorkTreeUpdater(repo, dirCache, oi);
281 }
282
283 private static int getInCoreFileSizeLimit(Config config) {
284 return config.getInt(ConfigConstants.CONFIG_MERGE_SECTION,
285 ConfigConstants.CONFIG_KEY_IN_CORE_LIMIT, 10 << 20);
286 }
287
288
289
290
291
292
293 public int getInCoreFileSizeLimit() {
294 return inCoreFileSizeLimit;
295 }
296
297
298
299
300
301
302
303
304 public DirCache getLockedDirCache() throws IOException {
305 if (dirCache == null) {
306 implicitDirCache = true;
307 if (inCore) {
308 dirCache = DirCache.newInCore();
309 } else {
310 dirCache = nonNullRepo().lockDirCache();
311 }
312 }
313 if (builder == null) {
314 builder = dirCache.builder();
315 }
316 return dirCache;
317 }
318
319
320
321
322
323
324
325 public DirCacheBuildIterator createDirCacheBuildIterator() {
326 return new DirCacheBuildIterator(builder);
327 }
328
329
330
331
332
333
334
335
336
337 public void writeWorkTreeChanges(boolean shouldCheckoutTheirs)
338 throws IOException {
339 handleDeletedFiles();
340
341 if (inCore) {
342 builder.finish();
343 return;
344 }
345 if (shouldCheckoutTheirs) {
346
347
348
349 checkout();
350 }
351
352
353
354
355
356
357 if (!builder.commit()) {
358 revertModifiedFiles();
359 throw new IndexWriteException();
360 }
361 }
362
363
364
365
366
367
368
369
370 public Result writeIndexChanges() throws IOException {
371 result.treeId = getLockedDirCache().writeTree(inserter);
372 indexChangesWritten = true;
373 return result;
374 }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 public void addToCheckout(String path, DirCacheEntry entry,
394 EolStreamType cleanupStreamType, String cleanupSmudgeCommand,
395 EolStreamType checkoutStreamType,
396 String checkoutSmudgeCommand) {
397 if (entry != null) {
398
399 toBeCheckedOut.put(path, entry);
400 }
401 addCheckoutMetadata(cleanupMetadataByPath, path, cleanupStreamType,
402 cleanupSmudgeCommand);
403 addCheckoutMetadata(checkoutMetadataByPath, path,
404 checkoutStreamType, checkoutSmudgeCommand);
405 }
406
407
408
409
410
411
412
413
414
415
416
417 public Map<String, DirCacheEntry> getToBeCheckedOut() {
418 return toBeCheckedOut;
419 }
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436 public void deleteFile(String path, File file, EolStreamType streamType,
437 String smudgeCommand) {
438 toBeDeleted.put(path, file);
439 if (file != null && file.isFile()) {
440 addCheckoutMetadata(cleanupMetadataByPath, path, streamType,
441 smudgeCommand);
442 }
443 }
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458 private void addCheckoutMetadata(Map<String, CheckoutMetadata> map,
459 String path, EolStreamType streamType, String smudgeCommand) {
460 if (inCore || map == null) {
461 return;
462 }
463 map.put(path, new CheckoutMetadata(streamType, smudgeCommand));
464 }
465
466
467
468
469
470
471
472
473
474
475
476 public EolStreamType detectCheckoutStreamType(Attributes attributes) {
477 if (inCore) {
478 return null;
479 }
480 return EolStreamTypeUtil.detectStreamType(OperationType.CHECKOUT_OP,
481 workingTreeOptions, attributes);
482 }
483
484 private void handleDeletedFiles() {
485
486
487
488 for (String path : toBeDeleted.descendingKeySet()) {
489 File file = inCore ? null : toBeDeleted.get(path);
490 if (file != null && !file.delete()) {
491 if (!file.isDirectory()) {
492 result.failedToDelete.add(path);
493 }
494 }
495 }
496 }
497
498
499
500
501
502
503
504 public void markAsModified(String path) {
505 result.modifiedFiles.add(path);
506 }
507
508
509
510
511
512
513 public List<String> getModifiedFiles() {
514 return result.modifiedFiles;
515 }
516
517 private void checkout() throws NoWorkTreeException, IOException {
518 for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
519 .entrySet()) {
520 DirCacheEntry dirCacheEntry = entry.getValue();
521 if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
522 new File(nonNullRepo().getWorkTree(), entry.getKey())
523 .mkdirs();
524 } else {
525 DirCacheCheckout.checkoutEntry(repo, dirCacheEntry, reader,
526 false, checkoutMetadataByPath.get(entry.getKey()),
527 workingTreeOptions);
528 result.modifiedFiles.add(entry.getKey());
529 }
530 }
531 }
532
533
534
535
536
537
538
539
540
541
542 public void revertModifiedFiles() throws IOException {
543 if (inCore) {
544 result.modifiedFiles.clear();
545 return;
546 }
547 if (indexChangesWritten) {
548 return;
549 }
550 for (String path : result.modifiedFiles) {
551 DirCacheEntry entry = dirCache.getEntry(path);
552 if (entry != null) {
553 DirCacheCheckout.checkoutEntry(repo, entry, reader, false,
554 cleanupMetadataByPath.get(path),
555 workingTreeOptions);
556 }
557 }
558 }
559
560 @Override
561 public void close() throws IOException {
562 if (implicitDirCache) {
563 dirCache.unlock();
564 }
565 }
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583 public void updateFileWithContent(StreamSupplier inputStream,
584 EolStreamType streamType, String smudgeCommand, String path,
585 File file) throws IOException {
586 if (inCore) {
587 return;
588 }
589 CheckoutMetadata metadata = new CheckoutMetadata(streamType,
590 smudgeCommand);
591
592 try (OutputStream outputStream = new FileOutputStream(file)) {
593 DirCacheCheckout.getContent(repo, path, metadata, inputStream,
594 workingTreeOptions, outputStream);
595 }
596 }
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620 public DirCacheEntry insertToIndex(InputStream input, byte[] path,
621 FileMode fileMode, int entryStage, Instant lastModified,
622 int len, Attribute lfsAttribute) throws IOException {
623 return addExistingToIndex(insertResult(input, lfsAttribute, len),
624 path, fileMode, entryStage, lastModified, len);
625 }
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644 public DirCacheEntry addExistingToIndex(ObjectId objectId, byte[] path,
645 FileMode fileMode, int entryStage, Instant lastModified,
646 int len) {
647 DirCacheEntry dce = new DirCacheEntry(path, entryStage);
648 dce.setFileMode(fileMode);
649 if (lastModified != null) {
650 dce.setLastModified(lastModified);
651 }
652 dce.setLength(inCore ? 0 : len);
653 dce.setObjectId(objectId);
654 builder.add(dce);
655 return dce;
656 }
657
658 private ObjectId insertResult(InputStream input, Attribute lfsAttribute,
659 long length) throws IOException {
660 try (LfsInputStream is = LfsFactory.getInstance()
661 .applyCleanFilter(repo, input, length, lfsAttribute)) {
662 return inserter.insert(OBJ_BLOB, is.getLength(), is);
663 }
664 }
665
666
667
668
669
670
671
672
673
674 @NonNull
675 private Repository nonNullRepo() throws NullPointerException {
676 return Objects.requireNonNull(repo,
677 () -> JGitText.get().repositoryIsRequired);
678 }
679 }
680
681
682
683
684
685 public enum MergeFailureReason {
686
687 DIRTY_INDEX,
688
689 DIRTY_WORKTREE,
690
691 COULD_NOT_DELETE
692 }
693
694
695
696
697
698
699 protected NameConflictTreeWalk tw;
700
701
702
703
704
705
706 protected String[] commitNames;
707
708
709
710
711
712
713 protected static final int T_BASE = 0;
714
715
716
717
718
719
720 protected static final int T_OURS = 1;
721
722
723
724
725
726
727 protected static final int T_THEIRS = 2;
728
729
730
731
732
733
734 protected static final int T_INDEX = 3;
735
736
737
738
739
740
741 protected static final int T_FILE = 4;
742
743
744
745
746
747
748 protected WorkTreeUpdater workTreeUpdater;
749
750
751
752
753
754
755 protected ObjectId resultTree;
756
757
758
759
760 protected List<String> modifiedFiles = new ArrayList<>();
761
762
763
764
765
766
767
768 protected List<String> unmergedPaths = new ArrayList<>();
769
770
771
772
773
774
775
776 protected Map<String, MergeResult<? extends Sequence>> mergeResults = new HashMap<>();
777
778
779
780
781
782
783 protected Map<String, MergeFailureReason> failingPaths = new HashMap<>();
784
785
786
787
788
789
790
791 protected boolean enterSubtree;
792
793
794
795
796
797
798
799
800
801 protected boolean inCore;
802
803
804
805
806
807 protected DirCache dircache;
808
809
810
811
812
813
814 protected WorkingTreeIterator workingTreeIterator;
815
816
817
818
819
820 protected MergeAlgorithm mergeAlgorithm;
821
822
823
824
825
826 @NonNull
827 private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
828
829 private static MergeAlgorithm getMergeAlgorithm(Config config) {
830 SupportedAlgorithm diffAlg = config.getEnum(
831 CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
832 HISTOGRAM);
833 return new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
834 }
835
836 private static String[] defaultCommitNames() {
837 return new String[]{"BASE", "OURS", "THEIRS"};
838 }
839
840 private static final Attributes NO_ATTRIBUTES = new Attributes();
841
842
843
844
845
846
847
848
849
850 protected ResolveMerger(Repository local, boolean inCore) {
851 super(local);
852 Config config = local.getConfig();
853 mergeAlgorithm = getMergeAlgorithm(config);
854 commitNames = defaultCommitNames();
855 this.inCore = inCore;
856 }
857
858
859
860
861
862
863
864 protected ResolveMerger(Repository local) {
865 this(local, false);
866 }
867
868
869
870
871
872
873
874
875
876
877 protected ResolveMerger(ObjectInserter inserter, Config config) {
878 super(inserter);
879 mergeAlgorithm = getMergeAlgorithm(config);
880 commitNames = defaultCommitNames();
881 inCore = true;
882 }
883
884
885
886
887
888
889
890 @NonNull
891 public ContentMergeStrategy getContentMergeStrategy() {
892 return contentStrategy;
893 }
894
895
896
897
898
899
900
901
902 public void setContentMergeStrategy(ContentMergeStrategy strategy) {
903 contentStrategy = strategy == null ? ContentMergeStrategy.CONFLICT
904 : strategy;
905 }
906
907
908 @Override
909 protected boolean mergeImpl() throws IOException {
910 return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
911 false);
912 }
913
914
915
916
917
918
919
920
921
922
923
924 private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
925 Instant lastMod, long len) {
926 if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
927 return workTreeUpdater.addExistingToIndex(p.getEntryObjectId(), path,
928 p.getEntryFileMode(), stage,
929 lastMod, (int) len);
930 }
931 return null;
932 }
933
934
935
936
937
938
939
940
941
942
943
944
945
946 private DirCacheEntry addConflict(CanonicalTreeParser base,
947 CanonicalTreeParser ours, CanonicalTreeParser theirs) {
948 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
949 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
950 return add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
951 }
952
953
954
955
956
957
958
959
960
961
962 private DirCacheEntry keep(DirCacheEntry e) {
963 return workTreeUpdater.addExistingToIndex(e.getObjectId(), e.getRawPath(), e.getFileMode(),
964 e.getStage(), e.getLastModifiedInstant(), e.getLength());
965 }
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981 protected void addToCheckout(String path, DirCacheEntry entry,
982 Attributes[] attributes)
983 throws IOException {
984 EolStreamType cleanupStreamType = workTreeUpdater.detectCheckoutStreamType(attributes[T_OURS]);
985 String cleanupSmudgeCommand = tw.getSmudgeCommand(attributes[T_OURS]);
986 EolStreamType checkoutStreamType = workTreeUpdater.detectCheckoutStreamType(attributes[T_THEIRS]);
987 String checkoutSmudgeCommand = tw.getSmudgeCommand(attributes[T_THEIRS]);
988 workTreeUpdater.addToCheckout(path, entry, cleanupStreamType, cleanupSmudgeCommand,
989 checkoutStreamType, checkoutSmudgeCommand);
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006 protected void addDeletion(String path, boolean isFile,
1007 Attributes attributes) throws IOException {
1008 if (db == null || nonNullRepo().isBare() || !isFile)
1009 return;
1010
1011 File file = new File(nonNullRepo().getWorkTree(), path);
1012 EolStreamType streamType = workTreeUpdater.detectCheckoutStreamType(attributes);
1013 String smudgeCommand = tw.getSmudgeCommand(attributes);
1014 workTreeUpdater.deleteFile(path, file, streamType, smudgeCommand);
1015 }
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061 protected boolean processEntry(CanonicalTreeParser base,
1062 CanonicalTreeParser ours, CanonicalTreeParser theirs,
1063 DirCacheBuildIterator index, WorkingTreeIterator work,
1064 boolean ignoreConflicts, Attributes[] attributes)
1065 throws IOException {
1066 enterSubtree = true;
1067 final int modeO = tw.getRawMode(T_OURS);
1068 final int modeT = tw.getRawMode(T_THEIRS);
1069 final int modeB = tw.getRawMode(T_BASE);
1070 boolean gitLinkMerging = isGitLink(modeO) || isGitLink(modeT)
1071 || isGitLink(modeB);
1072 if (modeO == 0 && modeT == 0 && modeB == 0) {
1073
1074 return true;
1075 }
1076
1077 if (isIndexDirty()) {
1078 return false;
1079 }
1080
1081 DirCacheEntry ourDce = null;
1082
1083 if (index == null || index.getDirCacheEntry() == null) {
1084
1085
1086 if (nonTree(modeO)) {
1087 ourDce = new DirCacheEntry(tw.getRawPath());
1088 ourDce.setObjectId(tw.getObjectId(T_OURS));
1089 ourDce.setFileMode(tw.getFileMode(T_OURS));
1090 }
1091 } else {
1092 ourDce = index.getDirCacheEntry();
1093 }
1094
1095 if (nonTree(modeO) && nonTree(modeT) && tw.idEqual(T_OURS, T_THEIRS)) {
1096
1097 if (modeO == modeT) {
1098
1099
1100
1101 keep(ourDce);
1102
1103 return true;
1104 }
1105
1106
1107
1108 int newMode = mergeFileModes(modeB, modeO, modeT);
1109 if (newMode != FileMode.MISSING.getBits()) {
1110 if (newMode == modeO) {
1111
1112 keep(ourDce);
1113 } else {
1114
1115
1116 if (isWorktreeDirty(work, ourDce)) {
1117 return false;
1118 }
1119
1120
1121
1122 DirCacheEntry e = add(tw.getRawPath(), theirs,
1123 DirCacheEntry.STAGE_0, EPOCH, 0);
1124 addToCheckout(tw.getPathString(), e, attributes);
1125 }
1126 return true;
1127 }
1128 if (!ignoreConflicts) {
1129
1130
1131
1132
1133
1134 addConflict(base, ours, theirs);
1135 unmergedPaths.add(tw.getPathString());
1136 mergeResults.put(tw.getPathString(),
1137 new MergeResult<>(Collections.emptyList()));
1138 }
1139 return true;
1140 }
1141
1142 if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
1143
1144
1145 if (ourDce != null) {
1146 keep(ourDce);
1147 }
1148
1149 return true;
1150 }
1151
1152 if (modeB == modeO && tw.idEqual(T_BASE, T_OURS)) {
1153
1154
1155
1156
1157 if (isWorktreeDirty(work, ourDce)) {
1158 return false;
1159 }
1160 if (nonTree(modeT)) {
1161
1162
1163
1164 DirCacheEntry e = add(tw.getRawPath(), theirs,
1165 DirCacheEntry.STAGE_0, EPOCH, 0);
1166 if (e != null) {
1167 addToCheckout(tw.getPathString(), e, attributes);
1168 }
1169 return true;
1170 }
1171
1172
1173
1174 if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) {
1175
1176 return true;
1177 }
1178 if (modeT != 0 && modeT == modeB) {
1179
1180 return true;
1181 }
1182 addDeletion(tw.getPathString(), nonTree(modeO), attributes[T_OURS]);
1183 return true;
1184 }
1185
1186 if (tw.isSubtree()) {
1187
1188
1189
1190
1191 if (nonTree(modeO) != nonTree(modeT)) {
1192 if (ignoreConflicts) {
1193
1194
1195
1196
1197 enterSubtree = false;
1198 return true;
1199 }
1200 if (nonTree(modeB)) {
1201 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
1202 }
1203 if (nonTree(modeO)) {
1204 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
1205 }
1206 if (nonTree(modeT)) {
1207 add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
1208 }
1209 unmergedPaths.add(tw.getPathString());
1210 enterSubtree = false;
1211 return true;
1212 }
1213
1214
1215
1216
1217
1218 if (!nonTree(modeO)) {
1219 return true;
1220 }
1221
1222
1223
1224 }
1225
1226 if (nonTree(modeO) && nonTree(modeT)) {
1227
1228 boolean worktreeDirty = isWorktreeDirty(work, ourDce);
1229 if (!attributes[T_OURS].canBeContentMerged() && worktreeDirty) {
1230 return false;
1231 }
1232
1233 if (gitLinkMerging && ignoreConflicts) {
1234
1235
1236 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
1237 return true;
1238 } else if (gitLinkMerging) {
1239 addConflict(base, ours, theirs);
1240 MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
1241 base, ours, theirs);
1242 result.setContainsConflicts(true);
1243 mergeResults.put(tw.getPathString(), result);
1244 unmergedPaths.add(tw.getPathString());
1245 return true;
1246 } else if (!attributes[T_OURS].canBeContentMerged()) {
1247
1248 switch (getContentMergeStrategy()) {
1249 case OURS:
1250 keep(ourDce);
1251 return true;
1252 case THEIRS:
1253 DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
1254 DirCacheEntry.STAGE_0, EPOCH, 0);
1255 addToCheckout(tw.getPathString(), theirEntry, attributes);
1256 return true;
1257 default:
1258 break;
1259 }
1260 addConflict(base, ours, theirs);
1261
1262
1263 unmergedPaths.add(tw.getPathString());
1264 return true;
1265 }
1266
1267
1268 if (worktreeDirty) {
1269 return false;
1270 }
1271
1272 MergeResult<RawText> result = null;
1273 boolean hasSymlink = FileMode.SYMLINK.equals(modeO)
1274 || FileMode.SYMLINK.equals(modeT);
1275 if (!hasSymlink) {
1276 try {
1277 result = contentMerge(base, ours, theirs, attributes,
1278 getContentMergeStrategy());
1279 } catch (BinaryBlobException e) {
1280
1281 }
1282 }
1283 if (result == null) {
1284 switch (getContentMergeStrategy()) {
1285 case OURS:
1286 keep(ourDce);
1287 return true;
1288 case THEIRS:
1289 DirCacheEntry e = add(tw.getRawPath(), theirs,
1290 DirCacheEntry.STAGE_0, EPOCH, 0);
1291 if (e != null) {
1292 addToCheckout(tw.getPathString(), e, attributes);
1293 }
1294 return true;
1295 default:
1296 result = new MergeResult<>(Collections.emptyList());
1297 result.setContainsConflicts(true);
1298 break;
1299 }
1300 }
1301 if (ignoreConflicts) {
1302 result.setContainsConflicts(false);
1303 }
1304 String currentPath = tw.getPathString();
1305 if (hasSymlink) {
1306 if (ignoreConflicts) {
1307 if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)) {
1308 DirCacheEntry e = add(tw.getRawPath(), theirs,
1309 DirCacheEntry.STAGE_0, EPOCH, 0);
1310 addToCheckout(currentPath, e, attributes);
1311 } else {
1312 keep(ourDce);
1313 }
1314 } else {
1315
1316 DirCacheEntry e = addConflict(base, ours, theirs);
1317 mergeResults.put(currentPath, result);
1318
1319
1320 if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)
1321 && e != null) {
1322 addToCheckout(currentPath, e, attributes);
1323 }
1324 }
1325 } else {
1326 updateIndex(base, ours, theirs, result, attributes[T_OURS]);
1327 }
1328 if (result.containsConflicts() && !ignoreConflicts) {
1329 unmergedPaths.add(currentPath);
1330 }
1331 workTreeUpdater.markAsModified(currentPath);
1332
1333 addToCheckout(currentPath, null, attributes);
1334 } else if (modeO != modeT) {
1335
1336 if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
1337 .idEqual(T_BASE, T_THEIRS)))) {
1338 if (gitLinkMerging && ignoreConflicts) {
1339 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
1340 } else if (gitLinkMerging) {
1341 addConflict(base, ours, theirs);
1342 MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
1343 base, ours, theirs);
1344 result.setContainsConflicts(true);
1345 mergeResults.put(tw.getPathString(), result);
1346 unmergedPaths.add(tw.getPathString());
1347 } else {
1348 boolean isSymLink = ((modeO | modeT)
1349 & FileMode.TYPE_MASK) == FileMode.TYPE_SYMLINK;
1350
1351
1352 MergeResult<RawText> result;
1353 if (isSymLink) {
1354
1355 result = new MergeResult<>(Collections.emptyList());
1356 result.setContainsConflicts(true);
1357 } else {
1358 try {
1359 result = contentMerge(base, ours, theirs,
1360 attributes, ContentMergeStrategy.CONFLICT);
1361 } catch (BinaryBlobException e) {
1362 result = new MergeResult<>(Collections.emptyList());
1363 result.setContainsConflicts(true);
1364 }
1365 }
1366 if (ignoreConflicts) {
1367 result.setContainsConflicts(false);
1368 if (isSymLink) {
1369 if (modeO != 0) {
1370 keep(ourDce);
1371 } else {
1372
1373 if (isWorktreeDirty(work, ourDce)) {
1374 return false;
1375 }
1376 DirCacheEntry e = add(tw.getRawPath(), theirs,
1377 DirCacheEntry.STAGE_0, EPOCH, 0);
1378 if (e != null) {
1379 addToCheckout(tw.getPathString(), e,
1380 attributes);
1381 }
1382 }
1383 } else {
1384
1385
1386
1387
1388 updateIndex(base, ours, theirs, result,
1389 attributes[T_OURS]);
1390 }
1391 } else {
1392 DirCacheEntry e = addConflict(base, ours, theirs);
1393
1394
1395 if (modeO == 0) {
1396
1397 if (isWorktreeDirty(work, ourDce)) {
1398 return false;
1399 }
1400 if (nonTree(modeT) && e != null) {
1401 addToCheckout(tw.getPathString(), e,
1402 attributes);
1403 }
1404 }
1405
1406 unmergedPaths.add(tw.getPathString());
1407
1408
1409 mergeResults.put(tw.getPathString(), result);
1410 }
1411 }
1412 }
1413 }
1414 return true;
1415 }
1416
1417 private static MergeResult<SubmoduleConflict> createGitLinksMergeResult(
1418 CanonicalTreeParser base, CanonicalTreeParser ours,
1419 CanonicalTreeParser theirs) {
1420 return new MergeResult<>(Arrays.asList(
1421 new SubmoduleConflict(
1422 base == null ? null : base.getEntryObjectId()),
1423 new SubmoduleConflict(
1424 ours == null ? null : ours.getEntryObjectId()),
1425 new SubmoduleConflict(
1426 theirs == null ? null : theirs.getEntryObjectId())));
1427 }
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445 private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
1446 CanonicalTreeParser ours, CanonicalTreeParser theirs,
1447 Attributes[] attributes, ContentMergeStrategy strategy)
1448 throws BinaryBlobException, IOException {
1449
1450
1451 RawText baseText = base == null ? RawText.EMPTY_TEXT
1452 : getRawText(base.getEntryObjectId(), attributes[T_BASE]);
1453 RawText ourText = ours == null ? RawText.EMPTY_TEXT
1454 : getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
1455 RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
1456 : getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
1457 mergeAlgorithm.setContentMergeStrategy(strategy);
1458 return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
1459 ourText, theirsText);
1460 }
1461
1462 private boolean isIndexDirty() {
1463 if (inCore) {
1464 return false;
1465 }
1466
1467 final int modeI = tw.getRawMode(T_INDEX);
1468 final int modeO = tw.getRawMode(T_OURS);
1469
1470
1471 final boolean isDirty = nonTree(modeI)
1472 && !(modeO == modeI && tw.idEqual(T_INDEX, T_OURS));
1473 if (isDirty) {
1474 failingPaths
1475 .put(tw.getPathString(), MergeFailureReason.DIRTY_INDEX);
1476 }
1477 return isDirty;
1478 }
1479
1480 private boolean isWorktreeDirty(WorkingTreeIterator work,
1481 DirCacheEntry ourDce) throws IOException {
1482 if (work == null) {
1483 return false;
1484 }
1485
1486 final int modeF = tw.getRawMode(T_FILE);
1487 final int modeO = tw.getRawMode(T_OURS);
1488
1489
1490 boolean isDirty;
1491 if (ourDce != null) {
1492 isDirty = work.isModified(ourDce, true, reader);
1493 } else {
1494 isDirty = work.isModeDifferent(modeO);
1495 if (!isDirty && nonTree(modeF)) {
1496 isDirty = !tw.idEqual(T_FILE, T_OURS);
1497 }
1498 }
1499
1500
1501 if (isDirty && modeF == FileMode.TYPE_TREE
1502 && modeO == FileMode.TYPE_MISSING) {
1503 isDirty = false;
1504 }
1505 if (isDirty) {
1506 failingPaths.put(tw.getPathString(),
1507 MergeFailureReason.DIRTY_WORKTREE);
1508 }
1509 return isDirty;
1510 }
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525 private void updateIndex(CanonicalTreeParser base,
1526 CanonicalTreeParser ours, CanonicalTreeParser theirs,
1527 MergeResult<RawText> result, Attributes attributes)
1528 throws IOException {
1529 TemporaryBuffer rawMerged = null;
1530 try {
1531 rawMerged = doMerge(result);
1532 File mergedFile = inCore ? null
1533 : writeMergedFile(rawMerged, attributes);
1534 if (result.containsConflicts()) {
1535
1536
1537
1538 addConflict(base, ours, theirs);
1539 mergeResults.put(tw.getPathString(), result);
1540 return;
1541 }
1542
1543
1544
1545 Instant lastModified = mergedFile == null ? null
1546 : nonNullRepo().getFS().lastModifiedInstant(mergedFile);
1547
1548
1549 int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1),
1550 tw.getRawMode(2));
1551 FileMode mode = newMode == FileMode.MISSING.getBits()
1552 ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode);
1553 workTreeUpdater.insertToIndex(rawMerged.openInputStream(),
1554 tw.getPathString().getBytes(UTF_8), mode,
1555 DirCacheEntry.STAGE_0, lastModified,
1556 (int) rawMerged.length(),
1557 attributes.get(Constants.ATTR_MERGE));
1558 } finally {
1559 if (rawMerged != null) {
1560 rawMerged.destroy();
1561 }
1562 }
1563 }
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575 private File writeMergedFile(TemporaryBuffer rawMerged,
1576 Attributes attributes)
1577 throws IOException {
1578 File workTree = nonNullRepo().getWorkTree();
1579 FS fs = nonNullRepo().getFS();
1580 File of = new File(workTree, tw.getPathString());
1581 File parentFolder = of.getParentFile();
1582 EolStreamType eol = workTreeUpdater.detectCheckoutStreamType(attributes);
1583 if (!fs.exists(parentFolder)) {
1584 parentFolder.mkdirs();
1585 }
1586 workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
1587 eol, tw.getSmudgeCommand(attributes), of.getPath(), of);
1588 return of;
1589 }
1590
1591 private TemporaryBuffer doMerge(MergeResult<RawText> result)
1592 throws IOException {
1593 TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(
1594 db != null ? nonNullRepo().getDirectory() : null, workTreeUpdater.getInCoreFileSizeLimit());
1595 boolean success = false;
1596 try {
1597 new MergeFormatter().formatMerge(buf, result,
1598 Arrays.asList(commitNames), UTF_8);
1599 buf.close();
1600 success = true;
1601 } finally {
1602 if (!success) {
1603 buf.destroy();
1604 }
1605 }
1606 return buf;
1607 }
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625 private int mergeFileModes(int modeB, int modeO, int modeT) {
1626 if (modeO == modeT) {
1627 return modeO;
1628 }
1629 if (modeB == modeO) {
1630
1631 return (modeT == FileMode.MISSING.getBits()) ? modeO : modeT;
1632 }
1633 if (modeB == modeT) {
1634
1635 return (modeO == FileMode.MISSING.getBits()) ? modeT : modeO;
1636 }
1637 return FileMode.MISSING.getBits();
1638 }
1639
1640 private RawText getRawText(ObjectId id,
1641 Attributes attributes)
1642 throws IOException, BinaryBlobException {
1643 if (id.equals(ObjectId.zeroId())) {
1644 return new RawText(new byte[]{});
1645 }
1646
1647 ObjectLoader loader = LfsFactory.getInstance().applySmudgeFilter(
1648 getRepository(), reader.open(id, OBJ_BLOB),
1649 attributes.get(Constants.ATTR_MERGE));
1650 int threshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
1651 return RawText.load(loader, threshold);
1652 }
1653
1654 private static boolean nonTree(int mode) {
1655 return mode != 0 && !FileMode.TREE.equals(mode);
1656 }
1657
1658 private static boolean isGitLink(int mode) {
1659 return FileMode.GITLINK.equals(mode);
1660 }
1661
1662
1663 @Override
1664 public ObjectId getResultTreeId() {
1665 return (resultTree == null) ? null : resultTree.toObjectId();
1666 }
1667
1668
1669
1670
1671
1672
1673
1674
1675 public void setCommitNames(String[] commitNames) {
1676 this.commitNames = commitNames;
1677 }
1678
1679
1680
1681
1682
1683
1684
1685 public String[] getCommitNames() {
1686 return commitNames;
1687 }
1688
1689
1690
1691
1692
1693
1694
1695
1696 public List<String> getUnmergedPaths() {
1697 return unmergedPaths;
1698 }
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708 public List<String> getModifiedFiles() {
1709 return workTreeUpdater != null ? workTreeUpdater.getModifiedFiles() : modifiedFiles;
1710 }
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722 public Map<String, DirCacheEntry> getToBeCheckedOut() {
1723 return workTreeUpdater.getToBeCheckedOut();
1724 }
1725
1726
1727
1728
1729
1730
1731 public Map<String, MergeResult<? extends Sequence>> getMergeResults() {
1732 return mergeResults;
1733 }
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743 public Map<String, MergeFailureReason> getFailingPaths() {
1744 return failingPaths.isEmpty() ? null : failingPaths;
1745 }
1746
1747
1748
1749
1750
1751
1752
1753
1754 public boolean failed() {
1755 return !failingPaths.isEmpty();
1756 }
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771 public void setDirCache(DirCache dc) {
1772 this.dircache = dc;
1773 }
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786 public void setWorkingTreeIterator(WorkingTreeIterator workingTreeIterator) {
1787 this.workingTreeIterator = workingTreeIterator;
1788 }
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824 protected boolean mergeTrees(AbstractTreeIterator baseTree,
1825 RevTree headTree, RevTree mergeTree, boolean ignoreConflicts)
1826 throws IOException {
1827 try {
1828 workTreeUpdater = inCore ?
1829 WorkTreeUpdater.createInCoreWorkTreeUpdater(db, dircache, getObjectInserter()) :
1830 WorkTreeUpdater.createWorkTreeUpdater(db, dircache);
1831 dircache = workTreeUpdater.getLockedDirCache();
1832 tw = new NameConflictTreeWalk(db, reader);
1833
1834 tw.addTree(baseTree);
1835 tw.setHead(tw.addTree(headTree));
1836 tw.addTree(mergeTree);
1837 DirCacheBuildIterator buildIt = workTreeUpdater.createDirCacheBuildIterator();
1838 int dciPos = tw.addTree(buildIt);
1839 if (workingTreeIterator != null) {
1840 tw.addTree(workingTreeIterator);
1841 workingTreeIterator.setDirCacheIterator(tw, dciPos);
1842 } else {
1843 tw.setFilter(TreeFilter.ANY_DIFF);
1844 }
1845
1846 if (!mergeTreeWalk(tw, ignoreConflicts)) {
1847 return false;
1848 }
1849
1850 workTreeUpdater.writeWorkTreeChanges(true);
1851 if (getUnmergedPaths().isEmpty() && !failed()) {
1852 WorkTreeUpdater.Result result = workTreeUpdater.writeIndexChanges();
1853 resultTree = result.getTreeId();
1854 modifiedFiles = result.getModifiedFiles();
1855 for (String f : result.getFailedToDelete()) {
1856 failingPaths.put(f, MergeFailureReason.COULD_NOT_DELETE);
1857 }
1858 return result.getFailedToDelete().isEmpty();
1859 }
1860 resultTree = null;
1861 return false;
1862 } finally {
1863 if(modifiedFiles.isEmpty()) {
1864 modifiedFiles = workTreeUpdater.getModifiedFiles();
1865 }
1866 workTreeUpdater.close();
1867 workTreeUpdater = null;
1868 }
1869 }
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883 protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
1884 throws IOException {
1885 boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
1886 boolean hasAttributeNodeProvider = treeWalk
1887 .getAttributesNodeProvider() != null;
1888 while (treeWalk.next()) {
1889 Attributes[] attributes = {NO_ATTRIBUTES, NO_ATTRIBUTES,
1890 NO_ATTRIBUTES};
1891 if (hasAttributeNodeProvider) {
1892 attributes[T_BASE] = treeWalk.getAttributes(T_BASE);
1893 attributes[T_OURS] = treeWalk.getAttributes(T_OURS);
1894 attributes[T_THEIRS] = treeWalk.getAttributes(T_THEIRS);
1895 }
1896 if (!processEntry(
1897 treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
1898 treeWalk.getTree(T_OURS, CanonicalTreeParser.class),
1899 treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
1900 treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
1901 hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
1902 WorkingTreeIterator.class) : null,
1903 ignoreConflicts, attributes)) {
1904 workTreeUpdater.revertModifiedFiles();
1905 return false;
1906 }
1907 if (treeWalk.isSubtree() && enterSubtree) {
1908 treeWalk.enterSubtree();
1909 }
1910 }
1911 return true;
1912 }
1913 }