/* * Copyright (C) 2010, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.diff; /** * Compares two {@link org.eclipse.jgit.diff.Sequence}s to create an * {@link org.eclipse.jgit.diff.EditList} of changes. *
* An algorithm's {@code diff} method must be callable from concurrent threads
* without data collisions. This permits some algorithms to use a singleton
* pattern, with concurrent invocations using the same singleton. Other
* algorithms may support parameterization, in which case the caller can create
* a unique instance per thread.
*/
public abstract class DiffAlgorithm {
/**
* Supported diff algorithm
*/
public enum SupportedAlgorithm {
/**
* Myers diff algorithm
*/
MYERS,
/**
* Histogram diff algorithm
*/
HISTOGRAM
}
/**
* Get diff algorithm
*
* @param alg
* the diff algorithm for which an implementation should be
* returned
* @return an implementation of the specified diff algorithm
*/
public static DiffAlgorithm getAlgorithm(SupportedAlgorithm alg) {
switch (alg) {
case MYERS:
return MyersDiff.INSTANCE;
case HISTOGRAM:
return new HistogramDiff();
default:
throw new IllegalArgumentException();
}
}
/**
* Compare two sequences and identify a list of edits between them.
*
* @param cmp
* the comparator supplying the element equivalence function.
* @param a
* the first (also known as old or pre-image) sequence. Edits
* returned by this algorithm will reference indexes using the
* 'A' side: {@link org.eclipse.jgit.diff.Edit#getBeginA()},
* {@link org.eclipse.jgit.diff.Edit#getEndA()}.
* @param b
* the second (also known as new or post-image) sequence. Edits
* returned by this algorithm will reference indexes using the
* 'B' side: {@link org.eclipse.jgit.diff.Edit#getBeginB()},
* {@link org.eclipse.jgit.diff.Edit#getEndB()}.
* @return a modifiable edit list comparing the two sequences. If empty, the
* sequences are identical according to {@code cmp}'s rules. The
* result list is never null.
*/
public EditList diff(
SequenceComparator super S> cmp, S a, S b) {
Edit region = cmp.reduceCommonStartEnd(a, b, coverEdit(a, b));
switch (region.getType()) {
case INSERT:
case DELETE:
return EditList.singleton(region);
case REPLACE: {
if (region.getLengthA() == 1 && region.getLengthB() == 1)
return EditList.singleton(region);
SubsequenceComparator cs = new SubsequenceComparator<>(cmp);
Subsequence as = Subsequence.a(a, region);
Subsequence bs = Subsequence.b(b, region);
EditList e = Subsequence.toBase(diffNonCommon(cs, as, bs), as, bs);
return normalize(cmp, e, a, b);
}
case EMPTY:
return new EditList(0);
default:
throw new IllegalStateException();
}
}
private static Edit coverEdit(S a, S b) {
return new Edit(0, a.size(), 0, b.size());
}
/**
* Reorganize an {@link EditList} for better diff consistency.
*
* {@code DiffAlgorithms} may return {@link Edit.Type#INSERT} or * {@link Edit.Type#DELETE} edits that can be "shifted". For * example, the deleted section *
* -a * -b * -c * a * b * c ** can be shifted down by 1, 2 or 3 locations. *
* To avoid later merge issues, we shift such edits to a * consistent location. {@code normalize} uses a simple strategy of * shifting such edits to their latest possible location. *
* This strategy may not always produce an aesthetically pleasing * diff. For instance, it works well with *
* function1 { * ... * } * * +function2 { * + ... * +} * + * function3 { * ... * } ** but less so for *
* # * # comment1 * # * function1() { * } * * # * +# comment3 * +# * +function3() { * +} * + * +# * # comment2 * # * function2() { * } ** More * sophisticated strategies are possible, say by calculating a * suitable "aesthetic cost" for each possible position and using * the lowest cost, but {@code normalize} just shifts edits * to the end as much as possible. * * @param