IDEA-12609 diff: do not consider equal changes as conflict

This commit is contained in:
Aleksey Pivovarov
2014-03-06 19:36:36 +04:00
parent 5379407891
commit 3403baba45
6 changed files with 253 additions and 184 deletions
@@ -15,120 +15,56 @@
*/
package com.intellij.openapi.diff.impl.incrementalMerge;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
/**
* One of two conflicting changes.
*
* @see MergeConflict
*/
class ConflictChange extends Change implements DiffRangeMarker.RangeInvalidListener {
class ConflictChange extends TwoSideChange.SideChange<MergeConflict> {
private static final Logger LOG = Logger.getInstance(ConflictChange.class);
private SimpleChangeSide myOriginalSide;
private MergeConflict myConflict;
@NotNull private final ChangeList myChangeList;
private ChangeType myType;
private boolean mySemiApplied;
public ConflictChange(@NotNull MergeConflict conflict,
@NotNull FragmentSide mergeSide,
@NotNull TextRange range,
@NotNull TextRange versionRange,
@NotNull ChangeList changeList) {
myConflict = conflict;
myChangeList = changeList;
myOriginalSide = new SimpleChangeSide(mergeSide, new DiffRangeMarker((DocumentEx)conflict.getOriginalDocument(mergeSide), range, this));
myType = ChangeType.CONFLICT;
}
@Override
protected void changeSide(ChangeSide sideToChange, DiffRangeMarker newRange) {
myConflict.setRange(newRange);
}
protected void removeFromList() {
myConflict.conflictRemoved();
myConflict = null;
}
@NotNull
public ChangeSide getChangeSide(@NotNull FragmentSide side) {
return isBranch(side) ? myOriginalSide : myConflict;
}
private static boolean isBranch(@NotNull FragmentSide side) {
return MergeList.BRANCH_SIDE == side;
}
public SimpleChangeSide getOriginalSide() {
return myOriginalSide;
}
public ChangeType getType() {
return myType;
}
public ChangeList getChangeList() {
return myConflict.getMergeList().getChanges(myOriginalSide.getFragmentSide());
super(conflict, changeList, ChangeType.CONFLICT, mergeSide, versionRange);
}
@Override
public void onApplied() {
apply();
markApplied();
// update the other variant of the conflict to point to the bottom
if (!mySemiApplied) {
ConflictChange otherChange = myConflict.getOtherChange(this);
LOG.assertTrue(otherChange != null, String.format("Other change is null. This change: %s Merge conflict: %s", this, myConflict));
ConflictChange otherChange = myTwoSideChange.getOtherChange(this);
LOG.assertTrue(otherChange != null, String.format("Other change is null. This change: %s Merge conflict: %s", this, myTwoSideChange));
otherChange.mySemiApplied = true;
otherChange.updateOtherSideOnConflictApply();
myConflict.removeOtherChange(this);
myTwoSideChange.removeOtherChange(this);
}
}
private void updateOtherSideOnConflictApply() {
if (myOriginalSide.getStart() == myOriginalSide.getEnd()) {
apply();
markApplied();
return;
}
int startOffset = myConflict.getRange().getEndOffset();
int startOffset = myTwoSideChange.getRange().getEndOffset();
TextRange emptyRange = new TextRange(startOffset, startOffset);
ConflictChange leftChange = isBranch(myOriginalSide.getFragmentSide()) ? null : this;
ConflictChange rightChange = isBranch(myOriginalSide.getFragmentSide()) ? this : null;
myConflict = myConflict.deriveSideForNotAppliedChange(emptyRange, leftChange, rightChange);
myTwoSideChange = myTwoSideChange.deriveSideForNotAppliedChange(emptyRange, leftChange, rightChange);
myOriginalSide.getHighlighterHolder().updateHighlighter(myOriginalSide, myType);
myConflict.getHighlighterHolder().updateHighlighter(myConflict, myType);
myTwoSideChange.getHighlighterHolder().updateHighlighter(myTwoSideChange, myType);
myChangeList.fireOnChangeApplied();
}
private void apply() {
myType = ChangeType.deriveApplied(myType);
myChangeList.apply(this);
myOriginalSide.getHighlighterHolder().updateHighlighter(myOriginalSide, myType);
myOriginalSide.getHighlighterHolder().setActions(new AnAction[0]);
// display, what one side of the conflict was resolved to
myConflict.getHighlighterHolder().updateHighlighter(myConflict, myType);
}
public void onRemovedFromList() {
myOriginalSide.getRange().removeListener(this);
myConflict = null;
myOriginalSide = null;
}
public boolean isValid() {
return myConflict != null;
}
public void onRangeInvalidated() {
removeFromList();
}
}
@@ -16,8 +16,6 @@
package com.intellij.openapi.diff.impl.incrementalMerge;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,30 +23,22 @@ import org.jetbrains.annotations.Nullable;
/**
* Represents a merge conflict, i.e. two {@link ConflictChange conflicting changes}, one from left, another from right.
*/
class MergeConflict extends ChangeSide implements DiffRangeMarker.RangeInvalidListener {
@NotNull private final MergeList myMergeList;
@NotNull private DiffRangeMarker myCommonRange;
@Nullable private ConflictChange myLeftChange;
@Nullable private ConflictChange myRightChange;
@NotNull private final ChangeHighlighterHolder myCommonHighlighterHolder;
MergeConflict(@NotNull TextRange commonRange,
@NotNull MergeList mergeList,
@NotNull TextRange leftMarker,
@NotNull TextRange rightMarker) {
myCommonRange = new DiffRangeMarker((DocumentEx)mergeList.getBaseDocument(), commonRange, this);
myMergeList = mergeList;
myCommonHighlighterHolder = new ChangeHighlighterHolder();
myLeftChange = new ConflictChange(this, FragmentSide.SIDE1, leftMarker, mergeList.getLeftChangeList());
myRightChange = new ConflictChange(this, FragmentSide.SIDE2, rightMarker, mergeList.getRightChangeList());
class MergeConflict extends TwoSideChange<ConflictChange> {
MergeConflict(@NotNull TextRange baseRange,
@NotNull TextRange leftRange,
@NotNull TextRange rightRange,
@NotNull MergeList mergeList) {
super(baseRange, mergeList, new ChangeHighlighterHolder());
myLeftChange = new ConflictChange(this, FragmentSide.SIDE1, leftRange, mergeList.getLeftChangeList());
myRightChange = new ConflictChange(this, FragmentSide.SIDE2, rightRange, mergeList.getRightChangeList());
}
private MergeConflict(@NotNull TextRange commonRange, @NotNull MergeList mergeList, @NotNull ChangeHighlighterHolder highlighterHolder,
@Nullable ConflictChange leftChange, @Nullable ConflictChange rightChange) {
myCommonRange = new DiffRangeMarker((DocumentEx)mergeList.getBaseDocument(), commonRange, this);
myMergeList = mergeList;
myCommonHighlighterHolder = highlighterHolder;
private MergeConflict(@NotNull TextRange baseRange,
@Nullable ConflictChange leftChange,
@Nullable ConflictChange rightChange,
@NotNull MergeList mergeList,
@NotNull ChangeHighlighterHolder highlighterHolder) {
super(baseRange, mergeList, highlighterHolder);
myLeftChange = leftChange;
myRightChange = rightChange;
}
@@ -58,86 +48,8 @@ class MergeConflict extends ChangeSide implements DiffRangeMarker.RangeInvalidLi
@Nullable ConflictChange leftChange,
@Nullable ConflictChange rightChange) {
ChangeHighlighterHolder highlighterHolder = new ChangeHighlighterHolder();
MergeConflict mergeConflict = new MergeConflict(baseRange, myMergeList, highlighterHolder, leftChange, rightChange);
MergeConflict mergeConflict = new MergeConflict(baseRange, leftChange, rightChange, myMergeList, highlighterHolder);
highlighterHolder.highlight(mergeConflict, myCommonHighlighterHolder.getEditor(), ChangeType.CONFLICT);
return mergeConflict;
}
@NotNull
public ChangeHighlighterHolder getHighlighterHolder() {
return myCommonHighlighterHolder;
}
@NotNull
public DiffRangeMarker getRange() {
return myCommonRange;
}
@Nullable
public ConflictChange getLeftChange() {
return myLeftChange;
}
@Nullable
public ConflictChange getRightChange() {
return myRightChange;
}
@Nullable
ConflictChange getOtherChange(@NotNull ConflictChange change) {
if (change == myLeftChange) {
return myRightChange;
}
else if (change == myRightChange) {
return myLeftChange;
}
else {
throw new IllegalStateException("Unexpected change: " + change);
}
}
public void conflictRemoved() {
removeHighlighters(myLeftChange);
removeHighlighters(myRightChange);
myCommonHighlighterHolder.removeHighlighters();
myMergeList.removeChanges(myLeftChange, myRightChange);
myCommonRange.removeListener(this);
}
public void setRange(@NotNull DiffRangeMarker range) {
myCommonRange = range;
}
public void removeOtherChange(@NotNull ConflictChange change) {
if (change == myLeftChange) {
myRightChange = null;
}
else if (change == myRightChange) {
myLeftChange = null;
}
else {
throw new IllegalStateException("Unexpected change: " + change);
}
}
private static void removeHighlighters(@Nullable ConflictChange change) {
if (change != null) {
change.getOriginalSide().getHighlighterHolder().removeHighlighters();
}
}
@NotNull
public Document getOriginalDocument(FragmentSide mergeSide) {
return myMergeList.getChanges(mergeSide).getDocument(MergeList.BRANCH_SIDE);
}
public void onRangeInvalidated() {
conflictRemoved();
}
@NotNull
public MergeList getMergeList() {
return myMergeList;
}
}
@@ -114,14 +114,11 @@ public class MergeList implements UserDataHolder {
TextRange rightRange = mergeFragment.getRight();
if (compareSubstring(leftText, leftRange, rightText, rightRange)) {
MergeConflict conflict = new MergeConflict(baseRange, mergeList, leftRange, rightRange);
MergeNoConflict conflict = new MergeNoConflict(baseRange, leftRange, rightRange, mergeList);
assert conflict.getLeftChange() != null;
assert conflict.getRightChange() != null;
leftChanges.add(conflict.getLeftChange());
rightChanges.add(conflict.getRightChange());
// TODO: no conflict
//rightChanges.add(SimpleChange.fromRanges(baseRange, rightRange, mergeList.myBaseToRightChangeList));
//leftChanges.add(SimpleChange.fromRanges(baseRange, leftRange, mergeList.myBaseToLeftChangeList));
}
else if (compareSubstring(baseText, baseRange, leftText, leftRange)) {
rightChanges.add(SimpleChange.fromRanges(baseRange, rightRange, mergeList.myBaseToRightChangeList));
@@ -130,7 +127,7 @@ public class MergeList implements UserDataHolder {
leftChanges.add(SimpleChange.fromRanges(baseRange, leftRange, mergeList.myBaseToLeftChangeList));
}
else {
MergeConflict conflict = new MergeConflict(baseRange, mergeList, leftRange, rightRange);
MergeConflict conflict = new MergeConflict(baseRange, leftRange, rightRange, mergeList);
assert conflict.getLeftChange() != null;
assert conflict.getRightChange() != null;
leftChanges.add(conflict.getLeftChange());
@@ -0,0 +1,16 @@
package com.intellij.openapi.diff.impl.incrementalMerge;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
public class MergeNoConflict extends TwoSideChange<NoConflictChange> {
MergeNoConflict(@NotNull TextRange baseRange,
@NotNull TextRange leftRange,
@NotNull TextRange rightRange,
@NotNull MergeList mergeList) {
super(baseRange, mergeList, new ChangeHighlighterHolder());
myLeftChange = new NoConflictChange(this, FragmentSide.SIDE1, baseRange, leftRange, mergeList.getLeftChangeList());
myRightChange = new NoConflictChange(this, FragmentSide.SIDE2, baseRange, rightRange, mergeList.getRightChangeList());
}
}
@@ -0,0 +1,27 @@
package com.intellij.openapi.diff.impl.incrementalMerge;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
public class NoConflictChange extends TwoSideChange.SideChange<MergeNoConflict> {
private static final Logger LOG = Logger.getInstance(NoConflictChange.class);
public NoConflictChange(@NotNull MergeNoConflict twoSideChange,
@NotNull FragmentSide mergeSide,
@NotNull TextRange baseRange,
@NotNull TextRange versionRange,
@NotNull ChangeList changeList) {
super(twoSideChange, changeList, ChangeType.fromRanges(baseRange, versionRange), mergeSide, versionRange);
}
@Override
public void onApplied() {
markApplied();
NoConflictChange otherChange = myTwoSideChange.getOtherChange(this);
LOG.assertTrue(otherChange != null, String.format("Other change is null. This change: %s Merge conflict: %s", this, myTwoSideChange));
otherChange.markApplied();
}
}
@@ -0,0 +1,181 @@
package com.intellij.openapi.diff.impl.incrementalMerge;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class TwoSideChange<T extends TwoSideChange.SideChange> extends ChangeSide implements DiffRangeMarker.RangeInvalidListener {
@NotNull protected final MergeList myMergeList;
@NotNull protected DiffRangeMarker myBaseRangeMarker;
protected T myLeftChange;
protected T myRightChange;
@NotNull protected final ChangeHighlighterHolder myCommonHighlighterHolder;
protected TwoSideChange(@NotNull TextRange baseRange,
@NotNull MergeList mergeList,
@NotNull ChangeHighlighterHolder highlighterHolder) {
myBaseRangeMarker = new DiffRangeMarker((DocumentEx)mergeList.getBaseDocument(), baseRange, this);
myMergeList = mergeList;
myCommonHighlighterHolder = highlighterHolder;
}
@NotNull
public ChangeHighlighterHolder getHighlighterHolder() {
return myCommonHighlighterHolder;
}
@NotNull
public DiffRangeMarker getRange() {
return myBaseRangeMarker;
}
@Nullable
public Change getLeftChange() {
return myLeftChange;
}
@Nullable
public Change getRightChange() {
return myRightChange;
}
public void setRange(@NotNull DiffRangeMarker range) {
myBaseRangeMarker = range;
}
@Nullable
T getOtherChange(@NotNull T change) {
if (change == myLeftChange) {
return myRightChange;
}
else if (change == myRightChange) {
return myLeftChange;
}
else {
throw new IllegalStateException("Unexpected change: " + change);
}
}
public void removeOtherChange(@NotNull T change) {
if (change == myLeftChange) {
myRightChange = null;
}
else if (change == myRightChange) {
myLeftChange = null;
}
else {
throw new IllegalStateException("Unexpected change: " + change);
}
}
public void conflictRemoved() {
removeHighlighters(myLeftChange);
removeHighlighters(myRightChange);
myCommonHighlighterHolder.removeHighlighters();
myMergeList.removeChanges(myLeftChange, myRightChange);
myBaseRangeMarker.removeListener(this);
}
private static <T extends SideChange> void removeHighlighters(@Nullable T change) {
if (change != null) {
change.getOriginalSide().getHighlighterHolder().removeHighlighters();
}
}
@NotNull
public Document getOriginalDocument(FragmentSide mergeSide) {
return myMergeList.getChanges(mergeSide).getDocument(MergeList.BRANCH_SIDE);
}
public void onRangeInvalidated() {
conflictRemoved();
}
@NotNull
public MergeList getMergeList() {
return myMergeList;
}
protected static abstract class SideChange<V extends TwoSideChange> extends Change implements DiffRangeMarker.RangeInvalidListener {
protected V myTwoSideChange;
@NotNull protected final ChangeList myChangeList;
protected SimpleChangeSide myOriginalSide;
@NotNull protected ChangeType myType;
protected SideChange(@NotNull V twoSideChange,
@NotNull ChangeList changeList,
@NotNull ChangeType type,
@NotNull FragmentSide mergeSide,
@NotNull TextRange versionRange) {
myTwoSideChange = twoSideChange;
myChangeList = changeList;
myOriginalSide =
new SimpleChangeSide(mergeSide, new DiffRangeMarker((DocumentEx)twoSideChange.getOriginalDocument(mergeSide), versionRange, this));
myType = type;
}
@NotNull
public ChangeType getType() {
return myType;
}
public SimpleChangeSide getOriginalSide() {
return myOriginalSide;
}
protected void markApplied() {
myType = ChangeType.deriveApplied(myType);
myChangeList.apply(this);
myOriginalSide.getHighlighterHolder().updateHighlighter(myOriginalSide, myType);
myOriginalSide.getHighlighterHolder().setActions(new AnAction[0]);
// display, what one side of the conflict was resolved to
myTwoSideChange.getHighlighterHolder().updateHighlighter(myTwoSideChange, myType);
}
public ChangeList getChangeList() {
return myTwoSideChange.getMergeList().getChanges(myOriginalSide.getFragmentSide());
}
@Override
protected void changeSide(ChangeSide sideToChange, DiffRangeMarker newRange) {
myTwoSideChange.setRange(newRange);
}
@NotNull
@Override
public ChangeSide getChangeSide(@NotNull FragmentSide side) {
return isBranch(side) ? myOriginalSide : myTwoSideChange;
}
protected static boolean isBranch(@NotNull FragmentSide side) {
return MergeList.BRANCH_SIDE == side;
}
protected void removeFromList() {
myTwoSideChange.conflictRemoved();
myTwoSideChange = null;
}
public boolean isValid() {
return myTwoSideChange != null;
}
public void onRemovedFromList() {
myOriginalSide.getRange().removeListener(this);
myTwoSideChange = null;
myOriginalSide = null;
}
public void onRangeInvalidated() {
removeFromList();
}
}
}