| /* | |
| * 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. | |
| * <p> | |
| * 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 <S> | |
| * type of sequence being compared. | |
| * @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 <S extends Sequence> 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<S> cs = new SubsequenceComparator<>(cmp); | |
| Subsequence<S> as = Subsequence.a(a, region); | |
| Subsequence<S> 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 <S extends Sequence> Edit coverEdit(S a, S b) { | |
| return new Edit(0, a.size(), 0, b.size()); | |
| } | |
| /** | |
| * Reorganize an {@link EditList} for better diff consistency. | |
| * <p> | |
| * {@code DiffAlgorithms} may return {@link Edit.Type#INSERT} or | |
| * {@link Edit.Type#DELETE} edits that can be "shifted". For | |
| * example, the deleted section | |
| * <pre> | |
| * -a | |
| * -b | |
| * -c | |
| * a | |
| * b | |
| * c | |
| * </pre> | |
| * can be shifted down by 1, 2 or 3 locations. | |
| * <p> | |
| * 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. | |
| * <p> | |
| * This strategy may not always produce an aesthetically pleasing | |
| * diff. For instance, it works well with | |
| * <pre> | |
| * function1 { | |
| * ... | |
| * } | |
| * | |
| * +function2 { | |
| * + ... | |
| * +} | |
| * + | |
| * function3 { | |
| * ... | |
| * } | |
| * </pre> | |
| * but less so for | |
| * <pre> | |
| * # | |
| * # comment1 | |
| * # | |
| * function1() { | |
| * } | |
| * | |
| * # | |
| * +# comment3 | |
| * +# | |
| * +function3() { | |
| * +} | |
| * + | |
| * +# | |
| * # comment2 | |
| * # | |
| * function2() { | |
| * } | |
| * </pre> | |
| * <a href="https://github.com/mhagger/diff-slider-tools">More | |
| * sophisticated strategies</a> 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 <S> | |
| * type of sequence being compared. | |
| * @param cmp | |
| * the comparator supplying the element equivalence function. | |
| * @param e | |
| * a modifiable edit list comparing the provided sequences. | |
| * @param a | |
| * the first (also known as old or pre-image) sequence. | |
| * @param b | |
| * the second (also known as new or post-image) sequence. | |
| * @return a modifiable edit list with edit regions shifted to their | |
| * latest possible location. The result list is never null. | |
| * @since 4.7 | |
| */ | |
| private static <S extends Sequence> EditList normalize( | |
| SequenceComparator<? super S> cmp, EditList e, S a, S b) { | |
| Edit prev = null; | |
| for (int i = e.size() - 1; i >= 0; i--) { | |
| Edit cur = e.get(i); | |
| Edit.Type curType = cur.getType(); | |
| int maxA = (prev == null) ? a.size() : prev.beginA; | |
| int maxB = (prev == null) ? b.size() : prev.beginB; | |
| if (curType == Edit.Type.INSERT) { | |
| while (cur.endA < maxA && cur.endB < maxB | |
| && cmp.equals(b, cur.beginB, b, cur.endB)) { | |
| cur.shift(1); | |
| } | |
| } else if (curType == Edit.Type.DELETE) { | |
| while (cur.endA < maxA && cur.endB < maxB | |
| && cmp.equals(a, cur.beginA, a, cur.endA)) { | |
| cur.shift(1); | |
| } | |
| } | |
| prev = cur; | |
| } | |
| return e; | |
| } | |
| /** | |
| * Compare two sequences and identify a list of edits between them. | |
| * | |
| * This method should be invoked only after the two sequences have been | |
| * proven to have no common starting or ending elements. The expected | |
| * elimination of common starting and ending elements is automatically | |
| * performed by the {@link #diff(SequenceComparator, Sequence, Sequence)} | |
| * method, which invokes this method using | |
| * {@link org.eclipse.jgit.diff.Subsequence}s. | |
| * | |
| * @param <S> | |
| * type of sequence being compared. | |
| * @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. | |
| */ | |
| public abstract <S extends Sequence> EditList diffNonCommon( | |
| SequenceComparator<? super S> cmp, S a, S b); | |
| } |