mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 17:20:55 +07:00
[gitlab] allow adding ssh or http remote during MR branch checkout
IDEA-323956 Fixed GitOrigin-RevId: ed91115e87a2075cd4866957dec0f965868d2a14
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7245fba005
commit
ae13bc5990
@@ -4,4 +4,5 @@ fragment project on Project {
|
|||||||
path
|
path
|
||||||
fullPath
|
fullPath
|
||||||
httpUrlToRepo
|
httpUrlToRepo
|
||||||
|
sshUrlToRepo
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,8 @@ data class GitLabProjectDTO(
|
|||||||
val nameWithNamespace: @Nls String,
|
val nameWithNamespace: @Nls String,
|
||||||
val path: @NlsSafe String,
|
val path: @NlsSafe String,
|
||||||
val fullPath: @NlsSafe String,
|
val fullPath: @NlsSafe String,
|
||||||
val httpUrlToRepo: String?,
|
val httpUrlToRepo: @NlsSafe String?,
|
||||||
)
|
val sshUrlToRepo: @NlsSafe String?
|
||||||
|
) {
|
||||||
|
val ownerPath: @NlsSafe String = fullPath.split("/").dropLast(1).joinToString("/")
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
package org.jetbrains.plugins.gitlab.mergerequest.ui.details.model
|
package org.jetbrains.plugins.gitlab.mergerequest.ui.details.model
|
||||||
|
|
||||||
|
import com.intellij.collaboration.async.combineState
|
||||||
import com.intellij.collaboration.async.launchNow
|
import com.intellij.collaboration.async.launchNow
|
||||||
import com.intellij.collaboration.async.modelFlow
|
import com.intellij.collaboration.async.modelFlow
|
||||||
import com.intellij.collaboration.messages.CollaborationToolsBundle
|
import com.intellij.collaboration.messages.CollaborationToolsBundle
|
||||||
@@ -12,7 +13,6 @@ import com.intellij.openapi.diagnostic.thisLogger
|
|||||||
import com.intellij.openapi.progress.ProgressIndicator
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
import com.intellij.openapi.progress.Task
|
import com.intellij.openapi.progress.Task
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.NlsSafe
|
|
||||||
import com.intellij.openapi.vcs.VcsNotifier
|
import com.intellij.openapi.vcs.VcsNotifier
|
||||||
import com.intellij.util.childScope
|
import com.intellij.util.childScope
|
||||||
import git4idea.GitUtil
|
import git4idea.GitUtil
|
||||||
@@ -24,7 +24,10 @@ import git4idea.repo.GitRepository
|
|||||||
import git4idea.repo.GitRepositoryChangeListener
|
import git4idea.repo.GitRepositoryChangeListener
|
||||||
import git4idea.ui.branch.GitBranchPopupActions
|
import git4idea.ui.branch.GitBranchPopupActions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectDTO
|
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectDTO
|
||||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest
|
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest
|
||||||
import org.jetbrains.plugins.gitlab.util.GitLabBundle
|
import org.jetbrains.plugins.gitlab.util.GitLabBundle
|
||||||
@@ -40,16 +43,14 @@ internal class GitLabMergeRequestBranchesViewModel(
|
|||||||
|
|
||||||
private val cs: CoroutineScope = parentCs.childScope()
|
private val cs: CoroutineScope = parentCs.childScope()
|
||||||
|
|
||||||
private val targetProject: StateFlow<GitLabProjectDTO> = mergeRequest.targetProject
|
override val sourceBranch: StateFlow<String> = with(mergeRequest) {
|
||||||
private val sourceProject: StateFlow<GitLabProjectDTO?> = mergeRequest.sourceProject
|
combineState(cs, targetProject, sourceProject, sourceBranch) { targetProject, sourceProject, sourceBranch ->
|
||||||
|
if (sourceProject == null) return@combineState ""
|
||||||
override val sourceBranch: StateFlow<String> =
|
if (targetProject == sourceProject) return@combineState sourceBranch
|
||||||
combine(targetProject, sourceProject, mergeRequest.sourceBranch) { targetProject, sourceProject, sourceBranch ->
|
val sourceProjectOwner = sourceProject.ownerPath
|
||||||
if (sourceProject == null) return@combine ""
|
return@combineState "$sourceProjectOwner:$sourceBranch"
|
||||||
if (targetProject == sourceProject) return@combine sourceBranch
|
}
|
||||||
val sourceProjectOwner = sourceProject.fullPath.split("/").dropLast(1).joinToString("/")
|
}
|
||||||
return@combine "$sourceProjectOwner:$sourceBranch"
|
|
||||||
}.stateIn(cs, SharingStarted.Lazily, mergeRequest.sourceBranch.value)
|
|
||||||
|
|
||||||
override val isCheckedOut: SharedFlow<Boolean> = callbackFlow {
|
override val isCheckedOut: SharedFlow<Boolean> = callbackFlow {
|
||||||
val cs = this
|
val cs = this
|
||||||
@@ -76,14 +77,12 @@ internal class GitLabMergeRequestBranchesViewModel(
|
|||||||
true
|
true
|
||||||
) {
|
) {
|
||||||
override fun run(indicator: ProgressIndicator) {
|
override fun run(indicator: ProgressIndicator) {
|
||||||
val sourceBranch = sourceBranch.value
|
val sourceProject = mergeRequest.sourceProject.value ?: return
|
||||||
val sourceProject = sourceProject.value ?: return
|
val sourceBranch = mergeRequest.sourceBranch.value
|
||||||
val httpForkUrl = sourceProject.httpUrlToRepo ?: return
|
|
||||||
val pullRequestAuthor = mergeRequest.author
|
|
||||||
|
|
||||||
val headRemote = git.findOrCreateRemote(repository, pullRequestAuthor.username, httpForkUrl)
|
val headRemote = git.findOrCreateRemote(repository, sourceProject)
|
||||||
if (headRemote == null) {
|
if (headRemote == null) {
|
||||||
notifyRemoteError(vcsNotifier, httpForkUrl)
|
notifyRemoteError(vcsNotifier, sourceProject)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,10 +97,15 @@ internal class GitLabMergeRequestBranchesViewModel(
|
|||||||
}.queue()
|
}.queue()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyRemoteError(vcsNotifier: VcsNotifier, httpForkUrl: @NlsSafe String?) {
|
private fun notifyRemoteError(vcsNotifier: VcsNotifier, project: GitLabProjectDTO) {
|
||||||
var failedMessage = GitLabBundle.message("merge.request.branch.checkout.resolve.remote.failed")
|
var failedMessage = GitLabBundle.message("merge.request.branch.checkout.resolve.remote.failed")
|
||||||
if (httpForkUrl != null) {
|
val httpUrl = project.httpUrlToRepo
|
||||||
failedMessage += "\n$httpForkUrl"
|
val sshUrl = project.sshUrlToRepo
|
||||||
|
if (httpUrl != null) {
|
||||||
|
failedMessage += "\n$httpUrl"
|
||||||
|
}
|
||||||
|
if (sshUrl != null) {
|
||||||
|
failedMessage += "\n$sshUrl"
|
||||||
}
|
}
|
||||||
vcsNotifier.notifyError(
|
vcsNotifier.notifyError(
|
||||||
MERGE_REQUEST_CANNOT_SET_TRACKING_BRANCH,
|
MERGE_REQUEST_CANNOT_SET_TRACKING_BRANCH,
|
||||||
@@ -110,39 +114,6 @@ internal class GitLabMergeRequestBranchesViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isBranchCheckedOut(repository: GitRepository, sourceBranch: String): Boolean {
|
|
||||||
val currentBranchName = repository.currentBranchName
|
|
||||||
return currentBranchName == sourceBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findRemote(repository: GitRepository, httpUrl: String?): GitRemote? =
|
|
||||||
repository.remotes.find {
|
|
||||||
it.firstUrl != null && (it.firstUrl == httpUrl ||
|
|
||||||
it.firstUrl == httpUrl + GitUtil.DOT_GIT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement logic use sshUrlToRepo
|
|
||||||
private fun Git.findOrCreateRemote(repository: GitRepository, remoteName: String, httpUrl: String?): GitRemote? {
|
|
||||||
val existingRemote = findRemote(repository, httpUrl)
|
|
||||||
if (existingRemote != null) return existingRemote
|
|
||||||
|
|
||||||
if (httpUrl != null && repository.remotes.any { it.name == remoteName }) {
|
|
||||||
return createRemote(repository, "pull_$remoteName", httpUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
|
||||||
httpUrl != null -> createRemote(repository, remoteName, httpUrl)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Git.createRemote(repository: GitRepository, remoteName: String, url: String): GitRemote? =
|
|
||||||
with(repository) {
|
|
||||||
addRemote(this, remoteName, url)
|
|
||||||
update()
|
|
||||||
remotes.find { it.name == remoteName }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showBranches() {
|
override fun showBranches() {
|
||||||
cs.launchNow {
|
cs.launchNow {
|
||||||
val source = sourceBranch.value
|
val source = sourceBranch.value
|
||||||
@@ -154,4 +125,68 @@ internal class GitLabMergeRequestBranchesViewModel(
|
|||||||
companion object {
|
companion object {
|
||||||
private const val MERGE_REQUEST_CANNOT_SET_TRACKING_BRANCH = "gitlab.merge.request.cannot.set.tracking.branch"
|
private const val MERGE_REQUEST_CANNOT_SET_TRACKING_BRANCH = "gitlab.merge.request.cannot.set.tracking.branch"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBranchCheckedOut(repository: GitRepository, sourceBranch: String): Boolean {
|
||||||
|
val currentBranchName = repository.currentBranchName
|
||||||
|
return currentBranchName == sourceBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Git.findOrCreateRemote(repository: GitRepository, project: GitLabProjectDTO): GitRemote? {
|
||||||
|
val existingRemote = findRemote(repository, project)
|
||||||
|
if (existingRemote != null) return existingRemote
|
||||||
|
|
||||||
|
val httpUrl = project.httpUrlToRepo
|
||||||
|
val sshUrl = project.sshUrlToRepo
|
||||||
|
val preferHttp = shouldAddHttpRemote(repository)
|
||||||
|
return if (preferHttp && httpUrl != null) {
|
||||||
|
createRemote(repository, project.ownerPath, httpUrl)
|
||||||
|
}
|
||||||
|
else if (sshUrl != null) {
|
||||||
|
createRemote(repository, project.ownerPath, sshUrl)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findRemote(repository: GitRepository, project: GitLabProjectDTO): GitRemote? =
|
||||||
|
repository.remotes.find {
|
||||||
|
val url = it.firstUrl
|
||||||
|
url != null && (url.removeSuffix("/").removeSuffix(GitUtil.DOT_GIT).endsWith(project.fullPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldAddHttpRemote(repository: GitRepository): Boolean {
|
||||||
|
val preferredRemoteUrl = repository.remotes.find { it.name == "origin" }?.firstUrl
|
||||||
|
?: repository.remotes.firstNotNullOfOrNull { it.firstUrl }
|
||||||
|
if (preferredRemoteUrl != null) {
|
||||||
|
return preferredRemoteUrl.startsWith("http")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Git.createRemote(repository: GitRepository, remoteName: String, url: String): GitRemote? {
|
||||||
|
val actualName = findNameForRemote(repository, remoteName) ?: return null
|
||||||
|
return with(repository) {
|
||||||
|
addRemote(this, actualName, url)
|
||||||
|
update()
|
||||||
|
remotes.find { it.name == actualName }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the [preferredName] if it is not taken or adds a numerical index to it
|
||||||
|
*/
|
||||||
|
private fun findNameForRemote(repository: GitRepository, preferredName: String): String? {
|
||||||
|
val exitingNames = repository.remotes.mapTo(mutableSetOf(), GitRemote::getName)
|
||||||
|
if (!exitingNames.contains(preferredName)) {
|
||||||
|
return preferredName
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return sequenceOf(1..Int.MAX_VALUE).map {
|
||||||
|
"${preferredName}_$it"
|
||||||
|
}.find {
|
||||||
|
exitingNames.contains(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user