mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Revert "[git] IJPL-207004 Add a notification action to retry fetch of checked out branch with --update-head-ok flag"
This reverts commit c733994e. Action will be just disabled in case if it's checked out in any working tree. (cherry picked from commit 270e543268d8fd8efd47c23905c9003e44a8e9e8) IJ-CR-194094 GitOrigin-RevId: abafa08103080fadb6ec496805073229a72e0722
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5e3abfb477
commit
08209238ef
@@ -2217,10 +2217,6 @@ f:git4idea.diff.GitSubmoduleDiffRequestProvider
|
||||
- canCreate(com.intellij.openapi.project.Project,com.intellij.openapi.vcs.changes.Change):Z
|
||||
- isEquals(com.intellij.openapi.vcs.changes.Change,com.intellij.openapi.vcs.changes.Change):com.intellij.util.ThreeState
|
||||
- process(com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer,com.intellij.openapi.util.UserDataHolder,com.intellij.openapi.progress.ProgressIndicator):com.intellij.diff.requests.DiffRequest
|
||||
f:git4idea.fetch.GitFetchErrorFormatter
|
||||
- sf:INSTANCE:git4idea.fetch.GitFetchErrorFormatter
|
||||
- f:format(java.lang.String):java.lang.String
|
||||
- f:isCheckedOutBranchError(java.lang.String):Z
|
||||
git4idea.fetch.GitFetchHandler
|
||||
- sf:Companion:git4idea.fetch.GitFetchHandler$Companion
|
||||
- sf:EP_NAME:com.intellij.openapi.extensions.ExtensionPointName
|
||||
|
||||
@@ -1074,8 +1074,6 @@ action.Git.Stage.Disable.description=Disable staging area and switch to changeli
|
||||
branches.fetch.finished=Fetched {0} targets
|
||||
branches.update.failed=Update failed
|
||||
branches.updating.process=Updating branches\u2026
|
||||
branches.update.anyway.notification.action=Update anyway
|
||||
branches.update.error.branch.checked.out=Branch ''{0}'' is checked out at ''{1}''
|
||||
branches.checking.existing.commits.process=Checking existing commits\u2026
|
||||
branches.create.new.branch.dialog.title=Create New Branch
|
||||
branches.tag.0=Tag ''{0}''
|
||||
|
||||
@@ -84,7 +84,6 @@ internal class GitAddCommitToRemoteBranchOperation(
|
||||
remote = remote,
|
||||
refspec = "refs/heads/$branchName:refs/remotes/${remote.name}/$branchName",
|
||||
unshallow = false,
|
||||
updateHeadOk = false,
|
||||
)
|
||||
|
||||
val result = GitFetchSupport.fetchSupport(project).fetch(listOf(fetchSpec))
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package git4idea.fetch
|
||||
|
||||
import com.intellij.openapi.ui.getPresentablePath
|
||||
import git4idea.branch.GitBranchUtil
|
||||
import git4idea.i18n.GitBundle
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object GitFetchErrorFormatter {
|
||||
private val BRANCH_CHECKED_OUT_PATTERN = Pattern.compile("refusing to fetch into branch '(.*)' checked out at '(.*)'")
|
||||
|
||||
fun isCheckedOutBranchError(errorMessage: @Nls String): Boolean {
|
||||
return BRANCH_CHECKED_OUT_PATTERN.matcher(errorMessage).matches()
|
||||
}
|
||||
|
||||
fun format(errorMessage: @Nls String): @Nls String {
|
||||
return tryFormatCheckedOutBranchError(errorMessage) ?: errorMessage
|
||||
}
|
||||
|
||||
private fun tryFormatCheckedOutBranchError(errorMessage: @Nls String): @Nls String? {
|
||||
val matcher = BRANCH_CHECKED_OUT_PATTERN.matcher(errorMessage)
|
||||
if (!matcher.matches()) return null
|
||||
|
||||
val branchRef = matcher.group(1)
|
||||
val path = matcher.group(2)
|
||||
|
||||
val shortBranchName = GitBranchUtil.stripRefsPrefix(branchRef)
|
||||
val shortPath = getPresentablePath(path)
|
||||
|
||||
return GitBundle.message("branches.update.error.branch.checked.out", shortBranchName, shortPath)
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ data class GitFetchSpec(
|
||||
val remote: GitRemote,
|
||||
val refspec: String? = null,
|
||||
val unshallow: Boolean = false,
|
||||
val updateHeadOk: Boolean = false, // allow fetch to update the head which corresponds to the current branch
|
||||
val fetchTagsMode: GitFetchTagsMode = GitVcsSettings.getInstance(repository.project).fetchTagsMode,
|
||||
val authMode: AuthenticationMode? = null,
|
||||
) {
|
||||
@@ -26,13 +25,11 @@ data class GitFetchSpec(
|
||||
add(NO_RECURSE_SUBMODULES)
|
||||
fetchTagsMode.param?.let { add(it) }
|
||||
if (unshallow) add(UNSHALLOW)
|
||||
if (updateHeadOk) add(UPDATE_HEAD_OK)
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NO_RECURSE_SUBMODULES = "--recurse-submodules=no"
|
||||
private const val UNSHALLOW = "--unshallow"
|
||||
private const val UPDATE_HEAD_OK = "--update-head-ok"
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.intellij.dvcs.MultiRootMessage
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.externalProcessAuthHelper.AuthenticationGate
|
||||
import com.intellij.externalProcessAuthHelper.RestrictingAuthenticationGate
|
||||
import com.intellij.notification.NotificationAction
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
@@ -24,9 +23,7 @@ import com.intellij.openapi.util.text.HtmlChunk
|
||||
import com.intellij.openapi.vcs.VcsException
|
||||
import com.intellij.openapi.vcs.VcsNotifier
|
||||
import com.intellij.openapi.vcs.changes.actions.VcsStatisticsCollector
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import git4idea.GitDisposable
|
||||
import git4idea.GitNotificationIdsHolder
|
||||
import git4idea.GitUtil.findRemoteByName
|
||||
import git4idea.GitUtil.mention
|
||||
@@ -38,8 +35,6 @@ import git4idea.config.GitConfigUtil
|
||||
import git4idea.i18n.GitBundle
|
||||
import git4idea.repo.GitRemote
|
||||
import git4idea.repo.GitRepository
|
||||
import git4idea.ui.branch.fetchBranches
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.util.concurrent.CancellationException
|
||||
@@ -285,10 +280,7 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
|
||||
fun error(): @Nls String {
|
||||
val errorMessage = multiRemoteMessage(true)
|
||||
for ((remote, result) in results) {
|
||||
if (result.error != null) {
|
||||
val presentableErrorMessage = GitFetchErrorFormatter.format(result.error)
|
||||
errorMessage.append(remote, presentableErrorMessage)
|
||||
}
|
||||
if (result.error != null) errorMessage.append(remote, result.error)
|
||||
}
|
||||
return errorMessage.asString()
|
||||
}
|
||||
@@ -379,20 +371,6 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
|
||||
val notification = VcsNotifier.standardNotification()
|
||||
.createNotification(title, message, NotificationType.ERROR)
|
||||
notification.setDisplayId(GitNotificationIdsHolder.FETCH_RESULT_ERROR)
|
||||
|
||||
if (allFailuresAreCheckedOutBranches()) {
|
||||
notification.addAction(NotificationAction.createSimpleExpiring(
|
||||
GitBundle.message("branches.update.anyway.notification.action")) {
|
||||
notification.expire()
|
||||
GitDisposable.getInstance(project).coroutineScope.launch {
|
||||
withBackgroundProgress(project, GitBundle.message("branches.updating.process")) {
|
||||
val failedDueToCheckedOutBranch = findFailedDueToCheckedOutBranch()
|
||||
fetchBranches(project, failedDueToCheckedOutBranch, updateHeadOk = true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
vcsNotifier.notify(notification)
|
||||
}
|
||||
|
||||
@@ -427,25 +405,5 @@ internal class GitFetchSupportImpl(private val project: Project) : GitFetchSuppo
|
||||
sb.appendRaw(text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun allFailuresAreCheckedOutBranches(): Boolean {
|
||||
return getAllFailedResults().all { GitFetchErrorFormatter.isCheckedOutBranchError(it.error) }
|
||||
}
|
||||
|
||||
private fun findFailedDueToCheckedOutBranch(): List<GitFetchSpec> {
|
||||
return getAllFailedResults()
|
||||
.filter { GitFetchErrorFormatter.isCheckedOutBranchError(it.error) }
|
||||
.map { it.fetchSpec }
|
||||
}
|
||||
|
||||
private fun getAllFailedResults(): List<FailedSingleRemoteResult> {
|
||||
return results.values.flatMap { repoResult ->
|
||||
repoResult.results.values.mapNotNull { singleRemoteResult ->
|
||||
singleRemoteResult.error?.let { error -> FailedSingleRemoteResult(singleRemoteResult.fetchSpec, error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class FailedSingleRemoteResult(val fetchSpec: GitFetchSpec, val error: @Nls String)
|
||||
}
|
||||
}
|
||||
@@ -19,41 +19,64 @@ import git4idea.branch.GitBranchPair
|
||||
import git4idea.branch.GitBranchUtil
|
||||
import git4idea.branch.GitNewBranchDialog
|
||||
import git4idea.config.GitVcsSettings
|
||||
import git4idea.fetch.GitFetchResult
|
||||
import git4idea.fetch.GitFetchSpec
|
||||
import git4idea.fetch.GitFetchSupport
|
||||
import git4idea.i18n.GitBundle
|
||||
import git4idea.repo.GitRepository
|
||||
import git4idea.update.GitUpdateExecutionProcess
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import javax.swing.Icon
|
||||
|
||||
@JvmOverloads
|
||||
internal fun createOrCheckoutNewBranch(
|
||||
project: Project,
|
||||
repositories: Collection<GitRepository>,
|
||||
startPoint: String,
|
||||
@Nls(capitalization = Nls.Capitalization.Title)
|
||||
title: String = GitBundle.message("branches.create.new.branch.dialog.title"),
|
||||
initialName: String? = null,
|
||||
) {
|
||||
val options = GitNewBranchDialog(project, repositories, title, initialName, showResetOption = true, localConflictsAllowed = true).showAndGetOptions()
|
||||
?: return
|
||||
internal fun createOrCheckoutNewBranch(project: Project,
|
||||
repositories: Collection<GitRepository>,
|
||||
startPoint: String,
|
||||
@Nls(capitalization = Nls.Capitalization.Title)
|
||||
title: String = GitBundle.message("branches.create.new.branch.dialog.title"),
|
||||
initialName: String? = null) {
|
||||
val options = GitNewBranchDialog(project, repositories, title, initialName, showResetOption = true, localConflictsAllowed = true).showAndGetOptions() ?: return
|
||||
GitBranchCheckoutOperation(project, options.repositories).perform(startPoint, options)
|
||||
}
|
||||
|
||||
internal fun updateBranches(project: Project, repositories: Collection<GitRepository>, localBranchNames: List<String>): Job {
|
||||
val repoToTrackingInfos =
|
||||
repositories.associateWith { it.branchTrackInfos.filter { info -> localBranchNames.contains(info.localBranch.name) } }
|
||||
if (repoToTrackingInfos.isEmpty()) return CompletableDeferred(Unit)
|
||||
|
||||
return GitDisposable.getInstance(project).coroutineScope.launch {
|
||||
withBackgroundProgress(project, GitBundle.message("branches.updating.process")) {
|
||||
val (fetchTargets, updateProcessTargets) = prepareUpdateTargets(repositories, localBranchNames)
|
||||
// If a branch is checked out, do update via GitUpdateExecutionProcess
|
||||
val updateProcessTargets = HashMap<GitRepository, GitBranchPair>()
|
||||
// Otherwise, perform fetch using remote:local refspec
|
||||
val fetchTargets = mutableListOf<GitFetchSpec>()
|
||||
|
||||
for ((repo, trackingInfos) in repoToTrackingInfos) {
|
||||
val currentBranch = repo.currentBranch
|
||||
for (trackingInfo in trackingInfos) {
|
||||
val localBranch = trackingInfo.localBranch
|
||||
val remoteBranch = trackingInfo.remoteBranch
|
||||
if (localBranch == currentBranch) {
|
||||
updateProcessTargets[repo] = GitBranchPair(currentBranch, remoteBranch)
|
||||
}
|
||||
else {
|
||||
// Fast-forward all non-current branches in the selection
|
||||
val localBranchName = localBranch.name
|
||||
val remoteBranchName = remoteBranch.nameForRemoteOperations
|
||||
fetchTargets.add(GitFetchSpec(repo, trackingInfo.remote, "$remoteBranchName:$localBranchName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchTargets.isNotEmpty()) {
|
||||
fetchBranches(project, fetchTargets, updateHeadOk = false)
|
||||
val fetchSuccessful = coroutineToIndicator {
|
||||
GitFetchSupport.fetchSupport(project).fetch(fetchTargets).showNotificationIfFailed(GitBundle.message("branches.update.failed"))
|
||||
}
|
||||
if (fetchSuccessful) {
|
||||
VcsNotifier.getInstance(project).notifySuccess(BRANCHES_UPDATE_SUCCESSFUL, "", GitBundle.message("branches.fetch.finished", fetchTargets.size))
|
||||
}
|
||||
}
|
||||
|
||||
if (updateProcessTargets.isNotEmpty()) {
|
||||
@@ -67,61 +90,6 @@ internal fun updateBranches(project: Project, repositories: Collection<GitReposi
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun prepareUpdateTargets(
|
||||
repositories: Collection<GitRepository>, localBranchNames: List<String>,
|
||||
): UpdateTargets {
|
||||
val repoToTrackingInfos =
|
||||
repositories.associateWith { it.branchTrackInfos.filter { info -> localBranchNames.contains(info.localBranch.name) } }
|
||||
|
||||
// If a branch is checked out, do update via GitUpdateExecutionProcess
|
||||
val updateProcessTargets = HashMap<GitRepository, GitBranchPair>()
|
||||
// Otherwise, perform fetch using remote:local refspec
|
||||
val fetchTargets = mutableListOf<GitFetchSpec>()
|
||||
|
||||
for ((repo, trackingInfos) in repoToTrackingInfos) {
|
||||
val currentBranch = repo.currentBranch
|
||||
for (trackingInfo in trackingInfos) {
|
||||
val localBranch = trackingInfo.localBranch
|
||||
val remoteBranch = trackingInfo.remoteBranch
|
||||
if (localBranch == currentBranch) {
|
||||
updateProcessTargets[repo] = GitBranchPair(currentBranch, remoteBranch)
|
||||
}
|
||||
else {
|
||||
// Fast-forward all non-current branches in the selection
|
||||
val localBranchName = localBranch.name
|
||||
val remoteBranchName = remoteBranch.nameForRemoteOperations
|
||||
fetchTargets.add(GitFetchSpec(repo, trackingInfo.remote, "$remoteBranchName:$localBranchName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UpdateTargets(fetchTargets, updateProcessTargets)
|
||||
}
|
||||
|
||||
internal suspend fun fetchBranches(
|
||||
project: Project,
|
||||
fetchTargets: List<GitFetchSpec>,
|
||||
updateHeadOk: Boolean,
|
||||
): GitFetchResult {
|
||||
return withContext(Dispatchers.Default) {
|
||||
val specsToFetch = if (updateHeadOk) {
|
||||
fetchTargets.map { it.copy(updateHeadOk = true) }
|
||||
}
|
||||
else {
|
||||
fetchTargets
|
||||
}
|
||||
val result = coroutineToIndicator {
|
||||
GitFetchSupport.fetchSupport(project).fetch(specsToFetch)
|
||||
}
|
||||
val fetchSuccessful = result.showNotificationIfFailed(GitBundle.message("branches.update.failed"))
|
||||
if (fetchSuccessful) {
|
||||
VcsNotifier.getInstance(project).notifySuccess(BRANCHES_UPDATE_SUCCESSFUL, "", GitBundle.message("branches.fetch.finished", specsToFetch.size))
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
internal fun isTrackingInfosExist(branchNames: List<String>, repositories: Collection<GitRepository>) =
|
||||
repositories
|
||||
.flatMap(GitRepository::getBranchTrackInfos)
|
||||
@@ -133,19 +101,15 @@ internal fun hasRemotes(project: Project): Boolean {
|
||||
|
||||
internal fun hasAnyRemotes(repositories: Collection<GitRepository>): Boolean = repositories.any { it.remotes.isNotEmpty() }
|
||||
|
||||
internal fun hasTrackingConflicts(
|
||||
conflictingLocalBranches: Map<GitRepository, GitLocalBranch>,
|
||||
remoteBranchName: String,
|
||||
): Boolean =
|
||||
internal fun hasTrackingConflicts(conflictingLocalBranches: Map<GitRepository, GitLocalBranch>,
|
||||
remoteBranchName: String): Boolean =
|
||||
conflictingLocalBranches.any { (repo, branch) ->
|
||||
val trackInfo = GitBranchUtil.getTrackInfoForBranch(repo, branch)
|
||||
trackInfo != null && !GitReference.BRANCH_NAME_HASHING_STRATEGY.equals(remoteBranchName, trackInfo.remoteBranch.name)
|
||||
}
|
||||
|
||||
internal abstract class BranchGroupingAction(
|
||||
private val key: GroupingKey,
|
||||
icon: Icon? = null,
|
||||
) : ToggleAction(key.text, key.description, icon), DumbAware {
|
||||
internal abstract class BranchGroupingAction(private val key: GroupingKey,
|
||||
icon: Icon? = null) : ToggleAction(key.text, key.description, icon), DumbAware {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.EDT
|
||||
}
|
||||
@@ -160,9 +124,3 @@ internal abstract class BranchGroupingAction(
|
||||
GitVcsSettings.getInstance(project).setBranchGroupingSettings(key, state)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal data class UpdateTargets(
|
||||
val fetchTargets: List<GitFetchSpec>,
|
||||
val updateProcessTargets: Map<GitRepository, GitBranchPair>,
|
||||
)
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package git4idea.ui.branch
|
||||
|
||||
import com.intellij.openapi.ui.getPresentablePath
|
||||
import com.intellij.openapi.vcs.Executor.cd
|
||||
import git4idea.i18n.GitBundle
|
||||
import git4idea.repo.GitWorkTreeBaseTest
|
||||
import git4idea.test.assertCurrentRevision
|
||||
import git4idea.test.checkout
|
||||
import git4idea.test.git
|
||||
import git4idea.test.initRepo
|
||||
import git4idea.test.tac
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
class GitFetchBranchesCheckedOutElsewhereTest : GitWorkTreeBaseTest() {
|
||||
lateinit var bro: Path
|
||||
lateinit var parent: Path
|
||||
|
||||
override fun initMainRepo(): Path {
|
||||
val mainDir = testNioRoot.resolve("main")
|
||||
Files.createDirectories(mainDir)
|
||||
initRepo(project, mainDir, true)
|
||||
return mainDir
|
||||
}
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
parent = prepareRemoteRepo(myRepo)
|
||||
bro = createBroRepo("bro", parent)
|
||||
}
|
||||
|
||||
fun `test fetch blocked by checked out branch then retries with update-head-ok flag`() {
|
||||
cd(myMainRoot)
|
||||
git("checkout -b feature")
|
||||
tac("a.txt", "main")
|
||||
git("push -u origin feature")
|
||||
|
||||
cd(bro)
|
||||
git("fetch origin feature")
|
||||
git("checkout feature")
|
||||
val broCommit = tac("b.txt", "bro")
|
||||
git("push -u origin feature")
|
||||
|
||||
myRepo.update()
|
||||
|
||||
val fetchTargets = prepareUpdateTargets(listOf(myRepo), listOf("feature")).fetchTargets
|
||||
runBlocking { fetchBranches(project, fetchTargets, updateHeadOk = false) }
|
||||
|
||||
val title = GitBundle.message("branches.update.failed")
|
||||
val message = GitBundle.message("branches.update.error.branch.checked.out", "feature", getPresentablePath(myMainRoot.toString()))
|
||||
assertErrorNotification(title, message, actions = listOf(
|
||||
GitBundle.message("branches.update.anyway.notification.action")
|
||||
))
|
||||
|
||||
runBlocking { fetchBranches(project, fetchTargets, updateHeadOk = true) }
|
||||
|
||||
assertSuccessfulNotification(GitBundle.message("branches.fetch.finished", 1))
|
||||
|
||||
cd(myMainRoot)
|
||||
checkout("-b another")
|
||||
myRepo.checkout("feature")
|
||||
myRepo.assertCurrentRevision(broCommit)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user