diff --git a/platform/diff-api/api-dump.txt b/platform/diff-api/api-dump.txt index fc484392dd85..1a5897852b3d 100644 --- a/platform/diff-api/api-dump.txt +++ b/platform/diff-api/api-dump.txt @@ -351,6 +351,7 @@ f:com.intellij.diff.merge.MergeTool$ToolbarComponents a:com.intellij.diff.merge.TextMergeRequest - com.intellij.diff.merge.ThreesideMergeRequest - ():V +- getConflictType():com.intellij.diff.merge.ConflictType - a:getContents():java.util.List - a:getOutputContent():com.intellij.diff.contents.DocumentContent a:com.intellij.diff.merge.ThreesideMergeRequest diff --git a/platform/diff-api/src/com/intellij/diff/merge/TextMergeRequest.java b/platform/diff-api/src/com/intellij/diff/merge/TextMergeRequest.java index d04baf952552..324df4cf9700 100644 --- a/platform/diff-api/src/com/intellij/diff/merge/TextMergeRequest.java +++ b/platform/diff-api/src/com/intellij/diff/merge/TextMergeRequest.java @@ -12,4 +12,8 @@ public abstract class TextMergeRequest extends ThreesideMergeRequest { @Override public abstract @NotNull DocumentContent getOutputContent(); + + public @NotNull ConflictType getConflictType() { + return ConflictType.DEFAULT; + } } diff --git a/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java b/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java index 2ec805bdd3d5..93d3c4277e38 100644 --- a/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java +++ b/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java @@ -333,7 +333,8 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory { List contents = DiffUtil.getDocumentContentsForViewer(project, byteContents, output, conflictType); - return new TextMergeRequestImpl(project, outputContent, originalContent, contents, title, contentTitles); + ConflictType effectiveConflictType = conflictType != null ? conflictType : ConflictType.DEFAULT; + return new TextMergeRequestImpl(project, outputContent, originalContent, contents, effectiveConflictType, title, contentTitles); } @Override diff --git a/platform/diff-impl/src/com/intellij/diff/merge/MergeConflictModel.kt b/platform/diff-impl/src/com/intellij/diff/merge/MergeConflictModel.kt index 972aa146e8fc..19f16438063c 100644 --- a/platform/diff-impl/src/com/intellij/diff/merge/MergeConflictModel.kt +++ b/platform/diff-impl/src/com/intellij/diff/merge/MergeConflictModel.kt @@ -355,8 +355,9 @@ class MergeConflictModel( } fun canResolveChangeAutomatically(changeIndex: Int, side: ThreeSide): Boolean { - val change = getByIndex(changeIndex) + if (mergeRequest.conflictType.isModifyDeleteFileConflict()) return false + val change = getByIndex(changeIndex) return if (change.isConflict) { side == ThreeSide.BASE && change.conflictType.canBeResolved() && @@ -371,6 +372,11 @@ class MergeConflictModel( } } + private fun ConflictType.isModifyDeleteFileConflict() = when (this) { + ConflictType.MODIFIED_DELETED, ConflictType.DELETED_MODIFIED -> true + ConflictType.DEFAULT, ConflictType.ADDED_ADDED -> false + } + private fun TextMergeRequest.resetOutputContent() { val text = ThreeSide.BASE.select(contents).document.text outputContent.document.setText(text) diff --git a/platform/diff-impl/src/com/intellij/diff/requests/TextMergeRequestImpl.java b/platform/diff-impl/src/com/intellij/diff/requests/TextMergeRequestImpl.java index 11c224786ad6..297b910e165b 100644 --- a/platform/diff-impl/src/com/intellij/diff/requests/TextMergeRequestImpl.java +++ b/platform/diff-impl/src/com/intellij/diff/requests/TextMergeRequestImpl.java @@ -2,6 +2,7 @@ package com.intellij.diff.requests; import com.intellij.diff.contents.DocumentContent; +import com.intellij.diff.merge.ConflictType; import com.intellij.diff.merge.MergeCallback; import com.intellij.diff.merge.MergeResult; import com.intellij.diff.merge.MergeUtil; @@ -25,6 +26,7 @@ public class TextMergeRequestImpl extends TextMergeRequest { private final @NotNull List myContents; private final @NotNull CharSequence myOriginalContent; + private final @NotNull ConflictType myConflictType; private final @Nullable @NlsContexts.DialogTitle String myTitle; private final @NotNull List myTitles; @@ -35,12 +37,23 @@ public class TextMergeRequestImpl extends TextMergeRequest { @NotNull List contents, @Nullable @NlsContexts.DialogTitle String title, @NotNull List<@Nls String> contentTitles) { + this(project, output, originalContent, contents, ConflictType.DEFAULT, title, contentTitles); + } + + public TextMergeRequestImpl(@Nullable Project project, + @NotNull DocumentContent output, + @NotNull CharSequence originalContent, + @NotNull List contents, + @NotNull ConflictType conflictType, + @Nullable @NlsContexts.DialogTitle String title, + @NotNull List<@Nls String> contentTitles) { assert contents.size() == 3; assert contentTitles.size() == 3; myProject = project; myOutput = output; myOriginalContent = originalContent; + myConflictType = conflictType; myContents = contents; myTitles = contentTitles; @@ -57,6 +70,11 @@ public class TextMergeRequestImpl extends TextMergeRequest { return myContents; } + @Override + public @NotNull ConflictType getConflictType() { + return myConflictType; + } + @Override public @Nullable String getTitle() { return myTitle; diff --git a/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTest.kt b/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTest.kt index 233068054b5e..27582dc4a3a6 100644 --- a/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTest.kt +++ b/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTest.kt @@ -813,6 +813,38 @@ class MergeTest : MergeTestBase() { } } + fun testDoNotAutoResolveDeletedByUsFileConflict() { + // "deleted by us": ours (left) removed the file, theirs (right) kept the base content (e.g. a rename). + doTest("", "A_B_C", "A_B_C", 1, ConflictType.DELETED_MODIFIED) { + assertFalse(0.canResolveConflict()) + + runApplyNonConflictsAction(ThreeSide.BASE) + 0.assertResolved(NONE) + assertContent("A_B_C") + } + } + + fun testDoNotAutoResolveDeletedByThemFileConflict() { + // "deleted by them": theirs (right) removed the file, ours (left) kept the base content. + doTest("A_B_C", "A_B_C", "", 1, ConflictType.MODIFIED_DELETED) { + assertFalse(0.canResolveConflict()) + + runApplyNonConflictsAction(ThreeSide.BASE) + 0.assertResolved(NONE) + assertContent("A_B_C") + } + } + + fun testAutoResolveDeletionWithoutFileConflictType() { + // Control: without the modify/delete ConflictType, the same one-sided change still auto-resolves. + doTest("", "A_B_C", "A_B_C", 1) { + assertTrue(0.canResolveConflict()) + + runApplyNonConflictsAction(ThreeSide.BASE) + 0.assertResolved(BOTH) + } + } + fun testApplySeveralActions() { doTest("X_1_Y_2_Z_3_4_U_W_", "X_a_Y_b_Z_c_U_d_W_", diff --git a/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTestBase.kt b/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTestBase.kt index b74aace45024..5426443d5f78 100644 --- a/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTestBase.kt +++ b/platform/diff-impl/tests/testSrc/com/intellij/diff/merge/MergeTestBase.kt @@ -48,7 +48,21 @@ abstract class MergeTestBase : HeavyDiffTestCase() { doTest(left, base, right, changesCount, IgnorePolicy.DEFAULT, f) } + fun doTest( + left: String, base: String, right: String, changesCount: Int, + conflictType: ConflictType, f: TestBuilder.() -> Unit, + ) { + doTest(left, base, right, changesCount, IgnorePolicy.DEFAULT, conflictType, f) + } + fun doTest(left: String, base: String, right: String, changesCount: Int, policy: IgnorePolicy, f: TestBuilder.() -> Unit) { + doTest(left, base, right, changesCount, policy, ConflictType.DEFAULT, f) + } + + fun doTest( + left: String, base: String, right: String, changesCount: Int, policy: IgnorePolicy, + conflictType: ConflictType, f: TestBuilder.() -> Unit, + ) { val contentFactory = DiffContentFactoryImpl() val leftContent: DocumentContent = contentFactory.create(parseSource(left)) val baseContent: DocumentContent = contentFactory.create(parseSource(base)) @@ -57,7 +71,7 @@ abstract class MergeTestBase : HeavyDiffTestCase() { outputContent.document.setReadOnly(false) val context = MockMergeContext(project) - val request = MockMergeRequest(leftContent, baseContent, rightContent, outputContent) + val request = MockMergeRequest(leftContent, baseContent, rightContent, outputContent, conflictType) val settings = TextDiffSettings() settings.ignorePolicy = policy @@ -376,10 +390,13 @@ abstract class MergeTestBase : HeavyDiffTestCase() { } } - private class MockMergeRequest(val left: DocumentContent, - val base: DocumentContent, - val right: DocumentContent, - val output: DocumentContent) : TextMergeRequest() { + private class MockMergeRequest( + val left: DocumentContent, + val base: DocumentContent, + val right: DocumentContent, + val output: DocumentContent, + private val requestConflictType: ConflictType = ConflictType.DEFAULT, + ) : TextMergeRequest() { override fun getTitle(): String? = null override fun applyResult(result: MergeResult) { @@ -390,6 +407,8 @@ abstract class MergeTestBase : HeavyDiffTestCase() { override fun getOutputContent(): DocumentContent = output override fun getContentTitles(): List = listOf(null, null, null) + + override fun getConflictType(): ConflictType = requestConflictType } enum class SidesState {