/*
 * 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 HistogramDiff is used as diff algorithm.
/** * 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 HistogramDiff as diff algorithm
/** * Creates a new MergeAlgorithm which uses * {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm */
public MergeAlgorithm() { this(new HistogramDiff()); }
Creates a new MergeAlgorithm
Params:
  • diff – the diff algorithm used by this merge
/** * 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); @SuppressWarnings("ReferenceEquality") private static boolean isEndEdit(Edit edit) { return edit == END_EDIT; }
Does the three way merge between a common base and two sequences.
Params:
  • cmp – comparison method for this execution.
  • base – the common base sequence
  • ours – the first sequence to be merged
  • theirs – the second sequence to be merged
Returns:the resulting content
/** * 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 (!isEndEdit(theirsEdit) || !isEndEdit(oursEdit)) { 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.
Params:
  • it – the iterator for which the next edit should be returned
Returns:the next edit from the iterator or END_EDIT if there no more edits
/** * 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); } }