MergeAlgorithm.java

  1. /*
  2.  * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.merge;

  11. import java.util.ArrayList;
  12. import java.util.Iterator;
  13. import java.util.List;

  14. import org.eclipse.jgit.diff.DiffAlgorithm;
  15. import org.eclipse.jgit.diff.Edit;
  16. import org.eclipse.jgit.diff.EditList;
  17. import org.eclipse.jgit.diff.HistogramDiff;
  18. import org.eclipse.jgit.diff.Sequence;
  19. import org.eclipse.jgit.diff.SequenceComparator;
  20. import org.eclipse.jgit.merge.MergeChunk.ConflictState;

  21. /**
  22.  * Provides the merge algorithm which does a three-way merge on content provided
  23.  * as RawText. By default {@link org.eclipse.jgit.diff.HistogramDiff} is used as
  24.  * diff algorithm.
  25.  */
  26. public final class MergeAlgorithm {
  27.     private final DiffAlgorithm diffAlg;

  28.     /**
  29.      * Creates a new MergeAlgorithm which uses
  30.      * {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm
  31.      */
  32.     public MergeAlgorithm() {
  33.         this(new HistogramDiff());
  34.     }

  35.     /**
  36.      * Creates a new MergeAlgorithm
  37.      *
  38.      * @param diff
  39.      *            the diff algorithm used by this merge
  40.      */
  41.     public MergeAlgorithm(DiffAlgorithm diff) {
  42.         this.diffAlg = diff;
  43.     }

  44.     // An special edit which acts as a sentinel value by marking the end the
  45.     // list of edits
  46.     private static final Edit END_EDIT = new Edit(Integer.MAX_VALUE,
  47.             Integer.MAX_VALUE);

  48.     @SuppressWarnings("ReferenceEquality")
  49.     private static boolean isEndEdit(Edit edit) {
  50.         return edit == END_EDIT;
  51.     }

  52.     /**
  53.      * Does the three way merge between a common base and two sequences.
  54.      *
  55.      * @param cmp comparison method for this execution.
  56.      * @param base the common base sequence
  57.      * @param ours the first sequence to be merged
  58.      * @param theirs the second sequence to be merged
  59.      * @return the resulting content
  60.      */
  61.     public <S extends Sequence> MergeResult<S> merge(
  62.             SequenceComparator<S> cmp, S base, S ours, S theirs) {
  63.         List<S> sequences = new ArrayList<>(3);
  64.         sequences.add(base);
  65.         sequences.add(ours);
  66.         sequences.add(theirs);
  67.         MergeResult<S> result = new MergeResult<>(sequences);

  68.         if (ours.size() == 0) {
  69.             if (theirs.size() != 0) {
  70.                 EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
  71.                 if (!theirsEdits.isEmpty()) {
  72.                     // we deleted, they modified -> Let their complete content
  73.                     // conflict with empty text
  74.                     result.add(1, 0, 0, ConflictState.FIRST_CONFLICTING_RANGE);
  75.                     result.add(2, 0, theirs.size(),
  76.                             ConflictState.NEXT_CONFLICTING_RANGE);
  77.                 } else
  78.                     // we deleted, they didn't modify -> Let our deletion win
  79.                     result.add(1, 0, 0, ConflictState.NO_CONFLICT);
  80.             } else
  81.                 // we and they deleted -> return a single chunk of nothing
  82.                 result.add(1, 0, 0, ConflictState.NO_CONFLICT);
  83.             return result;
  84.         } else if (theirs.size() == 0) {
  85.             EditList oursEdits = diffAlg.diff(cmp, base, ours);
  86.             if (!oursEdits.isEmpty()) {
  87.                 // we modified, they deleted -> Let our complete content
  88.                 // conflict with empty text
  89.                 result.add(1, 0, ours.size(),
  90.                         ConflictState.FIRST_CONFLICTING_RANGE);
  91.                 result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE);
  92.             } else
  93.                 // they deleted, we didn't modify -> Let their deletion win
  94.                 result.add(2, 0, 0, ConflictState.NO_CONFLICT);
  95.             return result;
  96.         }

  97.         EditList oursEdits = diffAlg.diff(cmp, base, ours);
  98.         Iterator<Edit> baseToOurs = oursEdits.iterator();
  99.         EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
  100.         Iterator<Edit> baseToTheirs = theirsEdits.iterator();
  101.         int current = 0; // points to the next line (first line is 0) of base
  102.                          // which was not handled yet
  103.         Edit oursEdit = nextEdit(baseToOurs);
  104.         Edit theirsEdit = nextEdit(baseToTheirs);

  105.         // iterate over all edits from base to ours and from base to theirs
  106.         // leave the loop when there are no edits more for ours or for theirs
  107.         // (or both)
  108.         while (!isEndEdit(theirsEdit) || !isEndEdit(oursEdit)) {
  109.             if (oursEdit.getEndA() < theirsEdit.getBeginA()) {
  110.                 // something was changed in ours not overlapping with any change
  111.                 // from theirs. First add the common part in front of the edit
  112.                 // then the edit.
  113.                 if (current != oursEdit.getBeginA()) {
  114.                     result.add(0, current, oursEdit.getBeginA(),
  115.                             ConflictState.NO_CONFLICT);
  116.                 }
  117.                 result.add(1, oursEdit.getBeginB(), oursEdit.getEndB(),
  118.                         ConflictState.NO_CONFLICT);
  119.                 current = oursEdit.getEndA();
  120.                 oursEdit = nextEdit(baseToOurs);
  121.             } else if (theirsEdit.getEndA() < oursEdit.getBeginA()) {
  122.                 // something was changed in theirs not overlapping with any
  123.                 // from ours. First add the common part in front of the edit
  124.                 // then the edit.
  125.                 if (current != theirsEdit.getBeginA()) {
  126.                     result.add(0, current, theirsEdit.getBeginA(),
  127.                             ConflictState.NO_CONFLICT);
  128.                 }
  129.                 result.add(2, theirsEdit.getBeginB(), theirsEdit.getEndB(),
  130.                         ConflictState.NO_CONFLICT);
  131.                 current = theirsEdit.getEndA();
  132.                 theirsEdit = nextEdit(baseToTheirs);
  133.             } else {
  134.                 // here we found a real overlapping modification

  135.                 // if there is a common part in front of the conflict add it
  136.                 if (oursEdit.getBeginA() != current
  137.                         && theirsEdit.getBeginA() != current) {
  138.                     result.add(0, current, Math.min(oursEdit.getBeginA(),
  139.                             theirsEdit.getBeginA()), ConflictState.NO_CONFLICT);
  140.                 }

  141.                 // set some initial values for the ranges in A and B which we
  142.                 // want to handle
  143.                 int oursBeginB = oursEdit.getBeginB();
  144.                 int theirsBeginB = theirsEdit.getBeginB();
  145.                 // harmonize the start of the ranges in A and B
  146.                 if (oursEdit.getBeginA() < theirsEdit.getBeginA()) {
  147.                     theirsBeginB -= theirsEdit.getBeginA()
  148.                             - oursEdit.getBeginA();
  149.                 } else {
  150.                     oursBeginB -= oursEdit.getBeginA() - theirsEdit.getBeginA();
  151.                 }

  152.                 // combine edits:
  153.                 // Maybe an Edit on one side corresponds to multiple Edits on
  154.                 // the other side. Then we have to combine the Edits of the
  155.                 // other side - so in the end we can merge together two single
  156.                 // edits.
  157.                 //
  158.                 // It is important to notice that this combining will extend the
  159.                 // ranges of our conflict always downwards (towards the end of
  160.                 // the content). The starts of the conflicting ranges in ours
  161.                 // and theirs are not touched here.
  162.                 //
  163.                 // This combining is an iterative process: after we have
  164.                 // combined some edits we have to do the check again. The
  165.                 // combined edits could now correspond to multiple edits on the
  166.                 // other side.
  167.                 //
  168.                 // Example: when this combining algorithm works on the following
  169.                 // edits
  170.                 // oursEdits=((0-5,0-5),(6-8,6-8),(10-11,10-11)) and
  171.                 // theirsEdits=((0-1,0-1),(2-3,2-3),(5-7,5-7))
  172.                 // it will merge them into
  173.                 // oursEdits=((0-8,0-8),(10-11,10-11)) and
  174.                 // theirsEdits=((0-7,0-7))
  175.                 //
  176.                 // Since the only interesting thing to us is how in ours and
  177.                 // theirs the end of the conflicting range is changing we let
  178.                 // oursEdit and theirsEdit point to the last conflicting edit
  179.                 Edit nextOursEdit = nextEdit(baseToOurs);
  180.                 Edit nextTheirsEdit = nextEdit(baseToTheirs);
  181.                 for (;;) {
  182.                     if (oursEdit.getEndA() >= nextTheirsEdit.getBeginA()) {
  183.                         theirsEdit = nextTheirsEdit;
  184.                         nextTheirsEdit = nextEdit(baseToTheirs);
  185.                     } else if (theirsEdit.getEndA() >= nextOursEdit.getBeginA()) {
  186.                         oursEdit = nextOursEdit;
  187.                         nextOursEdit = nextEdit(baseToOurs);
  188.                     } else {
  189.                         break;
  190.                     }
  191.                 }

  192.                 // harmonize the end of the ranges in A and B
  193.                 int oursEndB = oursEdit.getEndB();
  194.                 int theirsEndB = theirsEdit.getEndB();
  195.                 if (oursEdit.getEndA() < theirsEdit.getEndA()) {
  196.                     oursEndB += theirsEdit.getEndA() - oursEdit.getEndA();
  197.                 } else {
  198.                     theirsEndB += oursEdit.getEndA() - theirsEdit.getEndA();
  199.                 }

  200.                 // A conflicting region is found. Strip off common lines in
  201.                 // in the beginning and the end of the conflicting region

  202.                 // Determine the minimum length of the conflicting areas in OURS
  203.                 // and THEIRS. Also determine how much bigger the conflicting
  204.                 // area in THEIRS is compared to OURS. All that is needed to
  205.                 // limit the search for common areas at the beginning or end
  206.                 // (the common areas cannot be bigger then the smaller
  207.                 // conflicting area. The delta is needed to know whether the
  208.                 // complete conflicting area is common in OURS and THEIRS.
  209.                 int minBSize = oursEndB - oursBeginB;
  210.                 int BSizeDelta = minBSize - (theirsEndB - theirsBeginB);
  211.                 if (BSizeDelta > 0)
  212.                     minBSize -= BSizeDelta;

  213.                 int commonPrefix = 0;
  214.                 while (commonPrefix < minBSize
  215.                         && cmp.equals(ours, oursBeginB + commonPrefix, theirs,
  216.                                 theirsBeginB + commonPrefix))
  217.                     commonPrefix++;
  218.                 minBSize -= commonPrefix;
  219.                 int commonSuffix = 0;
  220.                 while (commonSuffix < minBSize
  221.                         && cmp.equals(ours, oursEndB - commonSuffix - 1, theirs,
  222.                                 theirsEndB - commonSuffix - 1))
  223.                     commonSuffix++;
  224.                 minBSize -= commonSuffix;

  225.                 // Add the common lines at start of conflict
  226.                 if (commonPrefix > 0)
  227.                     result.add(1, oursBeginB, oursBeginB + commonPrefix,
  228.                             ConflictState.NO_CONFLICT);

  229.                 // Add the conflict (Only if there is a conflict left to report)
  230.                 if (minBSize > 0 || BSizeDelta != 0) {
  231.                     result.add(1, oursBeginB + commonPrefix, oursEndB
  232.                             - commonSuffix,
  233.                             ConflictState.FIRST_CONFLICTING_RANGE);
  234.                     result.add(2, theirsBeginB + commonPrefix, theirsEndB
  235.                             - commonSuffix,
  236.                             ConflictState.NEXT_CONFLICTING_RANGE);
  237.                 }

  238.                 // Add the common lines at end of conflict
  239.                 if (commonSuffix > 0)
  240.                     result.add(1, oursEndB - commonSuffix, oursEndB,
  241.                             ConflictState.NO_CONFLICT);

  242.                 current = Math.max(oursEdit.getEndA(), theirsEdit.getEndA());
  243.                 oursEdit = nextOursEdit;
  244.                 theirsEdit = nextTheirsEdit;
  245.             }
  246.         }
  247.         // maybe we have a common part behind the last edit: copy it to the
  248.         // result
  249.         if (current < base.size()) {
  250.             result.add(0, current, base.size(), ConflictState.NO_CONFLICT);
  251.         }
  252.         return result;
  253.     }

  254.     /**
  255.      * Helper method which returns the next Edit for an Iterator over Edits.
  256.      * When there are no more edits left this method will return the constant
  257.      * END_EDIT.
  258.      *
  259.      * @param it
  260.      *            the iterator for which the next edit should be returned
  261.      * @return the next edit from the iterator or END_EDIT if there no more
  262.      *         edits
  263.      */
  264.     private static Edit nextEdit(Iterator<Edit> it) {
  265.         return (it.hasNext() ? it.next() : END_EDIT);
  266.     }
  267. }