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