MergeAlgorithm.java
- /*
- * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- package org.eclipse.jgit.merge;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- import org.eclipse.jgit.diff.DiffAlgorithm;
- import org.eclipse.jgit.diff.Edit;
- import org.eclipse.jgit.diff.EditList;
- import org.eclipse.jgit.diff.HistogramDiff;
- import org.eclipse.jgit.diff.Sequence;
- import org.eclipse.jgit.diff.SequenceComparator;
- import org.eclipse.jgit.merge.MergeChunk.ConflictState;
- /**
- * Provides the merge algorithm which does a three-way merge on content provided
- * as RawText. By default {@link org.eclipse.jgit.diff.HistogramDiff} is used as
- * diff algorithm.
- */
- public final class MergeAlgorithm {
- private final DiffAlgorithm diffAlg;
- /**
- * Creates a new MergeAlgorithm which uses
- * {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm
- */
- public MergeAlgorithm() {
- this(new HistogramDiff());
- }
- /**
- * Creates a new MergeAlgorithm
- *
- * @param diff
- * the diff algorithm used by this merge
- */
- public MergeAlgorithm(DiffAlgorithm diff) {
- this.diffAlg = diff;
- }
- // An special edit which acts as a sentinel value by marking the end the
- // list of edits
- private final static Edit END_EDIT = new Edit(Integer.MAX_VALUE,
- Integer.MAX_VALUE);
- /**
- * Does the three way merge between a common base and two sequences.
- *
- * @param cmp comparison method for this execution.
- * @param base the common base sequence
- * @param ours the first sequence to be merged
- * @param theirs the second sequence to be merged
- * @return the resulting content
- */
- public <S extends Sequence> MergeResult<S> merge(
- SequenceComparator<S> cmp, S base, S ours, S theirs) {
- List<S> sequences = new ArrayList<>(3);
- sequences.add(base);
- sequences.add(ours);
- sequences.add(theirs);
- MergeResult<S> result = new MergeResult<>(sequences);
- if (ours.size() == 0) {
- if (theirs.size() != 0) {
- EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
- if (!theirsEdits.isEmpty()) {
- // we deleted, they modified -> Let their complete content
- // conflict with empty text
- result.add(1, 0, 0, ConflictState.FIRST_CONFLICTING_RANGE);
- result.add(2, 0, theirs.size(),
- ConflictState.NEXT_CONFLICTING_RANGE);
- } else
- // we deleted, they didn't modify -> Let our deletion win
- result.add(1, 0, 0, ConflictState.NO_CONFLICT);
- } else
- // we and they deleted -> return a single chunk of nothing
- result.add(1, 0, 0, ConflictState.NO_CONFLICT);
- return result;
- } else if (theirs.size() == 0) {
- EditList oursEdits = diffAlg.diff(cmp, base, ours);
- if (!oursEdits.isEmpty()) {
- // we modified, they deleted -> Let our complete content
- // conflict with empty text
- result.add(1, 0, ours.size(),
- ConflictState.FIRST_CONFLICTING_RANGE);
- result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE);
- } else
- // they deleted, we didn't modify -> Let their deletion win
- result.add(2, 0, 0, ConflictState.NO_CONFLICT);
- return result;
- }
- EditList oursEdits = diffAlg.diff(cmp, base, ours);
- Iterator<Edit> baseToOurs = oursEdits.iterator();
- EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
- Iterator<Edit> baseToTheirs = theirsEdits.iterator();
- int current = 0; // points to the next line (first line is 0) of base
- // which was not handled yet
- Edit oursEdit = nextEdit(baseToOurs);
- Edit theirsEdit = nextEdit(baseToTheirs);
- // iterate over all edits from base to ours and from base to theirs
- // leave the loop when there are no edits more for ours or for theirs
- // (or both)
- while (theirsEdit != END_EDIT || oursEdit != END_EDIT) {
- if (oursEdit.getEndA() < theirsEdit.getBeginA()) {
- // something was changed in ours not overlapping with any change
- // from theirs. First add the common part in front of the edit
- // then the edit.
- if (current != oursEdit.getBeginA()) {
- result.add(0, current, oursEdit.getBeginA(),
- ConflictState.NO_CONFLICT);
- }
- result.add(1, oursEdit.getBeginB(), oursEdit.getEndB(),
- ConflictState.NO_CONFLICT);
- current = oursEdit.getEndA();
- oursEdit = nextEdit(baseToOurs);
- } else if (theirsEdit.getEndA() < oursEdit.getBeginA()) {
- // something was changed in theirs not overlapping with any
- // from ours. First add the common part in front of the edit
- // then the edit.
- if (current != theirsEdit.getBeginA()) {
- result.add(0, current, theirsEdit.getBeginA(),
- ConflictState.NO_CONFLICT);
- }
- result.add(2, theirsEdit.getBeginB(), theirsEdit.getEndB(),
- ConflictState.NO_CONFLICT);
- current = theirsEdit.getEndA();
- theirsEdit = nextEdit(baseToTheirs);
- } else {
- // here we found a real overlapping modification
- // if there is a common part in front of the conflict add it
- if (oursEdit.getBeginA() != current
- && theirsEdit.getBeginA() != current) {
- result.add(0, current, Math.min(oursEdit.getBeginA(),
- theirsEdit.getBeginA()), ConflictState.NO_CONFLICT);
- }
- // set some initial values for the ranges in A and B which we
- // want to handle
- int oursBeginB = oursEdit.getBeginB();
- int theirsBeginB = theirsEdit.getBeginB();
- // harmonize the start of the ranges in A and B
- if (oursEdit.getBeginA() < theirsEdit.getBeginA()) {
- theirsBeginB -= theirsEdit.getBeginA()
- - oursEdit.getBeginA();
- } else {
- oursBeginB -= oursEdit.getBeginA() - theirsEdit.getBeginA();
- }
- // combine edits:
- // Maybe an Edit on one side corresponds to multiple Edits on
- // the other side. Then we have to combine the Edits of the
- // other side - so in the end we can merge together two single
- // edits.
- //
- // It is important to notice that this combining will extend the
- // ranges of our conflict always downwards (towards the end of
- // the content). The starts of the conflicting ranges in ours
- // and theirs are not touched here.
- //
- // This combining is an iterative process: after we have
- // combined some edits we have to do the check again. The
- // combined edits could now correspond to multiple edits on the
- // other side.
- //
- // Example: when this combining algorithm works on the following
- // edits
- // oursEdits=((0-5,0-5),(6-8,6-8),(10-11,10-11)) and
- // theirsEdits=((0-1,0-1),(2-3,2-3),(5-7,5-7))
- // it will merge them into
- // oursEdits=((0-8,0-8),(10-11,10-11)) and
- // theirsEdits=((0-7,0-7))
- //
- // Since the only interesting thing to us is how in ours and
- // theirs the end of the conflicting range is changing we let
- // oursEdit and theirsEdit point to the last conflicting edit
- Edit nextOursEdit = nextEdit(baseToOurs);
- Edit nextTheirsEdit = nextEdit(baseToTheirs);
- for (;;) {
- if (oursEdit.getEndA() >= nextTheirsEdit.getBeginA()) {
- theirsEdit = nextTheirsEdit;
- nextTheirsEdit = nextEdit(baseToTheirs);
- } else if (theirsEdit.getEndA() >= nextOursEdit.getBeginA()) {
- oursEdit = nextOursEdit;
- nextOursEdit = nextEdit(baseToOurs);
- } else {
- break;
- }
- }
- // harmonize the end of the ranges in A and B
- int oursEndB = oursEdit.getEndB();
- int theirsEndB = theirsEdit.getEndB();
- if (oursEdit.getEndA() < theirsEdit.getEndA()) {
- oursEndB += theirsEdit.getEndA() - oursEdit.getEndA();
- } else {
- theirsEndB += oursEdit.getEndA() - theirsEdit.getEndA();
- }
- // A conflicting region is found. Strip off common lines in
- // in the beginning and the end of the conflicting region
- // Determine the minimum length of the conflicting areas in OURS
- // and THEIRS. Also determine how much bigger the conflicting
- // area in THEIRS is compared to OURS. All that is needed to
- // limit the search for common areas at the beginning or end
- // (the common areas cannot be bigger then the smaller
- // conflicting area. The delta is needed to know whether the
- // complete conflicting area is common in OURS and THEIRS.
- int minBSize = oursEndB - oursBeginB;
- int BSizeDelta = minBSize - (theirsEndB - theirsBeginB);
- if (BSizeDelta > 0)
- minBSize -= BSizeDelta;
- int commonPrefix = 0;
- while (commonPrefix < minBSize
- && cmp.equals(ours, oursBeginB + commonPrefix, theirs,
- theirsBeginB + commonPrefix))
- commonPrefix++;
- minBSize -= commonPrefix;
- int commonSuffix = 0;
- while (commonSuffix < minBSize
- && cmp.equals(ours, oursEndB - commonSuffix - 1, theirs,
- theirsEndB - commonSuffix - 1))
- commonSuffix++;
- minBSize -= commonSuffix;
- // Add the common lines at start of conflict
- if (commonPrefix > 0)
- result.add(1, oursBeginB, oursBeginB + commonPrefix,
- ConflictState.NO_CONFLICT);
- // Add the conflict (Only if there is a conflict left to report)
- if (minBSize > 0 || BSizeDelta != 0) {
- result.add(1, oursBeginB + commonPrefix, oursEndB
- - commonSuffix,
- ConflictState.FIRST_CONFLICTING_RANGE);
- result.add(2, theirsBeginB + commonPrefix, theirsEndB
- - commonSuffix,
- ConflictState.NEXT_CONFLICTING_RANGE);
- }
- // Add the common lines at end of conflict
- if (commonSuffix > 0)
- result.add(1, oursEndB - commonSuffix, oursEndB,
- ConflictState.NO_CONFLICT);
- current = Math.max(oursEdit.getEndA(), theirsEdit.getEndA());
- oursEdit = nextOursEdit;
- theirsEdit = nextTheirsEdit;
- }
- }
- // maybe we have a common part behind the last edit: copy it to the
- // result
- if (current < base.size()) {
- result.add(0, current, base.size(), ConflictState.NO_CONFLICT);
- }
- return result;
- }
- /**
- * Helper method which returns the next Edit for an Iterator over Edits.
- * When there are no more edits left this method will return the constant
- * END_EDIT.
- *
- * @param it
- * the iterator for which the next edit should be returned
- * @return the next edit from the iterator or END_EDIT if there no more
- * edits
- */
- private static Edit nextEdit(Iterator<Edit> it) {
- return (it.hasNext() ? it.next() : END_EDIT);
- }
- }