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