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 (nonTree(modeB))
708 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
709 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
710 unmergedPaths.add(tw.getPathString());
711 enterSubtree = false;
712 return true;
713 }
714 if (nonTree(modeT) && !nonTree(modeO)) {
715 if (nonTree(modeB))
716 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
717 add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
718 unmergedPaths.add(tw.getPathString());
719 enterSubtree = false;
720 return true;
721 }
722
723
724
725
726
727 if (!nonTree(modeO))
728 return true;
729
730
731
732 }
733
734 if (nonTree(modeO) && nonTree(modeT)) {
735
736 boolean worktreeDirty = isWorktreeDirty(work, ourDce);
737 if (!attributes.canBeContentMerged() && worktreeDirty) {
738 return false;
739 }
740
741 if (gitLinkMerging && ignoreConflicts) {
742
743
744 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
745 return true;
746 } else if (gitLinkMerging) {
747 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
748 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
749 add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
750 MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
751 base, ours, theirs);
752 result.setContainsConflicts(true);
753 mergeResults.put(tw.getPathString(), result);
754 unmergedPaths.add(tw.getPathString());
755 return true;
756 } else if (!attributes.canBeContentMerged()) {
757 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
758 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
759 add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
760
761
762 unmergedPaths.add(tw.getPathString());
763 return true;
764 }
765
766
767 if (worktreeDirty) {
768 return false;
769 }
770
771 MergeResult<RawText> result = contentMerge(base, ours, theirs,
772 attributes);
773 if (ignoreConflicts) {
774 result.setContainsConflicts(false);
775 }
776 updateIndex(base, ours, theirs, result, attributes);
777 String currentPath = tw.getPathString();
778 if (result.containsConflicts() && !ignoreConflicts) {
779 unmergedPaths.add(currentPath);
780 }
781 modifiedFiles.add(currentPath);
782 addCheckoutMetadata(currentPath, attributes);
783 } else if (modeO != modeT) {
784
785 if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
786 .idEqual(T_BASE, T_THEIRS)))) {
787 if (gitLinkMerging && ignoreConflicts) {
788 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
789 } else if (gitLinkMerging) {
790 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
791 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
792 add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
793 MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
794 base, ours, theirs);
795 result.setContainsConflicts(true);
796 mergeResults.put(tw.getPathString(), result);
797 unmergedPaths.add(tw.getPathString());
798 } else {
799 MergeResult<RawText> result = contentMerge(base, ours,
800 theirs, attributes);
801
802 if (ignoreConflicts) {
803
804
805
806
807 result.setContainsConflicts(false);
808 updateIndex(base, ours, theirs, result, attributes);
809 } else {
810 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
811 0);
812 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH,
813 0);
814 DirCacheEntry e = add(tw.getRawPath(), theirs,
815 DirCacheEntry.STAGE_3, EPOCH, 0);
816
817
818 if (modeO == 0) {
819
820 if (isWorktreeDirty(work, ourDce)) {
821 return false;
822 }
823 if (nonTree(modeT)) {
824 if (e != null) {
825 addToCheckout(tw.getPathString(), e,
826 attributes);
827 }
828 }
829 }
830
831 unmergedPaths.add(tw.getPathString());
832
833
834 mergeResults.put(tw.getPathString(), result);
835 }
836 }
837 }
838 }
839 return true;
840 }
841
842 private static MergeResult<SubmoduleConflict> createGitLinksMergeResult(
843 CanonicalTreeParser base, CanonicalTreeParser ours,
844 CanonicalTreeParser theirs) {
845 return new MergeResult<>(Arrays.asList(
846 new SubmoduleConflict(
847 base == null ? null : base.getEntryObjectId()),
848 new SubmoduleConflict(
849 ours == null ? null : ours.getEntryObjectId()),
850 new SubmoduleConflict(
851 theirs == null ? null : theirs.getEntryObjectId())));
852 }
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867 private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
868 CanonicalTreeParser ours, CanonicalTreeParser theirs,
869 Attributes attributes)
870 throws IOException {
871 RawText baseText;
872 RawText ourText;
873 RawText theirsText;
874
875 try {
876 baseText = base == null ? RawText.EMPTY_TEXT : getRawText(
877 base.getEntryObjectId(), attributes);
878 ourText = ours == null ? RawText.EMPTY_TEXT : getRawText(
879 ours.getEntryObjectId(), attributes);
880 theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText(
881 theirs.getEntryObjectId(), attributes);
882 } catch (BinaryBlobException e) {
883 MergeResult<RawText> r = new MergeResult<>(Collections.<RawText>emptyList());
884 r.setContainsConflicts(true);
885 return r;
886 }
887 return (mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
888 ourText, theirsText));
889 }
890
891 private boolean isIndexDirty() {
892 if (inCore)
893 return false;
894
895 final int modeI = tw.getRawMode(T_INDEX);
896 final int modeO = tw.getRawMode(T_OURS);
897
898
899 final boolean isDirty = nonTree(modeI)
900 && !(modeO == modeI && tw.idEqual(T_INDEX, T_OURS));
901 if (isDirty)
902 failingPaths
903 .put(tw.getPathString(), MergeFailureReason.DIRTY_INDEX);
904 return isDirty;
905 }
906
907 private boolean isWorktreeDirty(WorkingTreeIterator work,
908 DirCacheEntry ourDce) throws IOException {
909 if (work == null)
910 return false;
911
912 final int modeF = tw.getRawMode(T_FILE);
913 final int modeO = tw.getRawMode(T_OURS);
914
915
916 boolean isDirty;
917 if (ourDce != null)
918 isDirty = work.isModified(ourDce, true, reader);
919 else {
920 isDirty = work.isModeDifferent(modeO);
921 if (!isDirty && nonTree(modeF))
922 isDirty = !tw.idEqual(T_FILE, T_OURS);
923 }
924
925
926 if (isDirty && modeF == FileMode.TYPE_TREE
927 && modeO == FileMode.TYPE_MISSING)
928 isDirty = false;
929 if (isDirty)
930 failingPaths.put(tw.getPathString(),
931 MergeFailureReason.DIRTY_WORKTREE);
932 return isDirty;
933 }
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949 private void updateIndex(CanonicalTreeParser base,
950 CanonicalTreeParser ours, CanonicalTreeParser theirs,
951 MergeResult<RawText> result, Attributes attributes)
952 throws FileNotFoundException,
953 IOException {
954 TemporaryBuffer rawMerged = null;
955 try {
956 rawMerged = doMerge(result);
957 File mergedFile = inCore ? null
958 : writeMergedFile(rawMerged, attributes);
959 if (result.containsConflicts()) {
960
961
962
963 add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
964 add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
965 add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
966 mergeResults.put(tw.getPathString(), result);
967 return;
968 }
969
970
971
972 DirCacheEntry dce = new DirCacheEntry(tw.getPathString());
973
974
975
976 int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1),
977 tw.getRawMode(2));
978 dce.setFileMode(newMode == FileMode.MISSING.getBits()
979 ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode));
980 if (mergedFile != null) {
981 dce.setLastModified(
982 nonNullRepo().getFS().lastModifiedInstant(mergedFile));
983 dce.setLength((int) mergedFile.length());
984 }
985 dce.setObjectId(insertMergeResult(rawMerged, attributes));
986 builder.add(dce);
987 } finally {
988 if (rawMerged != null) {
989 rawMerged.destroy();
990 }
991 }
992 }
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005 private File writeMergedFile(TemporaryBuffer rawMerged,
1006 Attributes attributes)
1007 throws FileNotFoundException, IOException {
1008 File workTree = nonNullRepo().getWorkTree();
1009 FS fs = nonNullRepo().getFS();
1010 File of = new File(workTree, tw.getPathString());
1011 File parentFolder = of.getParentFile();
1012 if (!fs.exists(parentFolder)) {
1013 parentFolder.mkdirs();
1014 }
1015 EolStreamType streamType = EolStreamTypeUtil.detectStreamType(
1016 OperationType.CHECKOUT_OP, workingTreeOptions,
1017 attributes);
1018 try (OutputStream os = EolStreamTypeUtil.wrapOutputStream(
1019 new BufferedOutputStream(new FileOutputStream(of)),
1020 streamType)) {
1021 rawMerged.writeTo(os, null);
1022 }
1023 return of;
1024 }
1025
1026 private TemporaryBuffer doMerge(MergeResult<RawText> result)
1027 throws IOException {
1028 TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(
1029 db != null ? nonNullRepo().getDirectory() : null, inCoreLimit);
1030 boolean success = false;
1031 try {
1032 new MergeFormatter().formatMerge(buf, result,
1033 Arrays.asList(commitNames), UTF_8);
1034 buf.close();
1035 success = true;
1036 } finally {
1037 if (!success) {
1038 buf.destroy();
1039 }
1040 }
1041 return buf;
1042 }
1043
1044 private ObjectId insertMergeResult(TemporaryBuffer buf,
1045 Attributes attributes) throws IOException {
1046 InputStream in = buf.openInputStream();
1047 try (LfsInputStream is = LfsFactory.getInstance().applyCleanFilter(
1048 getRepository(), in,
1049 buf.length(), attributes.get(Constants.ATTR_MERGE))) {
1050 return getObjectInserter().insert(OBJ_BLOB, is.getLength(), is);
1051 }
1052 }
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070 private int mergeFileModes(int modeB, int modeO, int modeT) {
1071 if (modeO == modeT)
1072 return modeO;
1073 if (modeB == modeO)
1074
1075 return (modeT == FileMode.MISSING.getBits()) ? modeO : modeT;
1076 if (modeB == modeT)
1077
1078 return (modeO == FileMode.MISSING.getBits()) ? modeT : modeO;
1079 return FileMode.MISSING.getBits();
1080 }
1081
1082 private RawText getRawText(ObjectId id,
1083 Attributes attributes)
1084 throws IOException, BinaryBlobException {
1085 if (id.equals(ObjectId.zeroId()))
1086 return new RawText(new byte[] {});
1087
1088 ObjectLoader loader = LfsFactory.getInstance().applySmudgeFilter(
1089 getRepository(), reader.open(id, OBJ_BLOB),
1090 attributes.get(Constants.ATTR_MERGE));
1091 int threshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
1092 return RawText.load(loader, threshold);
1093 }
1094
1095 private static boolean nonTree(int mode) {
1096 return mode != 0 && !FileMode.TREE.equals(mode);
1097 }
1098
1099 private static boolean isGitLink(int mode) {
1100 return FileMode.GITLINK.equals(mode);
1101 }
1102
1103
1104 @Override
1105 public ObjectId getResultTreeId() {
1106 return (resultTree == null) ? null : resultTree.toObjectId();
1107 }
1108
1109
1110
1111
1112
1113
1114
1115
1116 public void setCommitNames(String[] commitNames) {
1117 this.commitNames = commitNames;
1118 }
1119
1120
1121
1122
1123
1124
1125
1126 public String[] getCommitNames() {
1127 return commitNames;
1128 }
1129
1130
1131
1132
1133
1134
1135
1136
1137 public List<String> getUnmergedPaths() {
1138 return unmergedPaths;
1139 }
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149 public List<String> getModifiedFiles() {
1150 return modifiedFiles;
1151 }
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163 public Map<String, DirCacheEntry> getToBeCheckedOut() {
1164 return toBeCheckedOut;
1165 }
1166
1167
1168
1169
1170
1171
1172 public Map<String, MergeResult<? extends Sequence>> getMergeResults() {
1173 return mergeResults;
1174 }
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184 public Map<String, MergeFailureReason> getFailingPaths() {
1185 return failingPaths.isEmpty() ? null : failingPaths;
1186 }
1187
1188
1189
1190
1191
1192
1193
1194
1195 public boolean failed() {
1196 return !failingPaths.isEmpty();
1197 }
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212 public void setDirCache(DirCache dc) {
1213 this.dircache = dc;
1214 implicitDirCache = false;
1215 }
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228 public void setWorkingTreeIterator(WorkingTreeIterator workingTreeIterator) {
1229 this.workingTreeIterator = workingTreeIterator;
1230 }
1231
1232
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 protected boolean mergeTrees(AbstractTreeIterator baseTree,
1267 RevTree headTree, RevTree mergeTree, boolean ignoreConflicts)
1268 throws IOException {
1269
1270 builder = dircache.builder();
1271 DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
1272
1273 tw = new NameConflictTreeWalk(db, reader);
1274 tw.addTree(baseTree);
1275 tw.addTree(headTree);
1276 tw.addTree(mergeTree);
1277 int dciPos = tw.addTree(buildIt);
1278 if (workingTreeIterator != null) {
1279 tw.addTree(workingTreeIterator);
1280 workingTreeIterator.setDirCacheIterator(tw, dciPos);
1281 } else {
1282 tw.setFilter(TreeFilter.ANY_DIFF);
1283 }
1284
1285 if (!mergeTreeWalk(tw, ignoreConflicts)) {
1286 return false;
1287 }
1288
1289 if (!inCore) {
1290
1291
1292
1293 checkout();
1294
1295
1296
1297
1298
1299 if (!builder.commit()) {
1300 cleanUp();
1301 throw new IndexWriteException();
1302 }
1303 builder = null;
1304
1305 } else {
1306 builder.finish();
1307 builder = null;
1308 }
1309
1310 if (getUnmergedPaths().isEmpty() && !failed()) {
1311 resultTree = dircache.writeTree(getObjectInserter());
1312 return true;
1313 }
1314 resultTree = null;
1315 return false;
1316 }
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330 protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
1331 throws IOException {
1332 boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
1333 boolean hasAttributeNodeProvider = treeWalk
1334 .getAttributesNodeProvider() != null;
1335 while (treeWalk.next()) {
1336 if (!processEntry(
1337 treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
1338 treeWalk.getTree(T_OURS, CanonicalTreeParser.class),
1339 treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
1340 treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
1341 hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
1342 WorkingTreeIterator.class) : null,
1343 ignoreConflicts, hasAttributeNodeProvider
1344 ? treeWalk.getAttributes()
1345 : NO_ATTRIBUTES)) {
1346 cleanUp();
1347 return false;
1348 }
1349 if (treeWalk.isSubtree() && enterSubtree)
1350 treeWalk.enterSubtree();
1351 }
1352 return true;
1353 }
1354 }