1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jgit.merge;
15
16 import static java.nio.charset.StandardCharsets.UTF_8;
17 import static java.time.Instant.EPOCH;
18 import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM;
19 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
20 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM;
21 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.time.Instant;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import org.eclipse.jgit.annotations.NonNull;
33 import org.eclipse.jgit.attributes.Attributes;
34 import org.eclipse.jgit.diff.DiffAlgorithm;
35 import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
36 import org.eclipse.jgit.diff.RawText;
37 import org.eclipse.jgit.diff.RawTextComparator;
38 import org.eclipse.jgit.diff.Sequence;
39 import org.eclipse.jgit.dircache.DirCache;
40 import org.eclipse.jgit.dircache.DirCacheBuildIterator;
41 import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
42 import org.eclipse.jgit.dircache.DirCacheEntry;
43 import org.eclipse.jgit.errors.BinaryBlobException;
44 import org.eclipse.jgit.lib.Config;
45 import org.eclipse.jgit.lib.Constants;
46 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
47 import org.eclipse.jgit.lib.FileMode;
48 import org.eclipse.jgit.lib.ObjectId;
49 import org.eclipse.jgit.lib.ObjectInserter;
50 import org.eclipse.jgit.lib.ObjectLoader;
51 import org.eclipse.jgit.lib.Repository;
52 import org.eclipse.jgit.revwalk.RevTree;
53 import org.eclipse.jgit.storage.pack.PackConfig;
54 import org.eclipse.jgit.submodule.SubmoduleConflict;
55 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
56 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
57 import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
58 import org.eclipse.jgit.treewalk.TreeWalk;
59 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
60 import org.eclipse.jgit.treewalk.filter.TreeFilter;
61 import org.eclipse.jgit.util.FS;
62 import org.eclipse.jgit.util.LfsFactory;
63 import org.eclipse.jgit.util.TemporaryBuffer;
64
65
66
67
68 public class ResolveMerger extends ThreeWayMerger {
69
70
71
72
73
74 public enum MergeFailureReason {
75
76 DIRTY_INDEX,
77
78 DIRTY_WORKTREE,
79
80 COULD_NOT_DELETE
81 }
82
83
84
85
86
87
88 protected NameConflictTreeWalk tw;
89
90
91
92
93
94
95 protected String[] commitNames;
96
97
98
99
100
101
102 protected static final int T_BASE = 0;
103
104
105
106
107
108
109 protected static final int T_OURS = 1;
110
111
112
113
114
115
116 protected static final int T_THEIRS = 2;
117
118
119
120
121
122
123 protected static final int T_INDEX = 3;
124
125
126
127
128
129
130 protected static final int T_FILE = 4;
131
132
133
134
135
136
137 protected WorkTreeUpdater workTreeUpdater;
138
139
140
141
142
143
144 protected ObjectId resultTree;
145
146
147
148
149 protected List<String> modifiedFiles = new ArrayList<>();
150
151
152
153
154
155
156
157 protected List<String> unmergedPaths = new ArrayList<>();
158
159
160
161
162
163
164
165 protected Map<String, MergeResult<? extends Sequence>> mergeResults = new HashMap<>();
166
167
168
169
170
171
172 protected Map<String, MergeFailureReason> failingPaths = new HashMap<>();
173
174
175
176
177
178
179
180 protected boolean enterSubtree;
181
182
183
184
185
186
187
188
189
190 protected boolean inCore;
191
192
193
194
195
196 protected DirCache dircache;
197
198
199
200
201
202
203 protected WorkingTreeIterator workingTreeIterator;
204
205
206
207
208
209 protected MergeAlgorithm mergeAlgorithm;
210
211
212
213
214
215 @NonNull
216 private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
217
218 private static MergeAlgorithm getMergeAlgorithm(Config config) {
219 SupportedAlgorithm diffAlg = config.getEnum(
220 CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
221 HISTOGRAM);
222 return new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
223 }
224
225 private static String[] defaultCommitNames() {
226 return new String[]{"BASE", "OURS", "THEIRS"};
227 }
228
229 private static final Attributes NO_ATTRIBUTES = new Attributes();
230
231
232
233
234
235
236
237
238
239 protected ResolveMerger(Repository local, boolean inCore) {
240 super(local);
241 Config config = local.getConfig();
242 mergeAlgorithm = getMergeAlgorithm(config);
243 commitNames = defaultCommitNames();
244 this.inCore = inCore;
245 }
246
247
248
249
250
251
252
253 protected ResolveMerger(Repository local) {
254 this(local, false);
255 }
256
257
258
259
260
261
262
263
264
265
266 protected ResolveMerger(ObjectInserter inserter, Config config) {
267 super(inserter);
268 mergeAlgorithm = getMergeAlgorithm(config);
269 commitNames = defaultCommitNames();
270 inCore = true;
271 }
272
273
274
275
276
277
278
279 @NonNull
280 public ContentMergeStrategy getContentMergeStrategy() {
281 return contentStrategy;
282 }
283
284
285
286
287
288
289
290
291 public void setContentMergeStrategy(ContentMergeStrategy strategy) {
292 contentStrategy = strategy == null ? ContentMergeStrategy.CONFLICT
293 : strategy;
294 }
295
296
297 @Override
298 protected boolean mergeImpl() throws IOException {
299 return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
300 false);
301 }
302
303
304
305
306
307
308
309
310
311
312
313 private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
314 Instant lastMod, long len) {
315 if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
316 return workTreeUpdater.addExistingToIndex(p.getEntryObjectId(), path,
317 p.getEntryFileMode(), stage,
318 lastMod, (int) len);
319 }
320 return null;
321 }
322
323
324
325
326
327
328
329
330
331
332
333
334
335 private DirCacheEntry addConflict(CanonicalTreeParser base,
336 CanonicalTreeParser ours, CanonicalTreeParser theirs) {
337 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
338 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
339 return add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
340 }
341
342
343
344
345
346
347
348
349
350
351 private DirCacheEntry keep(DirCacheEntry e) {
352 return workTreeUpdater.addExistingToIndex(e.getObjectId(), e.getRawPath(), e.getFileMode(),
353 e.getStage(), e.getLastModifiedInstant(), e.getLength());
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370 protected void addToCheckout(String path, DirCacheEntry entry,
371 Attributes[] attributes)
372 throws IOException {
373 EolStreamType cleanupStreamType = workTreeUpdater.detectCheckoutStreamType(attributes[T_OURS]);
374 String cleanupSmudgeCommand = tw.getSmudgeCommand(attributes[T_OURS]);
375 EolStreamType checkoutStreamType = workTreeUpdater.detectCheckoutStreamType(attributes[T_THEIRS]);
376 String checkoutSmudgeCommand = tw.getSmudgeCommand(attributes[T_THEIRS]);
377 workTreeUpdater.addToCheckout(path, entry, cleanupStreamType, cleanupSmudgeCommand,
378 checkoutStreamType, checkoutSmudgeCommand);
379 }
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 protected void addDeletion(String path, boolean isFile,
396 Attributes attributes) throws IOException {
397 if (db == null || nonNullRepo().isBare() || !isFile)
398 return;
399
400 File file = new File(nonNullRepo().getWorkTree(), path);
401 EolStreamType streamType = workTreeUpdater.detectCheckoutStreamType(attributes);
402 String smudgeCommand = tw.getSmudgeCommand(attributes);
403 workTreeUpdater.deleteFile(path, file, streamType, smudgeCommand);
404 }
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450 protected boolean processEntry(CanonicalTreeParser base,
451 CanonicalTreeParser ours, CanonicalTreeParser theirs,
452 DirCacheBuildIterator index, WorkingTreeIterator work,
453 boolean ignoreConflicts, Attributes[] attributes)
454 throws IOException {
455 enterSubtree = true;
456 final int modeO = tw.getRawMode(T_OURS);
457 final int modeT = tw.getRawMode(T_THEIRS);
458 final int modeB = tw.getRawMode(T_BASE);
459 boolean gitLinkMerging = isGitLink(modeO) || isGitLink(modeT)
460 || isGitLink(modeB);
461 if (modeO == 0 && modeT == 0 && modeB == 0) {
462
463 return true;
464 }
465
466 if (isIndexDirty()) {
467 return false;
468 }
469
470 DirCacheEntry ourDce = null;
471
472 if (index == null || index.getDirCacheEntry() == null) {
473
474
475 if (nonTree(modeO)) {
476 ourDce = new DirCacheEntry(tw.getRawPath());
477 ourDce.setObjectId(tw.getObjectId(T_OURS));
478 ourDce.setFileMode(tw.getFileMode(T_OURS));
479 }
480 } else {
481 ourDce = index.getDirCacheEntry();
482 }
483
484 if (nonTree(modeO) && nonTree(modeT) && tw.idEqual(T_OURS, T_THEIRS)) {
485
486 if (modeO == modeT) {
487
488
489
490 keep(ourDce);
491
492 return true;
493 }
494
495
496
497 int newMode = mergeFileModes(modeB, modeO, modeT);
498 if (newMode != FileMode.MISSING.getBits()) {
499 if (newMode == modeO) {
500
501 keep(ourDce);
502 } else {
503
504
505 if (isWorktreeDirty(work, ourDce)) {
506 return false;
507 }
508
509
510
511 DirCacheEntry e = add(tw.getRawPath(), theirs,
512 DirCacheEntry.STAGE_0, EPOCH, 0);
513 addToCheckout(tw.getPathString(), e, attributes);
514 }
515 return true;
516 }
517 if (!ignoreConflicts) {
518
519
520
521
522
523 addConflict(base, ours, theirs);
524 unmergedPaths.add(tw.getPathString());
525 mergeResults.put(tw.getPathString(),
526 new MergeResult<>(Collections.emptyList()));
527 }
528 return true;
529 }
530
531 if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
532
533
534 if (ourDce != null) {
535 keep(ourDce);
536 }
537
538 return true;
539 }
540
541 if (modeB == modeO && tw.idEqual(T_BASE, T_OURS)) {
542
543
544
545
546 if (isWorktreeDirty(work, ourDce)) {
547 return false;
548 }
549 if (nonTree(modeT)) {
550
551
552
553 DirCacheEntry e = add(tw.getRawPath(), theirs,
554 DirCacheEntry.STAGE_0, EPOCH, 0);
555 if (e != null) {
556 addToCheckout(tw.getPathString(), e, attributes);
557 }
558 return true;
559 }
560
561
562
563 if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) {
564
565 return true;
566 }
567 if (modeT != 0 && modeT == modeB) {
568
569 return true;
570 }
571 addDeletion(tw.getPathString(), nonTree(modeO), attributes[T_OURS]);
572 return true;
573 }
574
575 if (tw.isSubtree()) {
576
577
578
579
580 if (nonTree(modeO) != nonTree(modeT)) {
581 if (ignoreConflicts) {
582
583
584
585
586 enterSubtree = false;
587 return true;
588 }
589 if (nonTree(modeB)) {
590 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
591 }
592 if (nonTree(modeO)) {
593 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
594 }
595 if (nonTree(modeT)) {
596 add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
597 }
598 unmergedPaths.add(tw.getPathString());
599 enterSubtree = false;
600 return true;
601 }
602
603
604
605
606
607 if (!nonTree(modeO)) {
608 return true;
609 }
610
611
612
613 }
614
615 if (nonTree(modeO) && nonTree(modeT)) {
616
617 boolean worktreeDirty = isWorktreeDirty(work, ourDce);
618 if (!attributes[T_OURS].canBeContentMerged() && worktreeDirty) {
619 return false;
620 }
621
622 if (gitLinkMerging && ignoreConflicts) {
623
624
625 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
626 return true;
627 } else if (gitLinkMerging) {
628 addConflict(base, ours, theirs);
629 MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
630 base, ours, theirs);
631 result.setContainsConflicts(true);
632 mergeResults.put(tw.getPathString(), result);
633 unmergedPaths.add(tw.getPathString());
634 return true;
635 } else if (!attributes[T_OURS].canBeContentMerged()) {
636
637 switch (getContentMergeStrategy()) {
638 case OURS:
639 keep(ourDce);
640 return true;
641 case THEIRS:
642 DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
643 DirCacheEntry.STAGE_0, EPOCH, 0);
644 addToCheckout(tw.getPathString(), theirEntry, attributes);
645 return true;
646 default:
647 break;
648 }
649 addConflict(base, ours, theirs);
650
651
652 unmergedPaths.add(tw.getPathString());
653 return true;
654 }
655
656
657 if (worktreeDirty) {
658 return false;
659 }
660
661 MergeResult<RawText> result = null;
662 boolean hasSymlink = FileMode.SYMLINK.equals(modeO)
663 || FileMode.SYMLINK.equals(modeT);
664 if (!hasSymlink) {
665 try {
666 result = contentMerge(base, ours, theirs, attributes,
667 getContentMergeStrategy());
668 } catch (BinaryBlobException e) {
669
670 }
671 }
672 if (result == null) {
673 switch (getContentMergeStrategy()) {
674 case OURS:
675 keep(ourDce);
676 return true;
677 case THEIRS:
678 DirCacheEntry e = add(tw.getRawPath(), theirs,
679 DirCacheEntry.STAGE_0, EPOCH, 0);
680 if (e != null) {
681 addToCheckout(tw.getPathString(), e, attributes);
682 }
683 return true;
684 default:
685 result = new MergeResult<>(Collections.emptyList());
686 result.setContainsConflicts(true);
687 break;
688 }
689 }
690 if (ignoreConflicts) {
691 result.setContainsConflicts(false);
692 }
693 String currentPath = tw.getPathString();
694 if (hasSymlink) {
695 if (ignoreConflicts) {
696 if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)) {
697 DirCacheEntry e = add(tw.getRawPath(), theirs,
698 DirCacheEntry.STAGE_0, EPOCH, 0);
699 addToCheckout(currentPath, e, attributes);
700 } else {
701 keep(ourDce);
702 }
703 } else {
704
705 DirCacheEntry e = addConflict(base, ours, theirs);
706 mergeResults.put(currentPath, result);
707
708
709 if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)
710 && e != null) {
711 addToCheckout(currentPath, e, attributes);
712 }
713 }
714 } else {
715 updateIndex(base, ours, theirs, result, attributes[T_OURS]);
716 }
717 if (result.containsConflicts() && !ignoreConflicts) {
718 unmergedPaths.add(currentPath);
719 }
720 workTreeUpdater.markAsModified(currentPath);
721
722 addToCheckout(currentPath, null, attributes);
723 } else if (modeO != modeT) {
724
725 if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
726 .idEqual(T_BASE, T_THEIRS)))) {
727 if (gitLinkMerging && ignoreConflicts) {
728 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
729 } else if (gitLinkMerging) {
730 addConflict(base, ours, theirs);
731 MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
732 base, ours, theirs);
733 result.setContainsConflicts(true);
734 mergeResults.put(tw.getPathString(), result);
735 unmergedPaths.add(tw.getPathString());
736 } else {
737 boolean isSymLink = ((modeO | modeT)
738 & FileMode.TYPE_MASK) == FileMode.TYPE_SYMLINK;
739
740
741 MergeResult<RawText> result;
742 if (isSymLink) {
743
744 result = new MergeResult<>(Collections.emptyList());
745 result.setContainsConflicts(true);
746 } else {
747 try {
748 result = contentMerge(base, ours, theirs,
749 attributes, ContentMergeStrategy.CONFLICT);
750 } catch (BinaryBlobException e) {
751 result = new MergeResult<>(Collections.emptyList());
752 result.setContainsConflicts(true);
753 }
754 }
755 if (ignoreConflicts) {
756 result.setContainsConflicts(false);
757 if (isSymLink) {
758 if (modeO != 0) {
759 keep(ourDce);
760 } else {
761
762 if (isWorktreeDirty(work, ourDce)) {
763 return false;
764 }
765 DirCacheEntry e = add(tw.getRawPath(), theirs,
766 DirCacheEntry.STAGE_0, EPOCH, 0);
767 if (e != null) {
768 addToCheckout(tw.getPathString(), e,
769 attributes);
770 }
771 }
772 } else {
773
774
775
776
777 updateIndex(base, ours, theirs, result,
778 attributes[T_OURS]);
779 }
780 } else {
781 DirCacheEntry e = addConflict(base, ours, theirs);
782
783
784 if (modeO == 0) {
785
786 if (isWorktreeDirty(work, ourDce)) {
787 return false;
788 }
789 if (nonTree(modeT) && e != null) {
790 addToCheckout(tw.getPathString(), e,
791 attributes);
792 }
793 }
794
795 unmergedPaths.add(tw.getPathString());
796
797
798 mergeResults.put(tw.getPathString(), result);
799 }
800 }
801 }
802 }
803 return true;
804 }
805
806 private static MergeResult<SubmoduleConflict> createGitLinksMergeResult(
807 CanonicalTreeParser base, CanonicalTreeParser ours,
808 CanonicalTreeParser theirs) {
809 return new MergeResult<>(Arrays.asList(
810 new SubmoduleConflict(
811 base == null ? null : base.getEntryObjectId()),
812 new SubmoduleConflict(
813 ours == null ? null : ours.getEntryObjectId()),
814 new SubmoduleConflict(
815 theirs == null ? null : theirs.getEntryObjectId())));
816 }
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834 private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
835 CanonicalTreeParser ours, CanonicalTreeParser theirs,
836 Attributes[] attributes, ContentMergeStrategy strategy)
837 throws BinaryBlobException, IOException {
838
839
840 RawText baseText = base == null ? RawText.EMPTY_TEXT
841 : getRawText(base.getEntryObjectId(), attributes[T_BASE]);
842 RawText ourText = ours == null ? RawText.EMPTY_TEXT
843 : getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
844 RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
845 : getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
846 mergeAlgorithm.setContentMergeStrategy(strategy);
847 return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
848 ourText, theirsText);
849 }
850
851 private boolean isIndexDirty() {
852 if (inCore) {
853 return false;
854 }
855
856 final int modeI = tw.getRawMode(T_INDEX);
857 final int modeO = tw.getRawMode(T_OURS);
858
859
860 final boolean isDirty = nonTree(modeI)
861 && !(modeO == modeI && tw.idEqual(T_INDEX, T_OURS));
862 if (isDirty) {
863 failingPaths
864 .put(tw.getPathString(), MergeFailureReason.DIRTY_INDEX);
865 }
866 return isDirty;
867 }
868
869 private boolean isWorktreeDirty(WorkingTreeIterator work,
870 DirCacheEntry ourDce) throws IOException {
871 if (work == null) {
872 return false;
873 }
874
875 final int modeF = tw.getRawMode(T_FILE);
876 final int modeO = tw.getRawMode(T_OURS);
877
878
879 boolean isDirty;
880 if (ourDce != null) {
881 isDirty = work.isModified(ourDce, true, reader);
882 } else {
883 isDirty = work.isModeDifferent(modeO);
884 if (!isDirty && nonTree(modeF)) {
885 isDirty = !tw.idEqual(T_FILE, T_OURS);
886 }
887 }
888
889
890 if (isDirty && modeF == FileMode.TYPE_TREE
891 && modeO == FileMode.TYPE_MISSING) {
892 isDirty = false;
893 }
894 if (isDirty) {
895 failingPaths.put(tw.getPathString(),
896 MergeFailureReason.DIRTY_WORKTREE);
897 }
898 return isDirty;
899 }
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914 private void updateIndex(CanonicalTreeParser base,
915 CanonicalTreeParser ours, CanonicalTreeParser theirs,
916 MergeResult<RawText> result, Attributes attributes)
917 throws IOException {
918 TemporaryBuffer rawMerged = null;
919 try {
920 rawMerged = doMerge(result);
921 File mergedFile = inCore ? null
922 : writeMergedFile(rawMerged, attributes);
923 if (result.containsConflicts()) {
924
925
926
927 addConflict(base, ours, theirs);
928 mergeResults.put(tw.getPathString(), result);
929 return;
930 }
931
932
933
934 Instant lastModified = mergedFile == null ? null
935 : nonNullRepo().getFS().lastModifiedInstant(mergedFile);
936
937
938 int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1),
939 tw.getRawMode(2));
940 FileMode mode = newMode == FileMode.MISSING.getBits()
941 ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode);
942 workTreeUpdater.insertToIndex(rawMerged.openInputStream(),
943 tw.getPathString().getBytes(UTF_8), mode,
944 DirCacheEntry.STAGE_0, lastModified,
945 (int) rawMerged.length(),
946 attributes.get(Constants.ATTR_MERGE));
947 } finally {
948 if (rawMerged != null) {
949 rawMerged.destroy();
950 }
951 }
952 }
953
954
955
956
957
958
959
960
961
962
963
964 private File writeMergedFile(TemporaryBuffer rawMerged,
965 Attributes attributes)
966 throws IOException {
967 File workTree = nonNullRepo().getWorkTree();
968 FS fs = nonNullRepo().getFS();
969 File of = new File(workTree, tw.getPathString());
970 File parentFolder = of.getParentFile();
971 EolStreamType eol = workTreeUpdater.detectCheckoutStreamType(attributes);
972 if (!fs.exists(parentFolder)) {
973 parentFolder.mkdirs();
974 }
975 workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
976 eol, tw.getSmudgeCommand(attributes), of.getPath(), of);
977 return of;
978 }
979
980 private TemporaryBuffer doMerge(MergeResult<RawText> result)
981 throws IOException {
982 TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(
983 db != null ? nonNullRepo().getDirectory() : null, workTreeUpdater.getInCoreFileSizeLimit());
984 boolean success = false;
985 try {
986 new MergeFormatter().formatMerge(buf, result,
987 Arrays.asList(commitNames), UTF_8);
988 buf.close();
989 success = true;
990 } finally {
991 if (!success) {
992 buf.destroy();
993 }
994 }
995 return buf;
996 }
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014 private int mergeFileModes(int modeB, int modeO, int modeT) {
1015 if (modeO == modeT) {
1016 return modeO;
1017 }
1018 if (modeB == modeO) {
1019
1020 return (modeT == FileMode.MISSING.getBits()) ? modeO : modeT;
1021 }
1022 if (modeB == modeT) {
1023
1024 return (modeO == FileMode.MISSING.getBits()) ? modeT : modeO;
1025 }
1026 return FileMode.MISSING.getBits();
1027 }
1028
1029 private RawText getRawText(ObjectId id,
1030 Attributes attributes)
1031 throws IOException, BinaryBlobException {
1032 if (id.equals(ObjectId.zeroId())) {
1033 return new RawText(new byte[]{});
1034 }
1035
1036 ObjectLoader loader = LfsFactory.getInstance().applySmudgeFilter(
1037 getRepository(), reader.open(id, OBJ_BLOB),
1038 attributes.get(Constants.ATTR_MERGE));
1039 int threshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
1040 return RawText.load(loader, threshold);
1041 }
1042
1043 private static boolean nonTree(int mode) {
1044 return mode != 0 && !FileMode.TREE.equals(mode);
1045 }
1046
1047 private static boolean isGitLink(int mode) {
1048 return FileMode.GITLINK.equals(mode);
1049 }
1050
1051
1052 @Override
1053 public ObjectId getResultTreeId() {
1054 return (resultTree == null) ? null : resultTree.toObjectId();
1055 }
1056
1057
1058
1059
1060
1061
1062
1063
1064 public void setCommitNames(String[] commitNames) {
1065 this.commitNames = commitNames;
1066 }
1067
1068
1069
1070
1071
1072
1073
1074 public String[] getCommitNames() {
1075 return commitNames;
1076 }
1077
1078
1079
1080
1081
1082
1083
1084
1085 public List<String> getUnmergedPaths() {
1086 return unmergedPaths;
1087 }
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097 public List<String> getModifiedFiles() {
1098 return workTreeUpdater != null ? workTreeUpdater.getModifiedFiles() : modifiedFiles;
1099 }
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111 public Map<String, DirCacheEntry> getToBeCheckedOut() {
1112 return workTreeUpdater.getToBeCheckedOut();
1113 }
1114
1115
1116
1117
1118
1119
1120 public Map<String, MergeResult<? extends Sequence>> getMergeResults() {
1121 return mergeResults;
1122 }
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132 public Map<String, MergeFailureReason> getFailingPaths() {
1133 return failingPaths.isEmpty() ? null : failingPaths;
1134 }
1135
1136
1137
1138
1139
1140
1141
1142
1143 public boolean failed() {
1144 return !failingPaths.isEmpty();
1145 }
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160 public void setDirCache(DirCache dc) {
1161 this.dircache = dc;
1162 }
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175 public void setWorkingTreeIterator(WorkingTreeIterator workingTreeIterator) {
1176 this.workingTreeIterator = workingTreeIterator;
1177 }
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213 protected boolean mergeTrees(AbstractTreeIterator baseTree,
1214 RevTree headTree, RevTree mergeTree, boolean ignoreConflicts)
1215 throws IOException {
1216 try {
1217 workTreeUpdater = inCore ?
1218 WorkTreeUpdater.createInCoreWorkTreeUpdater(db, dircache, getObjectInserter()) :
1219 WorkTreeUpdater.createWorkTreeUpdater(db, dircache);
1220 dircache = workTreeUpdater.getLockedDirCache();
1221 tw = new NameConflictTreeWalk(db, reader);
1222
1223 tw.addTree(baseTree);
1224 tw.setHead(tw.addTree(headTree));
1225 tw.addTree(mergeTree);
1226 DirCacheBuildIterator buildIt = workTreeUpdater.createDirCacheBuildIterator();
1227 int dciPos = tw.addTree(buildIt);
1228 if (workingTreeIterator != null) {
1229 tw.addTree(workingTreeIterator);
1230 workingTreeIterator.setDirCacheIterator(tw, dciPos);
1231 } else {
1232 tw.setFilter(TreeFilter.ANY_DIFF);
1233 }
1234
1235 if (!mergeTreeWalk(tw, ignoreConflicts)) {
1236 return false;
1237 }
1238
1239 workTreeUpdater.writeWorkTreeChanges(true);
1240 if (getUnmergedPaths().isEmpty() && !failed()) {
1241 WorkTreeUpdater.Result result = workTreeUpdater.writeIndexChanges();
1242 resultTree = result.getTreeId();
1243 modifiedFiles = result.getModifiedFiles();
1244 for (String f : result.getFailedToDelete()) {
1245 failingPaths.put(f, MergeFailureReason.COULD_NOT_DELETE);
1246 }
1247 return result.getFailedToDelete().isEmpty();
1248 }
1249 resultTree = null;
1250 return false;
1251 } finally {
1252 if(modifiedFiles.isEmpty()) {
1253 modifiedFiles = workTreeUpdater.getModifiedFiles();
1254 }
1255 workTreeUpdater.close();
1256 workTreeUpdater = null;
1257 }
1258 }
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272 protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
1273 throws IOException {
1274 boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
1275 boolean hasAttributeNodeProvider = treeWalk
1276 .getAttributesNodeProvider() != null;
1277 while (treeWalk.next()) {
1278 Attributes[] attributes = {NO_ATTRIBUTES, NO_ATTRIBUTES,
1279 NO_ATTRIBUTES};
1280 if (hasAttributeNodeProvider) {
1281 attributes[T_BASE] = treeWalk.getAttributes(T_BASE);
1282 attributes[T_OURS] = treeWalk.getAttributes(T_OURS);
1283 attributes[T_THEIRS] = treeWalk.getAttributes(T_THEIRS);
1284 }
1285 if (!processEntry(
1286 treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
1287 treeWalk.getTree(T_OURS, CanonicalTreeParser.class),
1288 treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
1289 treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
1290 hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
1291 WorkingTreeIterator.class) : null,
1292 ignoreConflicts, attributes)) {
1293 workTreeUpdater.revertModifiedFiles();
1294 return false;
1295 }
1296 if (treeWalk.isSubtree() && enterSubtree) {
1297 treeWalk.enterSubtree();
1298 }
1299 }
1300 return true;
1301 }
1302 }