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