| /* | |
| * 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 HistogramDiff} is used as diff algorithm. | |
| */ | |
| public final class MergeAlgorithm { | |
| private final DiffAlgorithm diffAlg; | |
| /** | |
| * Creates a new MergeAlgorithm which uses {@link 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 <S> | |
| * type of sequence. | |
| * @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<S>(3); | |
| sequences.add(base); | |
| sequences.add(ours); | |
| sequences.add(theirs); | |
| MergeResult<S> result = new MergeResult<S>(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); | |
| } | |
| } |