mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 23:39:07 +07:00
[git4idea] Add a flow for resolving conflicts from MR/PR
GitOrigin-RevId: e517f674266631bd04417c9aceb9c6044f8ca5a6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3ccb7e84b6
commit
dd900f8db2
@@ -108,6 +108,9 @@ review.details.status.reviewer.wait.for.updates={0} is waiting for updates
|
||||
|
||||
review.details.view.timeline.action=View Timeline
|
||||
|
||||
review.details.resolve-conflicts.head=Source
|
||||
review.details.resolve-conflicts.base=Target
|
||||
|
||||
# Review submit
|
||||
review.comment.placeholder=Review comment
|
||||
review.submit.action=Submit
|
||||
|
||||
@@ -1496,6 +1496,8 @@ notification.title.error.merging=Merge Error
|
||||
progress.text.merging.repository=Merging{0}\u2026
|
||||
dialog.title.rebasing.merge.commits=Rebasing Merge Commits
|
||||
dialog.message.rebasing.merge.commits=You are about to rebase a merge commit with conflicts.\n\nChoose 'Merge' if you don't want to resolve conflicts again, or you still can rebase if you want to linearize the history.
|
||||
dialog.title.update.branch=Update {0}
|
||||
dialog.message.update.branch=You are about to update {0} from {1}.\n\nChoose ''Merge'' if you want to create a merge commit, ''Rebase'' if you want to rebase onto {1}.
|
||||
rebasing.merge.commits.button.merge=Merge
|
||||
rebasing.merge.commits.button.rebase=Rebase
|
||||
rebasing.merge.commits.button.cancel=Cancel
|
||||
|
||||
@@ -206,7 +206,23 @@ public interface GitBrancher {
|
||||
@NotNull List<? extends @NotNull GitRepository> repositories);
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #merge(GitReference, DeleteOnMergeOption, List)}
|
||||
* <p>Merges the given branch to the HEAD.</p>
|
||||
* <p>{@code git merge <name>}</p>
|
||||
* <p>If local changes prevent merging, proposes the "Smart merge" procedure (stash-merge-unstash).</p>
|
||||
* <p>If untracked files prevent merging, shows them in an error dialog.</p>
|
||||
*
|
||||
* @param reference local/remote branch or tag to be merged into HEAD.
|
||||
* @param deleteOnMerge specify whether the branch should be automatically deleted or proposed to be deleted after merge.
|
||||
* @param repositories repositories to operate on.
|
||||
* @param allowRollback whether to prompt the user to rollback on conflicts. Useful to set to `false` when prompting is not necessary.
|
||||
*/
|
||||
void merge(@NotNull GitReference reference,
|
||||
@NotNull DeleteOnMergeOption deleteOnMerge,
|
||||
@NotNull List<? extends @NotNull GitRepository> repositories,
|
||||
@NotNull Boolean allowRollback);
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #merge(GitReference, DeleteOnMergeOption, List, Boolean)}
|
||||
* @param branchName
|
||||
* @param deleteOnMerge
|
||||
* @param repositories
|
||||
|
||||
@@ -50,6 +50,14 @@ class GitBrancherImpl implements GitBrancher {
|
||||
return new GitBranchWorker(myProject, Git.getInstance(), new GitBranchUiHandlerImpl(myProject, indicator));
|
||||
}
|
||||
|
||||
private @NotNull GitBranchWorker newWorkerWithoutRollback(@NotNull ProgressIndicator indicator) {
|
||||
return new GitBranchWorker(myProject, Git.getInstance(), new GitBranchUiHandlerImpl(myProject, indicator) {
|
||||
@Override
|
||||
public boolean showUnmergedFilesMessageWithRollback(@NotNull String operationName, @NotNull String rollbackProposal) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void createBranch(@NotNull String name, @NotNull Map<GitRepository, String> startPoints, @Nullable Runnable callInAwtLater) {
|
||||
createBranch(name, startPoints, false, callInAwtLater);
|
||||
@@ -190,10 +198,19 @@ class GitBrancherImpl implements GitBrancher {
|
||||
public void merge(@NotNull GitReference reference,
|
||||
@NotNull DeleteOnMergeOption deleteOnMerge,
|
||||
@NotNull List<? extends @NotNull GitRepository> repositories) {
|
||||
merge(reference, deleteOnMerge, repositories, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(@NotNull GitReference reference,
|
||||
@NotNull DeleteOnMergeOption deleteOnMerge,
|
||||
@NotNull List<? extends @NotNull GitRepository> repositories,
|
||||
@NotNull Boolean allowRollback) {
|
||||
new CommonBackgroundTask(myProject, GitBundle.message("branch.merging.process", reference.getName()), null) {
|
||||
@Override
|
||||
public void execute(@NotNull ProgressIndicator indicator) {
|
||||
newWorker(indicator).merge(reference, deleteOnMerge, repositories);
|
||||
GitBranchWorker worker = allowRollback ? newWorker(indicator) : newWorkerWithoutRollback(indicator);
|
||||
worker.merge(reference, deleteOnMerge, repositories);
|
||||
}
|
||||
}.runInBackground();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package git4idea.remote.hosting.ui
|
||||
|
||||
import com.intellij.collaboration.messages.CollaborationToolsBundle
|
||||
import git4idea.remote.hosting.HostedGitRepositoryRemote
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
/**
|
||||
* Represents a request to resolve conflicts in a code review locally.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class ResolveConflictsLocallyCoordinates(
|
||||
val headRemoteDescriptor: HostedGitRepositoryRemote,
|
||||
val headRefName: String,
|
||||
val baseRemoteDescriptor: HostedGitRepositoryRemote,
|
||||
val baseRefName: String
|
||||
)
|
||||
|
||||
@ApiStatus.Internal
|
||||
enum class BaseOrHead(val text: @Nls String) {
|
||||
Base(CollaborationToolsBundle.message("review.details.resolve-conflicts.base")),
|
||||
Head(CollaborationToolsBundle.message("review.details.resolve-conflicts.head"));
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package git4idea.remote.hosting.ui
|
||||
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import git4idea.DialogManager
|
||||
import git4idea.i18n.GitBundle
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
@ApiStatus.Internal
|
||||
object ResolveConflictsLocallyDialogComponentFactory {
|
||||
private fun ResolveConflictsMethod.toMessage(): @Nls String = when (this) {
|
||||
ResolveConflictsMethod.REBASE -> GitBundle.message("rebasing.merge.commits.button.rebase")
|
||||
ResolveConflictsMethod.MERGE -> GitBundle.message("rebasing.merge.commits.button.merge")
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog to choose between rebasing or merging a branch with conflicts known on the remote.
|
||||
*/
|
||||
fun showBranchUpdateDialog(headRefName: String, baseRefName: String): ResolveConflictsMethod? {
|
||||
val exitCode = DialogManager.showMessage(
|
||||
GitBundle.message("dialog.message.update.branch", headRefName, baseRefName),
|
||||
GitBundle.message("dialog.title.update.branch", headRefName),
|
||||
ResolveConflictsMethod.entries.map { it.toMessage() }.toTypedArray() + GitBundle.message("rebasing.merge.commits.button.cancel"),
|
||||
ResolveConflictsMethod.entries.indexOf(ResolveConflictsMethod.REBASE),
|
||||
ResolveConflictsMethod.entries.indexOf(ResolveConflictsMethod.REBASE),
|
||||
Messages.getQuestionIcon(), null
|
||||
)
|
||||
return ResolveConflictsMethod.entries.getOrNull(exitCode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package git4idea.remote.hosting.ui
|
||||
|
||||
import com.intellij.collaboration.async.mapState
|
||||
import com.intellij.collaboration.ui.Either
|
||||
import com.intellij.collaboration.util.SingleCoroutineLauncher
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import git4idea.GitRemoteBranch
|
||||
import git4idea.GitStandardRemoteBranch
|
||||
import git4idea.branch.GitBrancher
|
||||
import git4idea.fetch.GitFetchSupport
|
||||
import git4idea.i18n.GitBundle
|
||||
import git4idea.remote.hosting.GitRemoteBranchesUtil
|
||||
import git4idea.remote.hosting.HostedGitRepositoryRemote
|
||||
import git4idea.repo.GitRemote
|
||||
import git4idea.repo.GitRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
enum class ResolveConflictsMethod {
|
||||
REBASE,
|
||||
MERGE;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface ResolveConflictsLocallyViewModel<Error : Any> {
|
||||
/**
|
||||
* Whether there are conflicts that need to be resolved before merging.
|
||||
*
|
||||
* If the value is `null`, there is a check currently in progress or there is something
|
||||
* else preventing us from knowing whether there are conflicts.
|
||||
*/
|
||||
val hasConflicts: StateFlow<Boolean?>
|
||||
|
||||
val requestOrError: StateFlow<Either<Error, ResolveConflictsLocallyCoordinates>>
|
||||
|
||||
val isBusy: StateFlow<Boolean>
|
||||
|
||||
fun performResolveConflicts(chooseMethod: suspend () -> ResolveConflictsMethod?)
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class BaseResolveConflictsLocallyViewModel<Error : Any>(
|
||||
parentCs: CoroutineScope,
|
||||
private val project: Project,
|
||||
private val gitRepository: GitRepository,
|
||||
) : ResolveConflictsLocallyViewModel<Error> {
|
||||
protected val cs = parentCs.childScope("Resolve Conflicts Locally Scope")
|
||||
private val taskLauncher = SingleCoroutineLauncher(cs.childScope("Resolve Conflicts Locally Task"))
|
||||
|
||||
private val requestFlow: StateFlow<ResolveConflictsLocallyCoordinates?>
|
||||
get() = requestOrError.mapState { it.asRightOrNull() }
|
||||
|
||||
private val repositories = listOf(gitRepository)
|
||||
|
||||
private val _isBusyFlow: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
override val isBusy: StateFlow<Boolean> = taskLauncher.busy
|
||||
|
||||
override fun performResolveConflicts(chooseMethod: suspend () -> ResolveConflictsMethod?) {
|
||||
taskLauncher.launch {
|
||||
val method = chooseMethod()
|
||||
when (method) {
|
||||
ResolveConflictsMethod.REBASE -> rebase()
|
||||
ResolveConflictsMethod.MERGE -> merge()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun rebase() {
|
||||
prepareAndPerformUpdate { brancher, baseBranch ->
|
||||
brancher.rebase(repositories, baseBranch.nameForLocalOperations)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun merge() {
|
||||
prepareAndPerformUpdate { brancher, baseBranch ->
|
||||
brancher.merge(baseBranch, GitBrancher.DeleteOnMergeOption.NOTHING, repositories, false)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun prepareAndPerformUpdate(updater: suspend (brancher: GitBrancher, baseBranch: GitRemoteBranch) -> Unit) {
|
||||
val request = requestFlow.value ?: return
|
||||
|
||||
val headRemote = getOrCreateRemote(request.headRemoteDescriptor) ?: return
|
||||
val headBranch = GitStandardRemoteBranch(headRemote, request.headRefName)
|
||||
|
||||
val baseRemote = getOrCreateRemote(request.baseRemoteDescriptor) ?: return
|
||||
val baseBranch = GitStandardRemoteBranch(baseRemote, request.baseRefName)
|
||||
|
||||
// Fetch base ref
|
||||
val fetcher = GitFetchSupport.fetchSupport(project)
|
||||
withContext(Dispatchers.IO) {
|
||||
withBackgroundProgress(project, GitBundle.message("git.fetch.progress")) {
|
||||
fetcher.fetch(gitRepository, baseRemote, baseBranch.nameForRemoteOperations)
|
||||
}
|
||||
}
|
||||
|
||||
// Checkout the head branch
|
||||
withContext(Dispatchers.Main) {
|
||||
GitRemoteBranchesUtil.checkoutRemoteBranch(gitRepository, headBranch)
|
||||
}
|
||||
|
||||
// Rebase or merge on the base ref
|
||||
val brancher = GitBrancher.getInstance(project)
|
||||
|
||||
return updater(brancher, baseBranch)
|
||||
}
|
||||
|
||||
private suspend fun getOrCreateRemote(remoteDescriptor: HostedGitRepositoryRemote): GitRemote? =
|
||||
GitRemoteBranchesUtil.findOrCreateRemote(gitRepository, remoteDescriptor)
|
||||
}
|
||||
Reference in New Issue
Block a user