fix [gitlab]: Gitlab rest api calls now use projectId where possible

* GitLabProjectCoordinates are not used as an argument for Api methods anymore
* rest api methods use projectId to create request path (except for the initial project request)
* initial project request uses encoded `owner/project` to get the path, and expected to be correctly redirected in case if the project was moved
* fixes IJPL-81282 by using ID instead of full path in REST API calls
* fixes project renaming issue (IJPL-82568) by calling REST endpoint with auto-redirect to get correct project path

#IJPL-81282 Fixed
#IJPL-82568 Fixed

GitOrigin-RevId: 1529c3ddaa855539740a8c192e565e94b8cd9751
This commit is contained in:
Valeria Golovina
2026-02-14 02:09:24 +00:00
committed by intellij-monorepo-bot
parent 432c6eb55e
commit da63796d14
46 changed files with 668 additions and 434 deletions
@@ -20,11 +20,14 @@ import java.util.UUID
/**
* A low-level helper representing a GitLab project, to which the app was authorized to connect
*
* @param actualProjectCoordinates coordinates of the project e.g., after rename
*/
class GitLabProjectConnection(
project: Project,
private val scope: CoroutineScope,
override val repo: GitLabProjectMapping,
actualProjectCoordinates: GitLabProjectCoordinates,
glProject: GitLabProjectDTO,
override val account: GitLabAccount,
val currentUser: GitLabUserDTO,
@@ -36,9 +39,17 @@ class GitLabProjectConnection(
val tokenRefreshFlow: Flow<Unit> = tokenState.drop(1).map { }
val imageLoader: GitLabImageLoader = GitLabImageLoader(apiClient, repo.repository)
val imageLoader: GitLabImageLoader = GitLabImageLoader(apiClient)
val projectData: GitLabProject = GitLabLazyProject(project, scope, apiClient, glMetadata, repo, glProject, currentUser, tokenRefreshFlow)
val projectData: GitLabProject = GitLabLazyProject(project,
scope,
apiClient,
glMetadata,
glProject,
currentUser,
tokenRefreshFlow,
actualProjectCoordinates,
repo.remote)
val serverVersion: GitLabVersion? = glMetadata?.version
@@ -11,11 +11,14 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import org.jetbrains.plugins.gitlab.GitLabProjectsManager
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectDTO
import org.jetbrains.plugins.gitlab.api.request.findProject
import org.jetbrains.plugins.gitlab.api.request.getCurrentUser
import org.jetbrains.plugins.gitlab.api.request.getProject
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountManager
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
@Service(Service.Level.PROJECT)
internal class GitLabProjectConnectionManager(project: Project, cs: CoroutineScope) :
@@ -28,12 +31,37 @@ internal class GitLabProjectConnectionManager(project: Project, cs: CoroutineSco
{ projectsManager },
{ accountManager }
) { glProjectMapping, account, tokenState ->
val scope = this
val apiClient = service<GitLabApiManager>().getClient(account.server) { tokenState.value }
val glMetadata = apiClient.getMetadataOrNull()
val currentUser = apiClient.graphQL.getCurrentUser()
val glProject = apiClient.graphQL.findProject(glProjectMapping.repository).body()
?: throw error("Unable to load the project")
GitLabProjectConnection(project, this, glProjectMapping, glProject, account, currentUser, apiClient, glMetadata, tokenState)
var projectCoordinates: GitLabProjectCoordinates = glProjectMapping.repository
val projectData: GitLabProjectDTO
val originalProject = apiClient.graphQL.findProject(projectCoordinates.projectPath).body()
if (originalProject != null) {
projectCoordinates = glProjectMapping.repository
projectData = originalProject
}
else {
val restProjectResponse = apiClient.rest.getProject(projectCoordinates.projectPath).body()
val actualProjectPath = GitLabProjectPath.extractProjectPath(restProjectResponse.pathWithNamespace)
?: error("Unable to parse the project path: ${restProjectResponse.pathWithNamespace}")
projectCoordinates = projectCoordinates.copy(projectPath = actualProjectPath)
projectData = apiClient.graphQL.findProject(actualProjectPath).body()
?: error("Could not find the project $actualProjectPath. Check if the project exists and you have access to it.")
}
GitLabProjectConnection(project,
scope,
glProjectMapping,
projectCoordinates,
projectData,
account,
currentUser,
apiClient,
glMetadata,
tokenState)
}
private val delegate = SingleHostedGitRepositoryConnectionManagerImpl(cs, connectionFactory)
@@ -20,8 +20,9 @@ data class GitLabProjectCoordinates(override val serverPath: GitLabServerPath, v
companion object {
fun create(server: GitLabServerPath, remote: GitRemoteUrlCoordinates): GitLabProjectCoordinates? {
val projectPath = GitLabProjectPath.create(server, remote) ?: return null
val projectPath = GitLabProjectPath.create(server, remote.url) ?: return null
return GitLabProjectCoordinates(server, projectPath)
}
}
}
@@ -0,0 +1,6 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab.api.dto
data class GitLabProjectRestDTO(
val pathWithNamespace: String,
)
@@ -16,7 +16,6 @@ import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabGQLQuery
import org.jetbrains.plugins.gitlab.api.GitLabId
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabServerMetadata
import org.jetbrains.plugins.gitlab.api.GitLabVersion
import org.jetbrains.plugins.gitlab.api.SinceGitLab
@@ -27,24 +26,26 @@ import org.jetbrains.plugins.gitlab.api.dto.GitLabNamespaceRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectForCloneDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectIsForkedDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabProjectsForCloneDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabRepositoryCreationRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabWorkItemDTO
import org.jetbrains.plugins.gitlab.api.dto.WithGitLabNamespace
import org.jetbrains.plugins.gitlab.api.gitLabQuery
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.projectApiUrl
import org.jetbrains.plugins.gitlab.api.withErrorStats
import org.jetbrains.plugins.gitlab.api.withQuery
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
import java.net.URI
import java.net.http.HttpRequest.BodyPublishers
import java.net.http.HttpResponse
@SinceGitLab("12.0")
suspend fun GitLabApi.GraphQL.findProject(project: GitLabProjectCoordinates): HttpResponse<out GitLabProjectDTO?> {
suspend fun GitLabApi.GraphQL.findProject(projectPath: GitLabProjectPath): HttpResponse<out GitLabProjectDTO?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
)
val request = gitLabQuery(GitLabGQLQuery.GET_PROJECT, parameters)
return withErrorStats(GitLabGQLQuery.GET_PROJECT) {
@@ -71,9 +72,9 @@ suspend fun GitLabApi.Rest.createProject(
}
@SinceGitLab("16.9")
private suspend fun GitLabApi.GraphQL.isProjectForked(project: GitLabProjectCoordinates): HttpResponse<out Boolean> {
private suspend fun GitLabApi.GraphQL.isProjectForked(projectPath: GitLabProjectPath): HttpResponse<out Boolean> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
)
val request = gitLabQuery(GitLabGQLQuery.GET_PROJECT_IS_FORKED, parameters)
return withErrorStats(GitLabGQLQuery.GET_PROJECT_IS_FORKED) {
@@ -82,28 +83,35 @@ private suspend fun GitLabApi.GraphQL.isProjectForked(project: GitLabProjectCoor
}
@SinceGitLab("12.0")
private suspend fun GitLabApi.Rest.isProjectForked(project: GitLabProjectCoordinates): HttpResponse<out GitLabProjectIsForkedDTO> {
val uri = project.restApiUri
val request = request(uri).GET().build()
private suspend fun GitLabApi.Rest.isProjectForked(projectPath: GitLabProjectPath): HttpResponse<out GitLabProjectIsForkedDTO> {
val request = request(projectApiUrl(projectPath.fullPath())).GET().build()
return withErrorStats(GitLabApiRequestName.REST_GET_PROJECT_IS_FORKED) {
loadJsonValue(request)
}
}
suspend fun GitLabApi.isProjectForked(project: GitLabProjectCoordinates): Boolean =
@SinceGitLab("3.0", note = "Not an exact version")
suspend fun GitLabApi.Rest.getProject(projectPath: GitLabProjectPath): HttpResponse<out GitLabProjectRestDTO> {
val request = request(projectApiUrl(projectPath.fullPath())).GET().build()
return withErrorStats(GitLabApiRequestName.REST_GET_PROJECT) {
loadJsonValue(request)
}
}
suspend fun GitLabApi.isProjectForked(projectPath: GitLabProjectPath): Boolean =
if (getMetadata().version < GitLabVersion(16, 9)) {
rest.isProjectForked(project).body().isForked
rest.isProjectForked(projectPath).body().isForked
}
else {
graphQL.isProjectForked(project).body()
graphQL.isProjectForked(projectPath).body()
}
@SinceGitLab("13.1", note = "No exact version")
fun GitLabApi.GraphQL.createAllProjectLabelsFlow(project: GitLabProjectCoordinates): Flow<List<GitLabLabelGQLDTO>> =
fun GitLabApi.GraphQL.createAllProjectLabelsFlow(projectPath: GitLabProjectPath): Flow<List<GitLabLabelGQLDTO>> =
ApiPageUtil.createGQLPagesFlow { page ->
val parameters = page.asParameters() + mapOf(
"fullPath" to project.projectPath.fullPath()
"fullPath" to projectPath.fullPath()
)
val request = gitLabQuery(GitLabGQLQuery.GET_PROJECT_LABELS, parameters)
withErrorStats(GitLabGQLQuery.GET_PROJECT_LABELS) {
@@ -112,10 +120,10 @@ fun GitLabApi.GraphQL.createAllProjectLabelsFlow(project: GitLabProjectCoordinat
}.map { it.nodes }
@SinceGitLab("15.2")
fun GitLabApi.GraphQL.createAllWorkItemsFlow(project: GitLabProjectCoordinates): Flow<List<GitLabWorkItemDTO>> =
fun GitLabApi.GraphQL.createAllWorkItemsFlow(projectPath: GitLabProjectPath): Flow<List<GitLabWorkItemDTO>> =
ApiPageUtil.createGQLPagesFlow { page ->
val parameters = page.asParameters() + mapOf(
"fullPath" to project.projectPath.fullPath()
"fullPath" to projectPath.fullPath()
)
val request = gitLabQuery(GitLabGQLQuery.GET_PROJECT_WORK_ITEMS, parameters)
withErrorStats(GitLabGQLQuery.GET_PROJECT_WORK_ITEMS) {
@@ -124,7 +132,7 @@ fun GitLabApi.GraphQL.createAllWorkItemsFlow(project: GitLabProjectCoordinates):
}.map { it.nodes }
@SinceGitLab("7.0", note = "No exact version")
fun getProjectUsersURI(project: GitLabProjectCoordinates) = project.restApiUri.resolveRelative("users")
fun GitLabApi.Rest.getProjectUsersURI(projectId: String): URI = projectApiUrl(projectId).resolveRelative("users")
@SinceGitLab("7.0", note = "No exact version")
suspend fun GitLabApi.Rest.getProjectUsers(uri: URI): HttpResponse<out List<GitLabUserRestDTO>> {
@@ -2,14 +2,11 @@
package org.jetbrains.plugins.gitlab.api
import com.intellij.collaboration.util.resolveRelative
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
import java.net.URI
import java.net.URLEncoder
val GitLabProjectPath.apiId: String
get() = URLEncoder.encode("$owner/$name", Charsets.UTF_8)
fun GitLabApi.projectApiUrl(projectId: String): URI = server.projectApiUri(URLEncoder.encode(projectId, Charsets.UTF_8))
val GitLabProjectCoordinates.restApiUri: URI
get() = serverPath.restApiUri
.resolveRelative("projects/")
.resolveRelative(projectPath.apiId + "/")
fun GitLabServerPath.projectApiUri(projectId: String): URI = restApiUri
.resolveRelative("projects/")
.resolveRelative("$projectId/")
@@ -8,7 +8,6 @@ import com.intellij.util.IconUtil
import com.intellij.util.ui.ImageUtil
import icons.CollaborationToolsIcons
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
import org.jetbrains.plugins.gitlab.api.request.loadImage
import java.awt.Image
@@ -22,15 +21,14 @@ private val LOG = logger<GitLabImageLoader>()
private const val LOADED_GRAVATAR_SIZE: Int = 80
class GitLabImageLoader(
private val apiClient: GitLabApi,
private val gitLabProjectCoordinates: GitLabProjectCoordinates
private val apiClient: GitLabApi
) : AsyncImageIconsProvider.AsyncImageLoader<GitLabUserDTO>, AsyncHtmlImageLoader {
override suspend fun load(key: GitLabUserDTO): Image? {
return key.avatarUrl?.let { avatarUrl ->
val actualUri = when {
avatarUrl.startsWith("http") -> avatarUrl
avatarUrl.startsWith("/avatar") -> "https://secure.gravatar.com$avatarUrl?s=$LOADED_GRAVATAR_SIZE&d=identicon"
else -> gitLabProjectCoordinates.serverPath.uri + avatarUrl
else -> apiClient.server.uri + avatarUrl
}
load(null, actualUri)
}
@@ -5,13 +5,12 @@ import com.intellij.collaboration.api.json.loadJsonList
import com.intellij.collaboration.api.json.loadJsonValue
import com.intellij.collaboration.util.resolveRelative
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.dto.GitLabCommitDetailedRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabCommitRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabDiffDTO
import org.jetbrains.plugins.gitlab.api.getMetadataOrNull
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.projectApiUrl
import org.jetbrains.plugins.gitlab.api.withErrorStats
import org.jetbrains.plugins.gitlab.api.withQuery
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
@@ -19,8 +18,8 @@ import java.net.URI
import java.net.http.HttpResponse
@SinceGitLab("9.0", note = "Not an exact version")
suspend fun GitLabApi.getMergeRequestCommitsURI(project: GitLabProjectCoordinates, mrIid: String): URI {
return project.restApiUri
fun GitLabApi.Rest.getMergeRequestCommitsURI(projectId: String, mrIid: String): URI {
return projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("commits")
@@ -48,11 +47,11 @@ suspend fun GitLabApi.Rest.loadMergeRequestChanges(uri: URI): HttpResponse<out G
}
@SinceGitLab("9.0", deprecatedIn = "15.7", note = "Deprecated in favour of /diffs")
suspend fun GitLabApi.getMergeRequestChangesURI(project: GitLabProjectCoordinates, mrIid: String): URI {
suspend fun GitLabApi.Rest.getMergeRequestChangesURI(projectId: String, mrIid: String): URI {
val metadata = getMetadataOrNull()
requireNotNull(metadata)
return project.restApiUri
return projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("changes")
@@ -67,11 +66,11 @@ suspend fun GitLabApi.Rest.loadMergeRequestDiffs(uri: URI): HttpResponse<out Lis
}
@SinceGitLab("15.7")
suspend fun GitLabApi.getMergeRequestDiffsURI(project: GitLabProjectCoordinates, mrIid: String, page: Int): URI {
suspend fun GitLabApi.Rest.getMergeRequestDiffsURI(projectId: String, mrIid: String, page: Int): URI {
val metadata = getMetadataOrNull()
requireNotNull(metadata)
return project.restApiUri
return projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("diffs")
@@ -89,8 +88,8 @@ suspend fun GitLabApi.Rest.loadCommitDiffs(uri: URI): HttpResponse<out List<GitL
}
@SinceGitLab("7.0")
fun getCommitDiffsURI(project: GitLabProjectCoordinates, commitSha: String): URI =
project.restApiUri
fun GitLabApi.Rest.getCommitDiffsURI(projectId: String, commitSha: String): URI =
projectApiUrl(projectId)
.resolveRelative("repository")
.resolveRelative("commits")
.resolveRelative(commitSha)
@@ -98,10 +97,10 @@ fun getCommitDiffsURI(project: GitLabProjectCoordinates, commitSha: String): URI
@SinceGitLab("7.0")
suspend fun GitLabApi.Rest.loadCommit(
project: GitLabProjectCoordinates,
projectId: String,
commitSha: String,
): HttpResponse<out GitLabCommitDetailedRestDTO> {
val uri = project.restApiUri
val uri = projectApiUrl(projectId)
.resolveRelative("repository")
.resolveRelative("commits")
.resolveRelative(commitSha)
@@ -12,41 +12,41 @@ import com.intellij.collaboration.util.resolveRelative
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabApiUriQueryBuilder
import org.jetbrains.plugins.gitlab.api.GitLabGQLQuery
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.dto.GitLabCommitDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabDiscussionRestDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabNoteRestDTO
import org.jetbrains.plugins.gitlab.api.gitLabQuery
import org.jetbrains.plugins.gitlab.api.loadList
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.projectApiUrl
import org.jetbrains.plugins.gitlab.api.withErrorStats
import org.jetbrains.plugins.gitlab.api.withQuery
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabDiffPositionInput
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.LineRangeDTO
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
import java.net.URI
import java.net.http.HttpRequest
import java.net.http.HttpResponse
@SinceGitLab("10.6")
suspend fun GitLabApi.Rest.loadMergeRequestDiscussions(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
): HttpResponse<out List<GitLabDiscussionRestDTO>> {
val uri = getMergeRequestDiscussionsUri(project, mrIid)
val uri = getMergeRequestDiscussionsUri(projectId, mrIid)
return loadList(GitLabApiRequestName.REST_GET_MERGE_REQUEST_DISCUSSIONS, uri.toString())
}
@SinceGitLab("14.7")
suspend fun GitLabApi.GraphQL.loadMergeRequestCommits(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
mrIid: String,
pagination: GraphQLRequestPagination? = null
): GraphQLConnectionDTO<GitLabCommitDTO>? {
val parameters = pagination.orDefault().asParameters() + mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"mriid" to mrIid
)
val request = gitLabQuery(GitLabGQLQuery.GET_MERGE_REQUEST_COMMITS, parameters)
@@ -59,19 +59,19 @@ private class CommitConnection(pageInfo: GraphQLCursorPageInfoDTO, nodes: List<G
: GraphQLConnectionDTO<GitLabCommitDTO>(pageInfo, nodes)
@SinceGitLab("10.6")
fun getMergeRequestDiscussionsUri(project: GitLabProjectCoordinates, mrIid: String): URI =
project.restApiUri
fun GitLabApi.Rest.getMergeRequestDiscussionsUri(projectId: String, mrIid: String): URI =
projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("discussions")
@SinceGitLab("10.6")
suspend fun GitLabApi.Rest.addNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
body: String,
): HttpResponse<out GitLabDiscussionRestDTO> {
val uri = getMergeRequestDiscussionsUri(project, mrIid).withQuery {
val uri = getMergeRequestDiscussionsUri(projectId, mrIid).withQuery {
"body" eq body
}
val request = request(uri).POST(HttpRequest.BodyPublishers.noBody()).build()
@@ -82,12 +82,12 @@ suspend fun GitLabApi.Rest.addNote(
@SinceGitLab("13.2")
suspend fun GitLabApi.Rest.addDiffNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
position: GitLabDiffPositionInput,
body: String,
): HttpResponse<out GitLabDiscussionRestDTO> {
val uri = getMergeRequestDiscussionsUri(project, mrIid).withQuery {
val uri = getMergeRequestDiscussionsUri(projectId, mrIid).withQuery {
"body" eq body
addDiffPositionParameters(position)
}
@@ -99,12 +99,12 @@ suspend fun GitLabApi.Rest.addDiffNote(
@SinceGitLab("10.6")
suspend fun GitLabApi.Rest.createReplyNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
discussionId: String,
body: String,
): HttpResponse<out GitLabNoteRestDTO> {
val uri = getMergeRequestDiscussionsUri(project, mrIid)
val uri = getMergeRequestDiscussionsUri(projectId, mrIid)
.resolveRelative(discussionId)
.resolveRelative("notes")
.withQuery {
@@ -118,13 +118,13 @@ suspend fun GitLabApi.Rest.createReplyNote(
@SinceGitLab("10.6")
suspend fun GitLabApi.Rest.updateNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
discussionId: String,
noteId: String,
body: String,
): HttpResponse<out GitLabNoteRestDTO> {
val uri = getMergeRequestDiscussionsUri(project, mrIid)
val uri = getMergeRequestDiscussionsUri(projectId, mrIid)
.resolveRelative(discussionId)
.resolveRelative("notes")
.resolveRelative(noteId)
@@ -139,12 +139,12 @@ suspend fun GitLabApi.Rest.updateNote(
@SinceGitLab("10.6")
suspend fun GitLabApi.Rest.deleteNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
discussionId: String,
noteId: String,
): HttpResponse<out Unit> {
val uri = getMergeRequestDiscussionsUri(project, mrIid)
val uri = getMergeRequestDiscussionsUri(projectId, mrIid)
.resolveRelative(discussionId)
.resolveRelative("notes")
.resolveRelative(noteId)
@@ -156,12 +156,12 @@ suspend fun GitLabApi.Rest.deleteNote(
@SinceGitLab("10.8")
suspend fun GitLabApi.Rest.changeMergeRequestDiscussionResolve(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
discussionId: String,
resolved: Boolean,
): HttpResponse<out GitLabDiscussionRestDTO> {
val uri = getMergeRequestDiscussionsUri(project, mrIid)
val uri = getMergeRequestDiscussionsUri(projectId, mrIid)
.resolveRelative(discussionId)
.withQuery {
"resolved" eq resolved
@@ -211,5 +211,3 @@ internal fun GitLabApiUriQueryBuilder.addLineRangeParameters(lineRange: LineRang
}
}
}
@@ -5,10 +5,9 @@ import com.intellij.collaboration.api.json.loadJsonValue
import com.intellij.collaboration.util.resolveRelative
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabApiUriQueryBuilder
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.dto.GitLabMergeRequestDraftNoteRestDTO
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.projectApiUrl
import org.jetbrains.plugins.gitlab.api.withErrorStats
import org.jetbrains.plugins.gitlab.api.withQuery
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabDiffPositionInput
@@ -18,25 +17,29 @@ import java.net.http.HttpRequest.BodyPublishers
import java.net.http.HttpResponse
@SinceGitLab("15.9")
fun getMergeRequestDraftNotesUri(project: GitLabProjectCoordinates, mrIid: String): URI =
project.restApiUri
fun GitLabApi.Rest.getMergeRequestDraftNotesUri(projectId: String, mrIid: String): URI =
projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("draft_notes")
private fun getSpecificMergeRequestDraftNoteUri(project: GitLabProjectCoordinates, mrIid: String, noteId: String): URI =
getMergeRequestDraftNotesUri(project, mrIid).resolveRelative(noteId)
private fun GitLabApi.Rest.getSpecificMergeRequestDraftNoteUri(
projectId: String,
mrIid: String,
noteId: String,
): URI =
getMergeRequestDraftNotesUri(projectId, mrIid).resolveRelative(noteId)
@SinceGitLab("15.10")
suspend fun GitLabApi.Rest.updateDraftNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
noteId: String,
position: GitLabMergeRequestDraftNoteRestDTO.Position,
body: String,
)
: HttpResponse<out Unit> {
val uri = getSpecificMergeRequestDraftNoteUri(project, mrIid, noteId).withQuery {
val uri = getSpecificMergeRequestDraftNoteUri(projectId, mrIid, noteId).withQuery {
"note" eq body
addDraftNotePositionParameters(position) // have to pass the existing position, otherwise it is reset to null
}
@@ -48,12 +51,12 @@ suspend fun GitLabApi.Rest.updateDraftNote(
@SinceGitLab("15.9")
suspend fun GitLabApi.Rest.deleteDraftNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
noteId: Long,
)
: HttpResponse<out Unit> {
val uri = getSpecificMergeRequestDraftNoteUri(project, mrIid, noteId.toString())
val uri = getSpecificMergeRequestDraftNoteUri(projectId, mrIid, noteId.toString())
val request = request(uri).DELETE().build()
return withErrorStats(GitLabApiRequestName.REST_DELETE_DRAFT_NOTE) {
sendAndAwaitCancellable(request)
@@ -62,11 +65,11 @@ suspend fun GitLabApi.Rest.deleteDraftNote(
@SinceGitLab("15.11")
suspend fun GitLabApi.Rest.submitDraftNotes(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
)
: HttpResponse<out Unit> {
val uri = getMergeRequestDraftNotesUri(project, mrIid).resolveRelative("bulk_publish")
val uri = getMergeRequestDraftNotesUri(projectId, mrIid).resolveRelative("bulk_publish")
val request = request(uri).POST(BodyPublishers.noBody()).build()
return withErrorStats(GitLabApiRequestName.REST_SUBMIT_DRAFT_NOTES) {
sendAndAwaitCancellable(request)
@@ -75,11 +78,11 @@ suspend fun GitLabApi.Rest.submitDraftNotes(
@SinceGitLab("15.10")
suspend fun GitLabApi.Rest.submitSingleDraftNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
noteId: Long,
): HttpResponse<out Unit> {
val uri = getSpecificMergeRequestDraftNoteUri(project, mrIid, noteId.toString()).resolveRelative("publish")
val uri = getSpecificMergeRequestDraftNoteUri(projectId, mrIid, noteId.toString()).resolveRelative("publish")
val request = request(uri).PUT(BodyPublishers.noBody()).build()
return withErrorStats(GitLabApiRequestName.REST_SUBMIT_SINGLE_DRAFT_NOTE) {
sendAndAwaitCancellable(request)
@@ -88,12 +91,12 @@ suspend fun GitLabApi.Rest.submitSingleDraftNote(
@SinceGitLab("16.3")
suspend fun GitLabApi.Rest.addDraftReplyNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
discussionId: String,
body: String,
): HttpResponse<out GitLabMergeRequestDraftNoteRestDTO> {
val uri = getMergeRequestDraftNotesUri(project, mrIid).withQuery {
val uri = getMergeRequestDraftNotesUri(projectId, mrIid).withQuery {
"note" eq body
"in_reply_to_discussion_id" eq discussionId
}
@@ -105,13 +108,13 @@ suspend fun GitLabApi.Rest.addDraftReplyNote(
@SinceGitLab("15.10")
suspend fun GitLabApi.Rest.addDraftNote(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
@SinceGitLab("16.3")
positionOrNull: GitLabDiffPositionInput?,
body: String,
): HttpResponse<out GitLabMergeRequestDraftNoteRestDTO> {
val uri = getMergeRequestDraftNotesUri(project, mrIid).withQuery {
val uri = getMergeRequestDraftNotesUri(projectId, mrIid).withQuery {
"note" eq body
positionOrNull?.let { addDiffPositionParameters(it) }
}
@@ -4,10 +4,9 @@ package org.jetbrains.plugins.gitlab.mergerequest.api.request
import com.intellij.collaboration.api.json.loadJsonValue
import com.intellij.collaboration.util.resolveRelative
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.dto.GitLabAwardEmojiRestDTO
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.projectApiUrl
import org.jetbrains.plugins.gitlab.api.withErrorStats
import org.jetbrains.plugins.gitlab.api.withQuery
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
@@ -16,8 +15,8 @@ import java.net.http.HttpRequest
import java.net.http.HttpResponse
@SinceGitLab("8.9")
fun getMRNotesAwardEmojiUri(project: GitLabProjectCoordinates, mrIid: String, noteId: String): URI =
project.restApiUri
fun GitLabApi.Rest.getMRNotesAwardEmojiUri(projectId: String, mrIid: String, noteId: String): URI =
projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("notes")
@@ -26,12 +25,12 @@ fun getMRNotesAwardEmojiUri(project: GitLabProjectCoordinates, mrIid: String, no
@SinceGitLab("8.9")
suspend fun GitLabApi.Rest.addAwardEmoji(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
noteId: String,
name: String,
): HttpResponse<out GitLabAwardEmojiRestDTO> {
val uri = getMRNotesAwardEmojiUri(project, mrIid, noteId).withQuery {
val uri = getMRNotesAwardEmojiUri(projectId, mrIid, noteId).withQuery {
"name" eq name
}
val request = request(uri).POST(HttpRequest.BodyPublishers.noBody()).build()
@@ -42,12 +41,12 @@ suspend fun GitLabApi.Rest.addAwardEmoji(
@SinceGitLab("8.9")
suspend fun GitLabApi.Rest.deleteAwardEmoji(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
noteId: String,
awardId: String,
): HttpResponse<out Unit> {
val uri = getMRNotesAwardEmojiUri(project, mrIid, noteId)
val uri = getMRNotesAwardEmojiUri(projectId, mrIid, noteId)
.resolveRelative(awardId)
val request = request(uri).DELETE().build()
return withErrorStats(GitLabApiRequestName.REST_DELETE_NOTE_AWARD_EMOJI) {
@@ -12,13 +12,12 @@ import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabEdition
import org.jetbrains.plugins.gitlab.api.GitLabGQLQuery
import org.jetbrains.plugins.gitlab.api.GitLabGidData
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.dto.GitLabGraphQLMutationResultDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabReviewerDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
import org.jetbrains.plugins.gitlab.api.gitLabQuery
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.projectApiUrl
import org.jetbrains.plugins.gitlab.api.withErrorStats
import org.jetbrains.plugins.gitlab.api.withQuery
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabMergeRequestByBranchDTO
@@ -29,6 +28,7 @@ import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabMergeRequestShort
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestState
import org.jetbrains.plugins.gitlab.mergerequest.data.asApiParameter
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
import java.net.URI
import java.net.http.HttpRequest
import java.net.http.HttpResponse
@@ -42,7 +42,7 @@ import java.net.http.HttpResponse
*/
@SinceGitLab("14.0", note = "No exact version, but definitely exists in minimal")
suspend fun GitLabApi.Rest.createMergeRequest(
project: GitLabProjectCoordinates,
projectId: String,
sourceBranch: String,
targetBranch: String,
title: String,
@@ -51,7 +51,7 @@ suspend fun GitLabApi.Rest.createMergeRequest(
assigneeIds: List<String>? = null,
labels: List<String>? = null
): HttpResponse<out GitLabMergeRequestShortRestDTO> {
val uri = project.restApiUri
val uri = projectApiUrl(projectId)
.resolveRelative("merge_requests")
.withQuery {
"source_branch" eq sourceBranch
@@ -69,17 +69,19 @@ suspend fun GitLabApi.Rest.createMergeRequest(
}
@SinceGitLab("7.0", note = "?search available since 10.4, ?scope since 9.5")
fun getMergeRequestListURI(project: GitLabProjectCoordinates,
searchQuery: String): URI =
project.restApiUri.resolveRelative("merge_requests").withQuery(searchQuery)
fun GitLabApi.Rest.getMergeRequestListURI(
projectId: String,
searchQuery: String
): URI =
projectApiUrl(projectId).resolveRelative("merge_requests").withQuery(searchQuery)
@SinceGitLab("12.0")
suspend fun GitLabApi.GraphQL.loadMergeRequest(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
mrIid: String
): HttpResponse<out GitLabMergeRequestDTO?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"mergeRequestId" to mrIid
)
val request = gitLabQuery(GitLabGQLQuery.GET_MERGE_REQUEST, parameters)
@@ -91,11 +93,11 @@ suspend fun GitLabApi.GraphQL.loadMergeRequest(
@ApiStatus.Internal
@SinceGitLab("14.0", note = "No exact version")
suspend fun GitLabApi.GraphQL.getMergeRequestMetrics(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
username: String,
): HttpResponse<out GitLabMergeRequestMetricsDTO?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"username" to username
)
val request = gitLabQuery(GitLabGQLQuery.GET_MERGE_REQUEST_METRICS, parameters)
@@ -106,13 +108,13 @@ suspend fun GitLabApi.GraphQL.getMergeRequestMetrics(
@SinceGitLab("13.1")
suspend fun GitLabApi.GraphQL.findMergeRequestsByBranch(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
state: GitLabMergeRequestState,
sourceBranch: String,
targetBranch: String? = null
): HttpResponse<out GraphQLConnectionDTO<GitLabMergeRequestByBranchDTO>?> {
val parameters = mutableMapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"state" to state.asApiParameter(),
"sourceBranches" to listOf(sourceBranch),
"targetBranches" to targetBranch?.let { listOf(it) }
@@ -127,24 +129,33 @@ private class MergeRequestsByBranchConnection(pageInfo: GraphQLCursorPageInfoDTO
: GraphQLConnectionDTO<GitLabMergeRequestByBranchDTO>(pageInfo, nodes)
@SinceGitLab("13.2")
fun getMergeRequestStateEventsUri(project: GitLabProjectCoordinates, mrIid: String): URI =
project.restApiUri.resolveRelative("merge_requests").resolveRelative(mrIid).resolveRelative("resource_state_events")
fun GitLabApi.Rest.getMergeRequestStateEventsUri(projectId: String, mrIid: String): URI =
projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("resource_state_events")
@SinceGitLab("11.4", note = "Maybe released in 11.3-rc5")
fun getMergeRequestLabelEventsUri(project: GitLabProjectCoordinates, mrIid: String): URI =
project.restApiUri.resolveRelative("merge_requests").resolveRelative(mrIid).resolveRelative("resource_label_events")
fun GitLabApi.Rest.getMergeRequestLabelEventsUri(projectId: String, mrIid: String): URI =
projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("resource_label_events")
@SinceGitLab("13.1")
fun getMergeRequestMilestoneEventsUri(project: GitLabProjectCoordinates, mrIid: String): URI =
project.restApiUri.resolveRelative("merge_requests").resolveRelative(mrIid).resolveRelative("resource_milestone_events")
fun GitLabApi.Rest.getMergeRequestMilestoneEventsUri(projectId: String, mrIid: String): URI =
projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("resource_milestone_events")
@SinceGitLab("10.6", editions = [GitLabEdition.Enterprise])
@SinceGitLab("13.3", editions = [GitLabEdition.Community], note = "Maybe released in 13.2-rc42 or so")
suspend fun GitLabApi.Rest.mergeRequestApprove(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String
): HttpResponse<out Unit> {
val uri = project.restApiUri
val uri = projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("approve")
@@ -157,10 +168,10 @@ suspend fun GitLabApi.Rest.mergeRequestApprove(
@SinceGitLab("10.6", editions = [GitLabEdition.Enterprise])
@SinceGitLab("13.3", editions = [GitLabEdition.Community], note = "Maybe released in 13.2-rc42 or so")
suspend fun GitLabApi.Rest.mergeRequestUnApprove(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String
): HttpResponse<out Unit> {
val uri = project.restApiUri
val uri = projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("unapprove")
@@ -172,10 +183,10 @@ suspend fun GitLabApi.Rest.mergeRequestUnApprove(
@SinceGitLab("11.6")
suspend fun GitLabApi.Rest.mergeRequestRebase(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String
): HttpResponse<out GitLabMergeRequestRebaseDTO> {
val uri = project.restApiUri
val uri = projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.resolveRelative("rebase")
@@ -187,12 +198,12 @@ suspend fun GitLabApi.Rest.mergeRequestRebase(
@SinceGitLab("13.9")
suspend fun GitLabApi.GraphQL.mergeRequestUpdate(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
mrIid: String,
state: GitLabMergeRequestNewState,
): HttpResponse<out GitLabGraphQLMutationResultDTO<GitLabMergeRequestDTO>?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"mergeRequestId" to mrIid,
"state" to state
)
@@ -211,12 +222,12 @@ suspend fun GitLabApi.GraphQL.mergeRequestUpdate(
*/
@SinceGitLab("13.8")
suspend fun GitLabApi.Rest.mergeRequestSetReviewers(
project: GitLabProjectCoordinates,
projectId: String,
mrIid: String,
reviewers: List<GitLabUserDTO>
): HttpResponse<out Unit> {
val reviewerIds = reviewers.map { GitLabGidData(it.id).guessRestId() }
val uri = project.restApiUri
val uri = projectApiUrl(projectId)
.resolveRelative("merge_requests")
.resolveRelative(mrIid)
.withQuery {
@@ -238,12 +249,12 @@ suspend fun GitLabApi.Rest.mergeRequestSetReviewers(
*/
@SinceGitLab("15.3")
suspend fun GitLabApi.GraphQL.mergeRequestSetReviewers(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
mrIid: String,
reviewers: List<GitLabUserDTO>
): HttpResponse<out GitLabGraphQLMutationResultDTO<GitLabMergeRequestDTO>?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"mergeRequestId" to mrIid,
"reviewerUsernames" to reviewers.map { it.username }
)
@@ -255,7 +266,7 @@ suspend fun GitLabApi.GraphQL.mergeRequestSetReviewers(
@SinceGitLab("13.10")
suspend fun GitLabApi.GraphQL.mergeRequestAccept(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
mrIid: String,
commitMessage: String,
sha: String,
@@ -263,7 +274,7 @@ suspend fun GitLabApi.GraphQL.mergeRequestAccept(
shouldRemoveSourceBranch: Boolean
): HttpResponse<out GitLabGraphQLMutationResultDTO<GitLabMergeRequestDTO>?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"mergeRequestId" to mrIid,
"commitMessage" to commitMessage,
"sha" to sha,
@@ -278,12 +289,12 @@ suspend fun GitLabApi.GraphQL.mergeRequestAccept(
@SinceGitLab("13.12")
suspend fun GitLabApi.GraphQL.mergeRequestSetDraft(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
mrIid: String,
isDraft: Boolean
): HttpResponse<out GitLabGraphQLMutationResultDTO<GitLabMergeRequestDTO>?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"mergeRequestId" to mrIid,
"isDraft" to isDraft
)
@@ -295,12 +306,12 @@ suspend fun GitLabApi.GraphQL.mergeRequestSetDraft(
@SinceGitLab("13.9")
suspend fun GitLabApi.GraphQL.mergeRequestReviewerRereview(
project: GitLabProjectCoordinates,
projectPath: GitLabProjectPath,
mrIid: String,
reviewer: GitLabReviewerDTO
): HttpResponse<out GitLabGraphQLMutationResultDTO<GitLabMergeRequestDTO>?> {
val parameters = mapOf(
"projectId" to project.projectPath.fullPath(),
"projectId" to projectPath.fullPath(),
"mergeRequestId" to mrIid,
"userId" to reviewer.id
)
@@ -33,7 +33,6 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabId
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabRestId
import org.jetbrains.plugins.gitlab.api.GitLabServerMetadata
import org.jetbrains.plugins.gitlab.api.GitLabVersion
@@ -77,7 +76,7 @@ class LoadedGitLabDiscussion(
parentCs: CoroutineScope,
private val api: GitLabApi,
glMetadata: GitLabServerMetadata?,
private val glProject: GitLabProjectCoordinates,
private val projectId: String,
private val currentUser: GitLabUserDTO,
private val eventSink: suspend (Change<GitLabDiscussionRestDTO>) -> Unit,
private val draftNotesEventSink: suspend (Change<GitLabMergeRequestDraftNoteRestDTO>) -> Unit,
@@ -131,7 +130,7 @@ class LoadedGitLabDiscussion(
.mapDataToModel(
GitLabNoteRestDTO::id,
{ note ->
MutableGitLabMergeRequestNote(this, api, glProject, mr, id, currentUser, noteEvents::emit, note)
MutableGitLabMergeRequestNote(this, api, projectId, mr, id, currentUser, noteEvents::emit, note)
},
MutableGitLabMergeRequestNote::update
).combine(draftNotes) { notes, draftNotes ->
@@ -160,7 +159,7 @@ class LoadedGitLabDiscussion(
operationsGuard.withLock {
val resolved = resolved.first()
val result = withContext(Dispatchers.IO) {
api.rest.changeMergeRequestDiscussionResolve(glProject, mr.iid, id.restId, !resolved).body()
api.rest.changeMergeRequestDiscussionResolve(projectId, mr.iid, id.restId, !resolved).body()
}
noteEvents.emit(GitLabNoteEvent.Changed(result.notes))
if (mr.details.value.targetProject.onlyAllowMergeIfAllDiscussionsAreResolved) {
@@ -173,7 +172,7 @@ class LoadedGitLabDiscussion(
override suspend fun addNote(body: String) {
withContext(cs.coroutineContext) {
val note = withContext(Dispatchers.IO) {
api.rest.createReplyNote(glProject, mr.iid, id.restId, body).body()
api.rest.createReplyNote(projectId, mr.iid, id.restId, body).body()
}
withContext(NonCancellable) {
@@ -185,7 +184,7 @@ class LoadedGitLabDiscussion(
override suspend fun addDraftNote(body: String) {
withContext(cs.coroutineContext) {
withContext(Dispatchers.IO) {
api.rest.addDraftReplyNote(glProject, mr.iid, id.restId, body).body()
api.rest.addDraftReplyNote(projectId, mr.iid, id.restId, body).body()
}?.also {
withContext(NonCancellable) {
draftNotesEventSink(AddedLast(it))
@@ -14,6 +14,7 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import git4idea.GitStandardRemoteBranch
import git4idea.changes.GitBranchComparisonResult
import git4idea.remote.GitRemoteUrlCoordinates
import git4idea.remote.hosting.GitRemoteBranchesUtil
import git4idea.remote.hosting.changesSignalFlow
import git4idea.repo.GitRepository
@@ -38,6 +39,7 @@ import kotlinx.coroutines.withContext
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabServerMetadata
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.api.GitLabVersion
import org.jetbrains.plugins.gitlab.api.dto.GitLabResourceLabelEventDTO
import org.jetbrains.plugins.gitlab.api.dto.GitLabResourceMilestoneEventDTO
@@ -62,7 +64,6 @@ import org.jetbrains.plugins.gitlab.mergerequest.api.request.mergeRequestUnAppro
import org.jetbrains.plugins.gitlab.mergerequest.api.request.mergeRequestUpdate
import org.jetbrains.plugins.gitlab.mergerequest.data.loaders.startGitLabRestETagListLoaderIn
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabRegistry
import org.jetbrains.plugins.gitlab.util.GitLabStatistics
@@ -70,8 +71,9 @@ private val LOG = logger<GitLabMergeRequest>()
@CodeReviewDomainEntity
interface GitLabMergeRequest : GitLabMergeRequestDiscussionsContainer {
val glProject: GitLabProjectCoordinates
val gitRepository: GitRepository
val serverPath: GitLabServerPath
val gitRemote: GitRemoteUrlCoordinates
val projectId: String
val iid: String
val gid: String
@@ -142,14 +144,15 @@ internal class LoadedGitLabMergeRequest(
parentCs: CoroutineScope,
private val api: GitLabApi,
private val glMetadata: GitLabServerMetadata?,
private val projectMapping: GitLabProjectMapping,
private val currentUser: GitLabUserDTO,
mergeRequest: GitLabMergeRequestDTO
private val projectCoordinates: GitLabProjectCoordinates,
override val projectId: String,
override val gitRemote: GitRemoteUrlCoordinates,
currentUser: GitLabUserDTO,
mergeRequest: GitLabMergeRequestDTO,
) : GitLabMergeRequest {
private val cs = parentCs.childScope(this::class, Dispatchers.Default)
override val glProject: GitLabProjectCoordinates = projectMapping.repository
override val gitRepository: GitRepository = projectMapping.gitRepository
override val serverPath: GitLabServerPath = projectCoordinates.serverPath
override val iid: String = mergeRequest.iid
override val gid: String = mergeRequest.id
@@ -167,13 +170,16 @@ internal class LoadedGitLabMergeRequest(
override val changes: SharedFlow<GitLabMergeRequestChanges> = mergeRequestDetailsState
.distinctUntilChangedBy(GitLabMergeRequestFullDetails::diffRefs)
.mapScoped { details -> GitLabMergeRequestChangesImpl(this, api, glMetadata, projectMapping, details) }
.mapScoped { details ->
GitLabMergeRequestChangesImpl(this, project, projectId, projectCoordinates.projectPath, gitRemote,
api, glMetadata, details)
}
.modelFlow(cs, LOG)
override val stateEvents by lazy {
startGitLabRestETagListLoaderIn(
cs,
getMergeRequestStateEventsUri(glProject, iid),
api.rest.getMergeRequestStateEventsUri(projectId, iid),
{ it.id },
requestReloadFlow = mergeRequestReloadRequest.withInitial(Unit),
@@ -189,7 +195,7 @@ internal class LoadedGitLabMergeRequest(
override val labelEvents by lazy {
startGitLabRestETagListLoaderIn(
cs,
getMergeRequestLabelEventsUri(glProject, iid),
api.rest.getMergeRequestLabelEventsUri(projectId, iid),
{ it.id },
requestReloadFlow = mergeRequestReloadRequest.withInitial(Unit),
@@ -205,7 +211,7 @@ internal class LoadedGitLabMergeRequest(
override val milestoneEvents by lazy {
startGitLabRestETagListLoaderIn(
cs,
getMergeRequestMilestoneEventsUri(glProject, iid),
api.rest.getMergeRequestMilestoneEventsUri(projectId, iid),
{ it.id },
requestReloadFlow = mergeRequestReloadRequest.withInitial(Unit),
@@ -225,7 +231,7 @@ internal class LoadedGitLabMergeRequest(
override val draftReviewText: MutableStateFlow<String> = MutableStateFlow("")
private val discussionsContainer =
GitLabMergeRequestDiscussionsContainerImpl(parentCs, project, api, glMetadata, projectMapping.repository, currentUser, this)
GitLabMergeRequestDiscussionsContainerImpl(parentCs, project, api, glMetadata, projectId, currentUser, this)
init {
cs.launch {
@@ -237,7 +243,7 @@ internal class LoadedGitLabMergeRequest(
}
cs.launch {
val repository = projectMapping.gitRepository
val repository = gitRemote.repository
repository.changesSignalFlow().withInitial(Unit).combine(details) { _, currentDetails ->
isCurrentDataInSyncWithRepository(currentDetails, repository)
}.distinctUntilChanged().filterNotNull().collectLatest {
@@ -255,7 +261,7 @@ internal class LoadedGitLabMergeRequest(
detailsLoadingGuard.lock()
_isLoading.value = true
val updatedMergeRequest = withContext(Dispatchers.IO) {
api.graphQL.loadMergeRequest(glProject, iid).body()!!
api.graphQL.loadMergeRequest(projectCoordinates.projectPath, iid).body()!!
}
return updateMergeRequestData(updatedMergeRequest)
}
@@ -266,7 +272,7 @@ internal class LoadedGitLabMergeRequest(
}
private fun isCurrentDataInSyncWithRepository(details: GitLabMergeRequestFullDetails, repository: GitRepository): Boolean? {
val remoteMrBranchHash = details.getSourceRemoteDescriptor(projectMapping.repository.serverPath)?.let {
val remoteMrBranchHash = details.getSourceRemoteDescriptor(serverPath)?.let {
GitRemoteBranchesUtil.findRemote(repository, it)
}?.let {
val branch = GitStandardRemoteBranch(it, details.sourceBranch)
@@ -329,7 +335,7 @@ internal class LoadedGitLabMergeRequest(
override suspend fun approve() {
try {
withContext(cs.coroutineContext + Dispatchers.IO) {
api.rest.mergeRequestApprove(glProject, iid)
api.rest.mergeRequestApprove(projectId, iid)
}
}
finally {
@@ -341,7 +347,7 @@ internal class LoadedGitLabMergeRequest(
override suspend fun unApprove() {
try {
withContext(cs.coroutineContext + Dispatchers.IO) {
api.rest.mergeRequestUnApprove(glProject, iid)
api.rest.mergeRequestUnApprove(projectId, iid)
}
}
finally {
@@ -352,7 +358,7 @@ internal class LoadedGitLabMergeRequest(
override suspend fun close() {
withContext(cs.coroutineContext + Dispatchers.IO) {
val updatedMergeRequest = api.graphQL.mergeRequestUpdate(glProject, iid, GitLabMergeRequestNewState.CLOSED)
val updatedMergeRequest = api.graphQL.mergeRequestUpdate(projectCoordinates.projectPath, iid, GitLabMergeRequestNewState.CLOSED)
.getResultOrThrow()
updateMergeRequestData(updatedMergeRequest)
stateEventsRefreshRequest.emit(Unit)
@@ -362,7 +368,7 @@ internal class LoadedGitLabMergeRequest(
override suspend fun reopen() {
withContext(cs.coroutineContext + Dispatchers.IO) {
val updatedMergeRequest = api.graphQL.mergeRequestUpdate(glProject, iid, GitLabMergeRequestNewState.OPEN)
val updatedMergeRequest = api.graphQL.mergeRequestUpdate(projectCoordinates.projectPath, iid, GitLabMergeRequestNewState.OPEN)
.getResultOrThrow()
updateMergeRequestData(updatedMergeRequest)
stateEventsRefreshRequest.emit(Unit)
@@ -372,7 +378,7 @@ internal class LoadedGitLabMergeRequest(
override suspend fun postReview() {
withContext(cs.coroutineContext + Dispatchers.IO) {
val updatedMergeRequest = api.graphQL.mergeRequestSetDraft(glProject, iid, isDraft = false)
val updatedMergeRequest = api.graphQL.mergeRequestSetDraft(projectCoordinates.projectPath, iid, isDraft = false)
.getResultOrThrow()
updateMergeRequestData(updatedMergeRequest)
}
@@ -383,11 +389,11 @@ internal class LoadedGitLabMergeRequest(
override suspend fun setReviewers(reviewers: List<GitLabUserDTO>) {
withContext(cs.coroutineContext + Dispatchers.IO) {
val updatedMergeRequest = if (glMetadata != null && GitLabVersion(15, 3) <= glMetadata.version) {
api.graphQL.mergeRequestSetReviewers(glProject, iid, reviewers).getResultOrThrow()
api.graphQL.mergeRequestSetReviewers(projectCoordinates.projectPath, iid, reviewers).getResultOrThrow()
}
else {
api.rest.mergeRequestSetReviewers(glProject, iid, reviewers).body()
api.graphQL.loadMergeRequest(glProject, iid).body() ?: error("Merge request could not be loaded")
api.rest.mergeRequestSetReviewers(projectId, iid, reviewers).body()
api.graphQL.loadMergeRequest(projectCoordinates.projectPath, iid).body() ?: error("Merge request could not be loaded")
}
updateMergeRequestData(updatedMergeRequest)
@@ -399,7 +405,7 @@ internal class LoadedGitLabMergeRequest(
override suspend fun reviewerRereview(reviewers: Collection<GitLabReviewerDTO>) {
withContext(cs.coroutineContext + Dispatchers.IO) {
reviewers.forEach { reviewer ->
val updatedMergeRequest = api.graphQL.mergeRequestReviewerRereview(glProject, iid, reviewer)
val updatedMergeRequest = api.graphQL.mergeRequestReviewerRereview(projectCoordinates.projectPath, iid, reviewer)
.getResultOrThrow()
updateMergeRequestData(updatedMergeRequest)
}
@@ -442,9 +448,10 @@ internal class LoadedGitLabMergeRequest(
val sha = mergeRequestDetailsState.value.diffRefs?.headSha ?: return
val shouldRemoveSourceBranch = mergeRequestDetailsState.value.shouldRemoveSourceBranch
?: mergeRequestDetailsState.value.targetProject.removeSourceBranchAfterMerge
api.graphQL.mergeRequestAccept(glProject, iid, commitMessage, sha, withSquash, shouldRemoveSourceBranch).getResultOrThrow()
api.graphQL.mergeRequestAccept(projectCoordinates.projectPath, iid, commitMessage, sha, withSquash, shouldRemoveSourceBranch)
.getResultOrThrow()
do {
val updatedMergeRequest = api.graphQL.loadMergeRequest(glProject, iid).body()!!
val updatedMergeRequest = api.graphQL.loadMergeRequest(projectCoordinates.projectPath, iid).body()!!
updateMergeRequestData(updatedMergeRequest)
delay(GitLabRegistry.getRequestPollingIntervalMillis().toLong())
attempts++
@@ -454,9 +461,9 @@ internal class LoadedGitLabMergeRequest(
private suspend fun runRebase() {
var attempts = 0
api.rest.mergeRequestRebase(glProject, iid)
api.rest.mergeRequestRebase(projectId, iid)
do {
val updatedMergeRequest = api.graphQL.loadMergeRequest(glProject, iid).body()!!
val updatedMergeRequest = api.graphQL.loadMergeRequest(projectCoordinates.projectPath, iid).body()!!
updateMergeRequestData(updatedMergeRequest)
delay(GitLabRegistry.getRequestPollingIntervalMillis().toLong())
attempts++
@@ -7,12 +7,14 @@ import com.intellij.collaboration.async.childScope
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.diff.impl.patch.PatchReader
import com.intellij.openapi.diff.impl.patch.TextFilePatch
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.FileStatus
import com.intellij.vcsUtil.VcsFileUtil
import git4idea.changes.GitBranchComparisonResult
import git4idea.changes.GitCommitShaWithPatches
import git4idea.changes.filePath
import git4idea.remote.GitRemoteUrlCoordinates
import git4idea.remote.hosting.GitCodeReviewUtils
import git4idea.repo.GitRepository
import kotlinx.coroutines.CoroutineScope
@@ -38,7 +40,7 @@ import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadCommitDiffs
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequestChanges
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequestCommits
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequestDiffs
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
private val LOG = logger<GitLabMergeRequestChanges>()
@@ -74,26 +76,29 @@ internal suspend fun GitLabMergeRequestChanges.loadRevisionsAndParseChanges(): G
class GitLabMergeRequestChangesImpl(
parentCs: CoroutineScope,
private val project: Project,
private val projectId: String,
private val projectPath: GitLabProjectPath,
private val gitRemoteUrlCoordinates: GitRemoteUrlCoordinates,
private val api: GitLabApi,
private val glMetadata: GitLabServerMetadata?,
private val projectMapping: GitLabProjectMapping,
private val mergeRequestDetails: GitLabMergeRequestFullDetails,
) : GitLabMergeRequestChanges {
private val cs = parentCs.childScope(this::class)
private val glProject = projectMapping.repository
private val commits: Deferred<List<GitLabCommit>> = cs.async {
if (glMetadata != null && glMetadata.version < GitLabVersion(14, 7)) {
val initialURI = api.getMergeRequestCommitsURI(glProject, mergeRequestDetails.iid)
val initialURI = api.rest.getMergeRequestCommitsURI(projectId, mergeRequestDetails.iid)
return@async ApiPageUtil.createPagesFlowByLinkHeader(initialURI) { uri -> api.rest.loadMergeRequestCommits(uri) }
.map { it.body() ?: emptyList() }
.foldToList(GitLabCommit.Companion::fromRestDTO)
.asReversed()
}
ApiPageUtil.createGQLPagesFlow { pagination -> api.graphQL.loadMergeRequestCommits(glProject, mergeRequestDetails.iid, pagination) }
ApiPageUtil.createGQLPagesFlow { pagination ->
api.graphQL.loadMergeRequestCommits(projectPath, mergeRequestDetails.iid, pagination)
}
.map { page -> page.nodes }
.foldToList(GitLabCommit.Companion::fromGraphQLDTO)
.asReversed()
@@ -108,7 +113,6 @@ class GitLabMergeRequestChangesImpl(
override suspend fun getParsedChanges(): GitBranchComparisonResult = parsedChanges.await()
private suspend fun loadChanges(commits: List<GitLabCommit>): GitBranchComparisonResult {
val repository = projectMapping.remote.repository
val diffRefs = mergeRequestDetails.diffRefs ?: error("Missing diff refs")
val baseSha = diffRefs.startSha
val mergeBaseSha = diffRefs.baseSha ?: error("Missing merge base revision")
@@ -117,8 +121,8 @@ class GitLabMergeRequestChangesImpl(
coroutineScope {
commits.map { commit ->
async {
val commitWithParents = api.rest.loadCommit(glProject, commit.sha).body()!!
val patches = ApiPageUtil.createPagesFlowByLinkHeader(getCommitDiffsURI(glProject, commit.sha)) {
val commitWithParents = api.rest.loadCommit(projectId, commit.sha).body()!!
val patches = ApiPageUtil.createPagesFlowByLinkHeader(api.rest.getCommitDiffsURI(projectId, commit.sha)) {
api.rest.loadCommitDiffs(it)
}.map { it.body() }.foldToList(GitLabDiffDTO::toPatch)
GitCommitShaWithPatches(commit.sha, commitWithParents.parentIds, patches)
@@ -136,14 +140,14 @@ class GitLabMergeRequestChangesImpl(
}
val headPatches = withContext(Dispatchers.IO) {
if (api.getMetadata().version < GitLabVersion(15, 7)) {
ApiPageUtil.createPagesFlowByLinkHeader(api.getMergeRequestChangesURI(glProject, mergeRequestDetails.iid)) {
ApiPageUtil.createPagesFlowByLinkHeader(api.rest.getMergeRequestChangesURI(projectId, mergeRequestDetails.iid)) {
api.rest.loadMergeRequestChanges(it)
}.map { it.body().changes }.foldToList(GitLabDiffDTO::toPatch)
}
else {
// doesn't send back Link headers...
ApiPageUtil.createPagesFlowByPagination { page ->
api.rest.loadMergeRequestDiffs(api.getMergeRequestDiffsURI(glProject, mergeRequestDetails.iid, page))
api.rest.loadMergeRequestDiffs(api.rest.getMergeRequestDiffsURI(projectId, mergeRequestDetails.iid, page))
}.map { it.body() }.foldToList(GitLabDiffDTO::toPatch)
}
}.apply {
@@ -153,7 +157,7 @@ class GitLabMergeRequestChangesImpl(
}
}
}
return GitBranchComparisonResult.create(repository.project, repository.root, baseSha, mergeBaseSha, commitsWithPatches, headPatches)
return GitBranchComparisonResult.create(project, gitRemoteUrlCoordinates.repository.root, baseSha, mergeBaseSha, commitsWithPatches, headPatches)
}
override suspend fun ensureAllRevisionsFetched() {
@@ -162,12 +166,15 @@ class GitLabMergeRequestChangesImpl(
revsToCheck.add(it)
}
if (GitCodeReviewUtils.testRevisionsExist(projectMapping.gitRepository, revsToCheck)) return
if (GitCodeReviewUtils.testRevisionsExist(gitRemoteUrlCoordinates.repository, revsToCheck)) return
GitCodeReviewUtils.fetch(projectMapping.gitRepository, projectMapping.gitRemote, mergeRequestDetails.targetBranch)
GitCodeReviewUtils.fetch(projectMapping.gitRepository, projectMapping.gitRemote, """merge-requests/${mergeRequestDetails.iid}/head:""")
GitCodeReviewUtils.fetch(gitRemoteUrlCoordinates.repository, gitRemoteUrlCoordinates.remote, mergeRequestDetails.targetBranch)
GitCodeReviewUtils.fetch(gitRemoteUrlCoordinates.repository, gitRemoteUrlCoordinates.remote,
"""merge-requests/${mergeRequestDetails.iid}/head:""")
check(GitCodeReviewUtils.testRevisionsExist(projectMapping.gitRepository, revsToCheck)) { "Failed to fetch some revisions" }
check(GitCodeReviewUtils.testRevisionsExist(gitRemoteUrlCoordinates.repository, revsToCheck)) {
"Failed to fetch some revisions"
}
}
}
@@ -27,7 +27,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabId
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabServerMetadata
import org.jetbrains.plugins.gitlab.api.GitLabVersion
import org.jetbrains.plugins.gitlab.api.dto.GitLabDiscussionRestDTO
@@ -73,7 +72,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
private val project: Project,
private val api: GitLabApi,
private val glMetadata: GitLabServerMetadata?,
private val glProject: GitLabProjectCoordinates,
private val projectId: String,
private val currentUser: GitLabUserDTO,
private val mr: GitLabMergeRequest,
) : GitLabMergeRequestDiscussionsContainer {
@@ -96,7 +95,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
private val nonEmptyDiscussionsData: SharedFlow<Result<List<GitLabDiscussionRestDTO>>> by lazy {
startGitLabRestETagListLoaderIn(
cs,
getMergeRequestDiscussionsUri(glProject, mr.iid),
api.rest.getMergeRequestDiscussionsUri(projectId, mr.iid),
{ it.id },
requestReloadFlow = reloadRequests,
@@ -121,7 +120,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
GitLabDiscussionRestDTO::id,
{ disc ->
LoadedGitLabDiscussion(this,
api, glMetadata, glProject, currentUser,
api, glMetadata, projectId, currentUser,
{ discussionEvents.emit(it) }, { draftNotesEvents.emit(it) },
mr, disc, getDiscussionDraftNotes(disc.id).throwFailure())
},
@@ -155,7 +154,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
else {
startGitLabRestETagListLoaderIn(
cs,
getMergeRequestDraftNotesUri(glProject, mr.iid),
api.rest.getMergeRequestDraftNotesUri(projectId, mr.iid),
{ it.id },
requestReloadFlow = reloadRequests,
@@ -182,7 +181,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
mapDataToModel(
GitLabMergeRequestDraftNoteRestDTO::id,
{
GitLabMergeRequestDraftNoteImpl(this, api, glMetadata, glProject, mr, { draftNotesEvents.emit(it) }, it, currentUser)
GitLabMergeRequestDraftNoteImpl(this, api, glMetadata, projectId, mr, { draftNotesEvents.emit(it) }, it, currentUser)
},
{ update(it) }
)
@@ -204,7 +203,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
override suspend fun addNote(body: String) {
withContext(cs.coroutineContext) {
val newDiscussion = withContext(Dispatchers.IO) {
api.rest.addNote(glProject, mr.iid, body).body()
api.rest.addNote(projectId, mr.iid, body).body()
}
withContext(NonCancellable) {
@@ -216,7 +215,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
override suspend fun addNote(position: GitLabMergeRequestNewDiscussionPosition, body: String) {
withContext(cs.coroutineContext) {
val newDiscussion = withContext(Dispatchers.IO) {
api.rest.addDiffNote(glProject, mr.iid, GitLabDiffPositionInput.from(position), body).body()
api.rest.addDiffNote(projectId, mr.iid, GitLabDiffPositionInput.from(position), body).body()
}
withContext(NonCancellable) {
@@ -228,7 +227,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
override suspend fun addDraftNote(body: String) {
withContext(cs.coroutineContext) {
val newNote = withContext(Dispatchers.IO) {
api.rest.addDraftNote(glProject, mr.iid, null, body).body()
api.rest.addDraftNote(projectId, mr.iid, null, body).body()
}
withContext(NonCancellable) {
@@ -240,7 +239,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
override suspend fun addDraftNote(position: GitLabMergeRequestNewDiscussionPosition, body: String) {
withContext(cs.coroutineContext) {
val newNote = withContext(Dispatchers.IO) {
api.rest.addDraftNote(glProject, mr.iid, GitLabDiffPositionInput.from(position), body).body()
api.rest.addDraftNote(projectId, mr.iid, GitLabDiffPositionInput.from(position), body).body()
}
withContext(NonCancellable) {
@@ -257,7 +256,7 @@ class GitLabMergeRequestDiscussionsContainerImpl(
}
withContext(Dispatchers.IO) {
api.rest.submitDraftNotes(glProject, mr.iid)
api.rest.submitDraftNotes(projectId, mr.iid)
}
withContext(NonCancellable) {
draftNotesEvents.emit(AllDeleted())
@@ -32,7 +32,6 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabId
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabRestId
import org.jetbrains.plugins.gitlab.api.GitLabServerMetadata
import org.jetbrains.plugins.gitlab.api.GitLabVersion
@@ -111,7 +110,7 @@ interface GitLabMergeRequestDraftNote : GitLabMergeRequestNote, MutableGitLabNot
class MutableGitLabMergeRequestNote(
parentCs: CoroutineScope,
private val api: GitLabApi,
private val project: GitLabProjectCoordinates,
private val projectId: String,
private val mr: GitLabMergeRequest,
private val discussionId: GitLabRestId,
private val currentUser: GitLabUserDTO,
@@ -153,7 +152,7 @@ class MutableGitLabMergeRequestNote(
withContext(cs.coroutineContext) {
operationsGuard.withLock {
withContext(Dispatchers.IO) {
api.rest.updateNote(project, mr.iid, discussionId.restId, id.restId, newText).body()
api.rest.updateNote(projectId, mr.iid, discussionId.restId, id.restId, newText).body()
}
}
data.update { it.copy(body = newText) }
@@ -164,7 +163,7 @@ class MutableGitLabMergeRequestNote(
withContext(cs.coroutineContext) {
operationsGuard.withLock {
withContext(Dispatchers.IO) {
api.rest.deleteNote(project, mr.iid, discussionId.restId, id.restId).body()
api.rest.deleteNote(projectId, mr.iid, discussionId.restId, id.restId).body()
}
}
eventSink(GitLabNoteEvent.Deleted(id))
@@ -183,13 +182,13 @@ class MutableGitLabMergeRequestNote(
}
withContext(Dispatchers.IO) {
if (noteEmoji == null) {
val awardedEmojiDTO = api.rest.addAwardEmoji(project, mr.iid, id.restId, reaction.name).body()
val awardedEmojiDTO = api.rest.addAwardEmoji(projectId, mr.iid, id.restId, reaction.name).body()
val awardEmoji = GitLabAwardEmoji.fromDto(awardedEmojiDTO, emojiMap)
updateEmojisLocally(awardEmoji)
}
else {
val awardId = noteEmoji.id.restId ?: return@withContext
api.rest.deleteAwardEmoji(project, mr.iid, id.restId, awardId).body()
api.rest.deleteAwardEmoji(projectId, mr.iid, id.restId, awardId).body()
updateEmojisLocally(noteEmoji)
}
}
@@ -213,7 +212,7 @@ class MutableGitLabMergeRequestNote(
private suspend fun loadEmojis() {
runCatching {
val emojiMap = emojiMapDeferred.await()
val uri = getMRNotesAwardEmojiUri(project, mr.iid, id.restId)
val uri = api.rest.getMRNotesAwardEmojiUri(projectId, mr.iid, id.restId)
val emojis: List<GitLabAwardEmojiRestDTO> = withContext(Dispatchers.IO) {
ApiPageUtil.createPagesFlowByLinkHeader(uri) { pageUri ->
@@ -241,7 +240,7 @@ class GitLabMergeRequestDraftNoteImpl(
parentCs: CoroutineScope,
private val api: GitLabApi,
private val glMetadata: GitLabServerMetadata?,
private val project: GitLabProjectCoordinates,
private val projectId: String,
private val mr: GitLabMergeRequest,
private val eventSink: suspend (Change<GitLabMergeRequestDraftNoteRestDTO>) -> Unit,
private val noteData: GitLabMergeRequestDraftNoteRestDTO,
@@ -274,7 +273,7 @@ class GitLabMergeRequestDraftNoteImpl(
operationsGuard.withLock {
withContext(Dispatchers.IO) {
// Checked by canEdit
api.rest.updateDraftNote(project, mr.iid, noteData.id.restId, noteData.position, newText)
api.rest.updateDraftNote(projectId, mr.iid, noteData.id.restId, noteData.position, newText)
}
}
data.update { it.copy(note = newText) }
@@ -287,7 +286,7 @@ class GitLabMergeRequestDraftNoteImpl(
withContext(Dispatchers.IO) {
// Shouldn't require extra check, delete and get draft notes was introduced in
// the same update
api.rest.deleteDraftNote(project, mr.iid, noteData.id.restId.toLong()).body()
api.rest.deleteDraftNote(projectId, mr.iid, noteData.id.restId.toLong()).body()
}
}
eventSink(Deleted { it.id == id })
@@ -300,7 +299,7 @@ class GitLabMergeRequestDraftNoteImpl(
withContext(Dispatchers.IO) {
// Shouldn't require extra check, delete and get draft notes was introduced in
// the same update
api.rest.submitSingleDraftNote(project, mr.iid, noteData.id.restId.toLong()).body()
api.rest.submitSingleDraftNote(projectId, mr.iid, noteData.id.restId.toLong()).body()
}
}
mr.refreshData()
@@ -9,6 +9,7 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.containers.nullize
import git4idea.remote.GitRemoteUrlCoordinates
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
@@ -25,7 +26,6 @@ import kotlinx.coroutines.flow.transformWhile
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabGidData
import org.jetbrains.plugins.gitlab.api.GitLabGraphQLMutationException
import org.jetbrains.plugins.gitlab.api.GitLabId
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabServerMetadata
import org.jetbrains.plugins.gitlab.api.GitLabVersion
@@ -43,7 +43,6 @@ import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabMergeRequestDTO
import org.jetbrains.plugins.gitlab.mergerequest.api.request.createMergeRequest
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequest
import org.jetbrains.plugins.gitlab.upload.markdownUploadFile
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabRegistry
import org.jetbrains.plugins.gitlab.util.GitLabStatistics
import java.awt.image.BufferedImage
@@ -58,7 +57,9 @@ private val LOG = logger<GitLabProject>()
@CodeReviewDomainEntity
interface GitLabProject {
val projectMapping: GitLabProjectMapping
val projectCoordinates: GitLabProjectCoordinates
val gitRemote: GitRemoteUrlCoordinates
val projectId: String
val dataReloadSignal: SharedFlow<Unit>
val mergeRequests: GitLabProjectMergeRequestsStore
@@ -69,7 +70,6 @@ interface GitLabProject {
fun getMembersBatches(): Flow<List<GitLabUserDTO>>
val defaultBranch: String?
val gitLabProjectId: GitLabId
suspend fun isMultipleAssigneesAllowed(): Boolean
suspend fun isMultipleReviewersAllowed(): Boolean
@@ -106,15 +106,16 @@ class GitLabLazyProject(
parentCs: CoroutineScope,
private val api: GitLabApi,
private val glMetadata: GitLabServerMetadata?,
override val projectMapping: GitLabProjectMapping,
private val initialData: GitLabProjectDTO,
private val currentUser: GitLabUserDTO,
private val tokenRefreshFlow: Flow<Unit>,
override val projectCoordinates: GitLabProjectCoordinates,
override val gitRemote: GitRemoteUrlCoordinates,
) : GitLabProject {
private val cs = parentCs.childScope(javaClass.name)
private val projectCoordinates: GitLabProjectCoordinates = projectMapping.repository
override val projectId: String = initialData.id.guessRestId()
private val _dataReloadSignal = MutableSharedFlow<Unit>(replay = 1)
override val dataReloadSignal: SharedFlow<Unit> = _dataReloadSignal.asSharedFlow()
@@ -126,20 +127,19 @@ class GitLabLazyProject(
loadMultipleAssigneesAllowedFallback()
}
override val gitLabProjectId: GitLabId = initialData.id
override val mergeRequests by lazy {
CachingGitLabProjectMergeRequestsStore(project, cs, api, glMetadata, projectMapping, currentUser, tokenRefreshFlow)
override val mergeRequests: GitLabProjectMergeRequestsStore by lazy {
CachingGitLabProjectMergeRequestsStore(project, cs, api, glMetadata, currentUser,
tokenRefreshFlow, projectCoordinates, projectId, gitRemote)
}
private val labelsLoader = BatchesLoader(cs,
api.graphQL.createAllProjectLabelsFlow(projectMapping.repository).map { labels ->
api.graphQL.createAllProjectLabelsFlow(projectCoordinates.projectPath).map { labels ->
labels.map {
GitLabLabel(it.title, it.color)
}
})
private val membersLoader = BatchesLoader(cs,
ApiPageUtil.createPagesFlowByLinkHeader(getProjectUsersURI(projectMapping.repository)) {
ApiPageUtil.createPagesFlowByLinkHeader(api.rest.getProjectUsersURI(projectId)) {
api.rest.getProjectUsers(it)
}.map { response -> response.body().map(GitLabUserDTO::fromRestDTO) })
@@ -189,7 +189,7 @@ class GitLabLazyProject(
val assigneeIds = assignees.nullize()?.map { GitLabGidData(it.id).guessRestId() }
val labelTitles = labels.nullize()?.map { it.title }
val iid = api.rest.createMergeRequest(
projectCoordinates,
projectId,
sourceBranch,
targetBranch,
title,
@@ -200,7 +200,7 @@ class GitLabLazyProject(
).body().iid
val attempts = GitLabRegistry.getRequestPollingAttempts()
repeat(attempts) {
val data = api.graphQL.loadMergeRequest(projectCoordinates, iid).body()
val data = api.graphQL.loadMergeRequest(projectCoordinates.projectPath, iid).body()
if (data?.diffRefs != null) {
return@async data
}
@@ -221,7 +221,7 @@ class GitLabLazyProject(
val filename = path.fileName.toString()
val mimeType = Files.probeContentType(path) ?: "application/octet-stream"
Files.newInputStream(path).use {
api.rest.markdownUploadFile(projectCoordinates, filename, mimeType, it).body()
api.rest.markdownUploadFile(projectId, filename, mimeType, it).body()
}
}.await()
GitLabStatistics.logFileUploadActionExecuted(project)
@@ -235,7 +235,7 @@ class GitLabLazyProject(
outputStream.toByteArray()
}
ByteArrayInputStream(byteArray).use {
api.rest.markdownUploadFile(projectCoordinates, "image.png", "image/png", it).body()
api.rest.markdownUploadFile(projectId, "image.png", "image/png", it).body()
}
}.await()
GitLabStatistics.logFileUploadActionExecuted(project)
@@ -247,10 +247,10 @@ class GitLabLazyProject(
}
private suspend fun getAllowsMultipleAssigneesPropertyFromNamespacePlan() = try {
api.rest.getProjectNamespace(projectMapping.repository.projectPath.owner).body()?.plan?.let {
api.rest.getProjectNamespace(projectCoordinates.projectPath.owner).body()?.plan?.let {
it != GitLabPlan.FREE
} ?: run {
LOG.warn("Failed to find namespace for project ${projectMapping.repository}")
LOG.warn("Failed to find namespace for project ${projectCoordinates.projectPath.fullPath()}")
null
}
}
@@ -258,13 +258,13 @@ class GitLabLazyProject(
throw ce
}
catch (e: Exception) {
LOG.warn("Failed to load namespace for project ${projectMapping.repository}", e)
LOG.warn("Failed to load namespace for project ${projectCoordinates.projectPath.fullPath()}", e)
null
}
private suspend fun getAllowsMultipleAssigneesPropertyFromIssueWidget(): Boolean {
val widgetAssignees: WorkItemWidgetAssignees? = try {
api.graphQL.createAllWorkItemsFlow(projectMapping.repository)
api.graphQL.createAllWorkItemsFlow(projectCoordinates.projectPath)
.transformWhile { workItems ->
val widget = workItems.find { workItem -> workItem.workItemType.name == WorkItemType.ISSUE_TYPE }
?.widgets
@@ -285,7 +285,7 @@ class GitLabLazyProject(
throw ce
}
catch (e: Exception) {
LOG.warn("Failed to load work item widgets for project ${projectMapping.repository}", e)
LOG.warn("Failed to load work item widgets for project ${projectCoordinates.projectPath}", e)
return false
}
return widgetAssignees?.allowsMultipleAssignees ?: false
@@ -12,6 +12,7 @@ import com.intellij.collaboration.messages.CollaborationToolsBundle
import com.intellij.collaboration.util.ResultUtil.runCatchingUser
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import git4idea.remote.GitRemoteUrlCoordinates
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,7 +45,6 @@ import org.jetbrains.plugins.gitlab.mergerequest.api.request.getMergeRequestList
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequest
import org.jetbrains.plugins.gitlab.mergerequest.data.loaders.startGitLabRestETagListLoaderIn
import org.jetbrains.plugins.gitlab.util.GitLabApiRequestName
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import java.util.concurrent.ConcurrentHashMap
interface GitLabProjectMergeRequestsStore {
@@ -75,18 +75,20 @@ interface GitLabProjectMergeRequestsStore {
targetBranchName: String? = null): List<GitLabMergeRequestByBranchDTO>
}
class CachingGitLabProjectMergeRequestsStore(private val project: Project,
parentCs: CoroutineScope,
private val api: GitLabApi,
private val glMetadata: GitLabServerMetadata?,
private val projectMapping: GitLabProjectMapping,
private val currentUser: GitLabUserDTO,
private val tokenRefreshFlow: Flow<Unit>) : GitLabProjectMergeRequestsStore {
class CachingGitLabProjectMergeRequestsStore(
private val project: Project,
parentCs: CoroutineScope,
private val api: GitLabApi,
private val glMetadata: GitLabServerMetadata?,
private val currentUser: GitLabUserDTO,
private val tokenRefreshFlow: Flow<Unit>,
private val projectCoordinates: GitLabProjectCoordinates,
private val projectId: String,
private val gitRemote: GitRemoteUrlCoordinates,
) : GitLabProjectMergeRequestsStore {
private val cs = parentCs.childScope(this::class)
private val glProject: GitLabProjectCoordinates = projectMapping.repository
private val detailsCache = Caffeine.newBuilder()
.weakValues()
.build<String, GitLabMergeRequestDetails>()
@@ -99,7 +101,7 @@ class CachingGitLabProjectMergeRequestsStore(private val project: Project,
override fun getListLoaderIn(cs: CoroutineScope, searchQuery: String): ReloadablePotentiallyInfiniteListLoader<GitLabMergeRequestShortRestDTO> {
val loader = startGitLabRestETagListLoaderIn(
cs,
getMergeRequestListURI(glProject, searchQuery),
api.rest.getMergeRequestListURI(projectId, searchQuery),
{ it.id },
requestReloadFlow = tokenRefreshFlow.withInitial(Unit),
@@ -143,7 +145,15 @@ class CachingGitLabProjectMergeRequestsStore(private val project: Project,
.withInitial(iid)
.map { mrId -> runCatchingUser { loadMergeRequest(mrId) } } // TODO: create from cached details
.transformConsecutiveSuccesses {
mapScoped { mrData -> LoadedGitLabMergeRequest(project, this, api, glMetadata, projectMapping, currentUser, mrData) }
mapScoped { mrData -> LoadedGitLabMergeRequest(project,
this,
api,
glMetadata,
projectCoordinates,
projectId,
gitRemote,
currentUser,
mrData) }
}
.shareIn(cs, SharingStarted.WhileSubscribed(0, 1000), 1)
// this the model will only be alive while it's needed
@@ -154,7 +164,7 @@ class CachingGitLabProjectMergeRequestsStore(private val project: Project,
sourceBranchName: String,
targetBranchName: String?): List<GitLabMergeRequestByBranchDTO> =
withContext(Dispatchers.IO) {
val body = api.graphQL.findMergeRequestsByBranch(projectMapping.repository, state, sourceBranchName, targetBranchName).body()
val body = api.graphQL.findMergeRequestsByBranch(projectCoordinates.projectPath, state, sourceBranchName, targetBranchName).body()
body!!.nodes
}
@@ -167,7 +177,7 @@ class CachingGitLabProjectMergeRequestsStore(private val project: Project,
@Throws(HttpStatusErrorException::class, IllegalStateException::class)
private suspend fun loadMergeRequest(iid: String): GitLabMergeRequestDTO {
return withContext(Dispatchers.IO) {
val body = api.graphQL.loadMergeRequest(glProject, iid).body()
val body = api.graphQL.loadMergeRequest(projectCoordinates.projectPath, iid).body()
if (body == null) {
api.rest.getCurrentUser() // Exception is generated automatically if status code >= 400
error(CollaborationToolsBundle.message("graphql.errors", "empty response"))
@@ -49,10 +49,14 @@ internal class GitLabMergeRequestViewModels(
private val openMergeRequestTimeline: (String, Boolean) -> Unit,
private val openMergeRequestDiff: (String, Boolean) -> Unit,
) {
private val htmlConverter: GitLabMarkdownToHtmlConverter = GitLabMarkdownToHtmlConverter(project,
projectData.projectMapping.gitRepository,
projectData.projectMapping.repository,
projectData.gitLabProjectId)
private val htmlConverter: GitLabMarkdownToHtmlConverter =
GitLabMarkdownToHtmlConverter(
project,
projectData.gitRemote.repository,
projectData.projectCoordinates.serverPath,
projectData.projectId,
projectData.projectCoordinates.projectPath
)
private val cs = parentCs.childScope(javaClass.name)
@@ -82,7 +86,9 @@ internal class GitLabMergeRequestViewModels(
val diffVm: GitLabMergeRequestDiffViewModel get() = _diffVm
val editorReviewVm: GitLabMergeRequestEditorReviewViewModel by lazy {
GitLabMergeRequestEditorReviewViewModel(cs, project, projectData.projectMapping, currentUser, mergeRequest,
GitLabMergeRequestEditorReviewViewModel(cs, project,
projectData.gitRemote, projectData.projectCoordinates,
currentUser, mergeRequest,
discussionsVms, avatarIconProvider, imageLoader,
openMergeRequestDetails, openMergeRequestDiff).apply {
setup()
@@ -32,7 +32,7 @@ import javax.swing.JComponent
internal object GitLabMergeRequestCreateComponentFactory {
fun create(project: Project, cs: CoroutineScope, createVm: GitLabMergeRequestCreateViewModel): JComponent {
val directionModel = GitLabMergeRequestCreateDirectionModel(createVm.projectsManager, createVm.projectData.projectMapping)
val directionModel = GitLabMergeRequestCreateDirectionModel(createVm)
cs.launchNow {
createVm.branchState.filterNotNull().collect { branchState ->
directionModel.baseBranch = branchState.baseBranch
@@ -5,15 +5,13 @@ import com.intellij.collaboration.ui.SimpleEventListener
import com.intellij.util.EventDispatcher
import git4idea.GitBranch
import git4idea.GitRemoteBranch
import git4idea.remote.hosting.knownRepositories
import git4idea.ui.branch.MergeDirectionModel
import org.jetbrains.plugins.gitlab.GitLabProjectsManager
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
internal class GitLabMergeRequestCreateDirectionModel(
private val repositoriesManager: GitLabProjectsManager,
override val baseRepo: GitLabProjectMapping
private val createVm: GitLabMergeRequestCreateViewModel
) : MergeDirectionModel<GitLabProjectMapping> {
override val baseRepo: GitLabProjectMapping = createVm.projectMapping
private val changeEventDispatcher = EventDispatcher.create(SimpleEventListener::class.java)
override var baseBranch: GitRemoteBranch? = null
@@ -37,7 +35,7 @@ internal class GitLabMergeRequestCreateDirectionModel(
}
override fun getKnownRepoMappings(): List<GitLabProjectMapping> {
return repositoriesManager.knownRepositories.toList()
return createVm.getAllKnownProjects()
}
override fun setHead(repo: GitLabProjectMapping?, branch: GitBranch?) {
@@ -28,6 +28,7 @@ import git4idea.GitRemoteBranch
import git4idea.config.GitSharedSettings
import git4idea.history.GitLogUtil
import git4idea.remote.hosting.changesSignalFlow
import git4idea.remote.hosting.knownRepositories
import git4idea.repo.GitRepository
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
@@ -44,7 +45,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -61,8 +61,7 @@ import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabStatistics
internal interface GitLabMergeRequestCreateViewModel : CodeReviewTitleDescriptionViewModel {
val projectsManager: GitLabProjectsManager
val projectData: GitLabProject
val projectMapping: GitLabProjectMapping
val avatarIconProvider: IconsProvider<GitLabUserDTO>
val titleGenerationVm: StateFlow<GitLabMergeRequestCreateTitleGenerationViewModel?>
@@ -91,6 +90,8 @@ internal interface GitLabMergeRequestCreateViewModel : CodeReviewTitleDescriptio
fun updateBranchState(state: BranchState?)
fun getAllKnownProjects(): List<GitLabProjectMapping>
fun setReviewers(reviewers: List<GitLabUserDTO>)
fun setAssignees(assignees: List<GitLabUserDTO>)
fun setLabels(labels: List<GitLabLabel>)
@@ -102,8 +103,8 @@ internal interface GitLabMergeRequestCreateViewModel : CodeReviewTitleDescriptio
internal class GitLabMergeRequestCreateViewModelImpl(
private val project: Project,
parentCs: CoroutineScope,
override val projectsManager: GitLabProjectsManager,
override val projectData: GitLabProject,
private val projectsManager: GitLabProjectsManager,
private val projectData: GitLabProject,
override val avatarIconProvider: IconsProvider<GitLabUserDTO>,
override val openReviewTabAction: suspend (mrIid: String) -> Unit,
private val onReviewCreated: () -> Unit,
@@ -158,7 +159,9 @@ internal class GitLabMergeRequestCreateViewModelImpl(
}?.iid
}
private val gitRepository: GitRepository = projectData.projectMapping.gitRepository
private val gitRepository: GitRepository = projectData.gitRemote.repository
override val projectMapping: GitLabProjectMapping = GitLabProjectMapping(projectData.projectCoordinates, projectData.gitRemote)
private val commitRevisionComparisonFlow: Flow<CommitRevisionComparison?> =
combine(gitRepository.changesSignalFlow().withInitial(Unit), branchState) { _, state -> state }
.map { state ->
@@ -240,7 +243,7 @@ internal class GitLabMergeRequestCreateViewModelImpl(
init {
cs.launch {
val baseRepo = projectData.projectMapping
val baseRepo = GitLabProjectMapping(projectData.projectCoordinates, projectData.gitRemote)
val baseGitRepo = baseRepo.gitRepository
val defaultBranch = projectData.defaultBranch ?: return@launch
val baseBranch = baseGitRepo.getBranchTrackInfo(defaultBranch)?.remoteBranch ?: return@launch
@@ -277,6 +280,8 @@ internal class GitLabMergeRequestCreateViewModelImpl(
_branchState.value = state
}
override fun getAllKnownProjects(): List<GitLabProjectMapping> = projectsManager.knownRepositories.toList()
override fun setReviewers(reviewers: List<GitLabUserDTO>) {
_reviewers.value = reviewers
GitLabStatistics.logMrCreationReviewersAdjusted(project)
@@ -9,9 +9,9 @@ import com.intellij.collaboration.ui.codereview.details.model.CodeReviewBranches
import com.intellij.collaboration.ui.codereview.details.model.CodeReviewBranchesViewModel
import com.intellij.dvcs.DvcsUtil
import com.intellij.openapi.diagnostic.thisLogger
import git4idea.remote.GitRemoteUrlCoordinates
import git4idea.remote.hosting.GitRemoteBranchesUtil
import git4idea.remote.hosting.changesSignalFlow
import git4idea.repo.GitRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
@@ -19,20 +19,20 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestFullDetails
import org.jetbrains.plugins.gitlab.mergerequest.data.getSourceRemoteDescriptor
import org.jetbrains.plugins.gitlab.mergerequest.data.getSpecialRemoteBranchForHead
import org.jetbrains.plugins.gitlab.mergerequest.util.GitLabMergeRequestBranchUtil
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabStatistics
internal class GitLabMergeRequestBranchesViewModel(
parentCs: CoroutineScope,
private val mergeRequest: GitLabMergeRequest,
private val mapping: GitLabProjectMapping
private val serverPath: GitLabServerPath,
private val gitRemote: GitRemoteUrlCoordinates,
) : CodeReviewBranchesViewModel {
private val gitRepository: GitRepository = mapping.remote.repository
private val cs: CoroutineScope = parentCs.childScope(this::class)
@@ -48,15 +48,15 @@ internal class GitLabMergeRequestBranchesViewModel(
return "$sourceProjectOwner:${details.sourceBranch}"
}
override val isCheckedOut: SharedFlow<Boolean> = gitRepository.changesSignalFlow().withInitial(Unit)
override val isCheckedOut: SharedFlow<Boolean> = gitRemote.repository.changesSignalFlow().withInitial(Unit)
.combine(mergeRequest.details) { _, details ->
val sourceRemote = details.getSourceRemoteDescriptor(mapping.repository.serverPath)
val sourceRemote = details.getSourceRemoteDescriptor(serverPath)
if (sourceRemote != null) {
GitRemoteBranchesUtil.isRemoteBranchCheckedOut(gitRepository, sourceRemote, details.sourceBranch)
GitRemoteBranchesUtil.isRemoteBranchCheckedOut(gitRemote.repository, sourceRemote, details.sourceBranch)
}
else {
val specialRef = details.getSpecialRemoteBranchForHead(mapping.gitRemote)
GitRemoteBranchesUtil.isRemoteBranchCheckedOut(gitRepository, specialRef)
val specialRef = details.getSpecialRemoteBranchForHead(gitRemote.remote)
GitRemoteBranchesUtil.isRemoteBranchCheckedOut(gitRemote.repository, specialRef)
}
}.modelFlow(cs, thisLogger())
@@ -66,18 +66,18 @@ internal class GitLabMergeRequestBranchesViewModel(
override fun fetchAndCheckoutRemoteBranch() {
cs.launch {
val details = mergeRequest.details.first()
GitLabMergeRequestBranchUtil.fetchAndCheckoutBranch(mapping, details)
GitLabMergeRequestBranchUtil.fetchAndCheckoutBranch(gitRemote.repository, serverPath, details)
}
GitLabStatistics.logMrActionExecuted(gitRepository.project, GitLabStatistics.MergeRequestAction.BRANCH_CHECKOUT)
GitLabStatistics.logMrActionExecuted(gitRemote.repository.project, GitLabStatistics.MergeRequestAction.BRANCH_CHECKOUT)
}
override val canShowInLog: Boolean = true
override fun fetchAndShowInLog() {
cs.launch {
val details = mergeRequest.details.first()
GitLabMergeRequestBranchUtil.fetchAndShowRemoteBranchInLog(mapping, details)
GitLabMergeRequestBranchUtil.fetchAndShowRemoteBranchInLog(gitRemote.repository, serverPath, details)
}
GitLabStatistics.logMrActionExecuted(gitRepository.project, GitLabStatistics.MergeRequestAction.SHOW_BRANCH_IN_LOG)
GitLabStatistics.logMrActionExecuted(gitRemote.repository.project, GitLabStatistics.MergeRequestAction.SHOW_BRANCH_IN_LOG)
}
override fun showBranches() {
@@ -69,11 +69,11 @@ internal class GitLabMergeRequestChangeListViewModelImpl(
persistentChangesViewedState.updatesFlow.withInitial(Unit),
) { discPos, draftsPos, _ ->
changes.associateWith { change ->
val sha = parsedChanges.findLatestCommitWithChangesTo(mergeRequest.gitRepository, change.filePath)
val sha = parsedChanges.findLatestCommitWithChangesTo(mergeRequest.gitRemote.repository, change.filePath)
val isRead = !isOnLatest || sha?.let {
persistentChangesViewedState.isViewed(
mergeRequest.glProject, mergeRequest.iid,
mergeRequest.gitRepository,
mergeRequest.serverPath, mergeRequest.projectId, mergeRequest.iid,
mergeRequest.gitRemote.repository,
change.filePath, it
)
} ?: false
@@ -104,13 +104,13 @@ internal class GitLabMergeRequestChangeListViewModelImpl(
override fun setViewedState(changes: Iterable<RefComparisonChange>, viewed: Boolean) {
val filePathsWithShas = changes.mapNotNull { change ->
val path = change.filePath
parsedChanges.findLatestCommitWithChangesTo(mergeRequest.gitRepository, path)?.let {
parsedChanges.findLatestCommitWithChangesTo(mergeRequest.gitRemote.repository, path)?.let {
path to it
}
}
persistentChangesViewedState.markViewed(
mergeRequest.glProject, mergeRequest.iid,
mergeRequest.gitRepository,
mergeRequest.serverPath, mergeRequest.projectId, mergeRequest.iid,
mergeRequest.gitRemote.repository,
filePathsWithShas,
viewed
)
@@ -50,7 +50,7 @@ internal class GitLabMergeRequestDetailsViewModelImpl(
currentUser: GitLabUserDTO,
projectData: GitLabProject,
private val mergeRequest: GitLabMergeRequest,
private val avatarIconsProvider: IconsProvider<GitLabUserDTO>,
avatarIconsProvider: IconsProvider<GitLabUserDTO>,
htmlConverter: GitLabMarkdownToHtmlConverter,
) : GitLabMergeRequestDetailsViewModel {
@@ -86,9 +86,19 @@ internal class GitLabMergeRequestDetailsViewModelImpl(
override val detailsReviewFlowVm = GitLabMergeRequestReviewFlowViewModelImpl(
project, cs, currentUser, projectData, mergeRequest, avatarIconsProvider
)
override val branchesVm = GitLabMergeRequestBranchesViewModel(cs, mergeRequest, projectData.projectMapping)
override val statusVm = GitLabMergeRequestStatusViewModelImpl(project, cs, projectData.projectMapping.gitRepository,
projectData.projectMapping.repository.serverPath, mergeRequest)
override val branchesVm = GitLabMergeRequestBranchesViewModel(
cs,
mergeRequest,
projectData.projectCoordinates.serverPath,
projectData.gitRemote
)
override val statusVm = GitLabMergeRequestStatusViewModelImpl(
project,
cs,
projectData.gitRemote.repository,
projectData.projectCoordinates.serverPath,
mergeRequest
)
override val changesVm = GitLabMergeRequestChangesViewModelImpl(project, cs, mergeRequest, htmlConverter)
override fun reloadData() {
@@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import java.time.Duration
@Service(Service.Level.PROJECT)
@@ -40,7 +40,8 @@ internal class GitLabPersistentMergeRequestChangesViewedState
@Serializable
data class MRId(
val project: GitLabProjectCoordinates,
val serverPath: GitLabServerPath,
val projectId: String,
val iid: String
)
@@ -55,22 +56,31 @@ internal class GitLabPersistentMergeRequestChangesViewedState
val viewedFiles: Map<String, String> = mapOf()
)
fun isViewed(glProject: GitLabProjectCoordinates, iid: String,
gitRepository: GitRepository,
filePath: FilePath, sha: String): Boolean {
fun isViewed(
serverPath: GitLabServerPath,
projectId: String,
iid: String,
gitRepository: GitRepository,
filePath: FilePath,
sha: String,
): Boolean {
val relPath = VcsFileUtil.relativePath(gitRepository.root, filePath)
return state.statesMap[MRId(glProject, iid)]?.let {
return state.statesMap[MRId(serverPath, projectId, iid)]?.let {
it.viewedFiles[relPath] == sha
} ?: false
}
fun markViewed(glProject: GitLabProjectCoordinates, iid: String,
gitRepository: GitRepository,
filePathsAndShas: Iterable<Pair<FilePath, String>>,
viewed: Boolean) {
fun markViewed(
serverPath: GitLabServerPath,
projectId: String,
iid: String,
gitRepository: GitRepository,
filePathsAndShas: Iterable<Pair<FilePath, String>>,
viewed: Boolean,
) {
val relPathsAndShas = filePathsAndShas.map { VcsFileUtil.relativePath(gitRepository.root, it.first) to it.second }
updateStateOrCreateAndCleanup(MRId(glProject, iid)) { st ->
updateStateOrCreateAndCleanup(MRId(serverPath, projectId, iid)) { st ->
if (viewed) {
MRViewedState(id = st.id, viewedFiles = st.viewedFiles + relPathsAndShas)
}
@@ -119,8 +119,8 @@ internal class GitLabMergeRequestDiffReviewViewModelImpl(
override fun markViewed() {
val sha = mergeRequest.details.value.diffRefs?.headSha ?: return
persistentChangesViewedState.markViewed(
mergeRequest.glProject, mergeRequest.iid,
mergeRequest.gitRepository,
mergeRequest.serverPath, mergeRequest.projectId, mergeRequest.iid,
mergeRequest.gitRemote.repository,
listOf(change.filePath to sha),
true
)
@@ -30,7 +30,9 @@ import com.intellij.util.EventDispatcher
import git4idea.branch.GitBranchSyncStatus
import git4idea.changes.GitBranchComparisonResult
import git4idea.changes.GitTextFilePatchWithHistory
import git4idea.remote.GitRemoteUrlCoordinates
import git4idea.remote.hosting.localCommitsSyncStatus
import git4idea.repo.GitRepository
import git4idea.ui.branch.GitCurrentBranchPresenter
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
@@ -55,6 +57,7 @@ import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
import org.jetbrains.plugins.gitlab.data.GitLabImageLoader
import org.jetbrains.plugins.gitlab.mergerequest.GitLabMergeRequestsPreferences
@@ -63,7 +66,6 @@ import org.jetbrains.plugins.gitlab.mergerequest.ui.createDiffDataFlow
import org.jetbrains.plugins.gitlab.mergerequest.ui.review.GitLabMergeRequestDiscussionsViewModels
import org.jetbrains.plugins.gitlab.mergerequest.ui.review.GitLabMergeRequestReviewViewModelBase
import org.jetbrains.plugins.gitlab.mergerequest.util.GitLabMergeRequestBranchUtil
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabStatistics
import java.util.EventListener
@@ -73,7 +75,8 @@ private val LOG = logger<GitLabMergeRequestEditorReviewViewModel>()
class GitLabMergeRequestEditorReviewViewModel internal constructor(
parentCs: CoroutineScope,
private val project: Project,
private val projectMapping: GitLabProjectMapping,
gitRemote: GitRemoteUrlCoordinates,
private val actualProjectCoordinates: GitLabProjectCoordinates,
currentUser: GitLabUserDTO,
private val mergeRequest: GitLabMergeRequest,
private val discussionsVms: GitLabMergeRequestDiscussionsViewModels,
@@ -88,6 +91,8 @@ class GitLabMergeRequestEditorReviewViewModel internal constructor(
), CodeReviewInEditorViewModel {
private val preferences = project.service<GitLabMergeRequestsPreferences>()
private val gitRepository: GitRepository = gitRemote.repository
val mergeRequestIid: String = mergeRequest.iid
private val _actualChangesState = MutableStateFlow<ChangesState>(ChangesState.NotLoaded)
@@ -138,7 +143,7 @@ class GitLabMergeRequestEditorReviewViewModel internal constructor(
@OptIn(ExperimentalCoroutinesApi::class)
val localRepositorySyncStatus: StateFlow<ComputedResult<GitBranchSyncStatus?>?> by lazy {
val repository = projectMapping.remote.repository
val repository = gitRepository
_actualChangesState.map {
(it as? ChangesState.Loaded)?.changes?.commits?.map { it.sha }
}.distinctUntilChanged().transformLatest {
@@ -188,7 +193,7 @@ class GitLabMergeRequestEditorReviewViewModel internal constructor(
override fun updateBranch() {
cs.launch {
val details = mergeRequest.refreshDataNow()
GitLabMergeRequestBranchUtil.fetchAndCheckoutBranch(projectMapping, details)
GitLabMergeRequestBranchUtil.fetchAndCheckoutBranch(gitRepository, actualProjectCoordinates.serverPath, details)
}
}
@@ -238,7 +243,7 @@ class GitLabMergeRequestEditorReviewViewModel internal constructor(
*/
fun getFileStateFlow(virtualFile: VirtualFile): Flow<FileReviewState> {
if (!virtualFile.isValid || virtualFile.isDirectory ||
!VfsUtilCore.isAncestor(projectMapping.remote.repository.root, virtualFile, true)) {
!VfsUtilCore.isAncestor(gitRepository.root, virtualFile, true)) {
return flowOf(FileReviewState.NotInReview)
}
val filePath = VcsContextFactory.getInstance().createFilePathOn(virtualFile)
@@ -81,7 +81,7 @@ class GitLabMergeRequestTimelineDiscussionViewModelImpl(
.modelFlow(cs, LOG)
override val id: String = discussion.id.toString()
override val serverUrl: URL = mr.glProject.serverPath.toURL()
override val serverUrl: URL = mr.serverPath.toURL()
override val author: Flow<GitLabUserDTO> = mainNote.map { it.author }
private val _repliesFolded = MutableStateFlow(true)
@@ -126,7 +126,7 @@ sealed interface GitLabMergeRequestTimelineItemViewModel {
override val author: GitLabUserDTO = note.author
override val createdAt: Date? = note.createdAt
override val isDraft: Boolean = note is GitLabMergeRequestDraftNote
override val serverUrl: URL = mr.glProject.serverPath.toURL()
override val serverUrl: URL = mr.serverPath.toURL()
override val actionsVm: GitLabNoteAdminActionsViewModel? =
if (note is MutableGitLabNote && note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, projectData, note) else null
@@ -105,7 +105,7 @@ internal class LoadAllGitLabMergeRequestTimelineViewModel(
null
}
override val serverUrl: URL = mergeRequest.glProject.serverPath.toURL()
override val serverUrl: URL = mergeRequest.serverPath.toURL()
private val _diffRequests = MutableSharedFlow<ChangesSelection.Precise>()
val diffRequests: Flow<ChangesSelection.Precise> = _diffRequests.asSharedFlow()
@@ -45,7 +45,7 @@ internal sealed interface GitLabReviewTabViewModel : ReviewTabViewModel {
) : GitLabReviewTabViewModel, Disposable {
private val cs = parentCs.childScope(this::class).cancelledWith(this)
private val projectPath = projectData.projectMapping.repository.projectPath.fullPath()
private val projectPath = projectData.projectCoordinates.projectPath
override val displayName: String = GitLabBundle.message("merge.request.create.tab.title", projectPath)
val createVm = GitLabMergeRequestCreateViewModelImpl(
@@ -3,50 +3,67 @@ package org.jetbrains.plugins.gitlab.mergerequest.util
import git4idea.GitRemoteBranch
import git4idea.remote.hosting.GitRemoteBranchesUtil
import git4idea.repo.GitRepository
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestFullDetails
import org.jetbrains.plugins.gitlab.mergerequest.data.getSourceRemoteDescriptor
import org.jetbrains.plugins.gitlab.mergerequest.data.getSpecialRemoteBranchForHead
import org.jetbrains.plugins.gitlab.mergerequest.data.getTargetRemoteDescriptor
import org.jetbrains.plugins.gitlab.mergerequest.data.isFork
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
object GitLabMergeRequestBranchUtil {
private const val FORK_BRANCH_PREFIX = "fork"
private suspend fun findSourceRemoteBranch(mapping: GitLabProjectMapping, details: GitLabMergeRequestFullDetails): GitRemoteBranch? {
val sourceRemoteDescriptor = details.getSourceRemoteDescriptor(mapping.repository.serverPath)
private suspend fun findSourceRemoteBranch(
gitRepository: GitRepository,
serverPath: GitLabServerPath,
details: GitLabMergeRequestFullDetails
): GitRemoteBranch? {
val sourceRemoteDescriptor = details.getSourceRemoteDescriptor(serverPath)
if (sourceRemoteDescriptor != null) {
// Public fork / regular branch
return GitRemoteBranchesUtil.findOrCreateRemoteBranch(mapping.gitRepository, sourceRemoteDescriptor, details.sourceBranch)
return GitRemoteBranchesUtil.findOrCreateRemoteBranch(gitRepository, sourceRemoteDescriptor, details.sourceBranch)
} else {
// Private/deleted fork, can still fetch using special MR head ref
val targetRemoteDescriptor = details.getTargetRemoteDescriptor(mapping.repository.serverPath)
val targetRemote = GitRemoteBranchesUtil.findOrCreateRemote(mapping.gitRepository, targetRemoteDescriptor) ?: return null
val targetRemoteDescriptor = details.getTargetRemoteDescriptor(serverPath)
val targetRemote = GitRemoteBranchesUtil.findOrCreateRemote(gitRepository, targetRemoteDescriptor) ?: return null
return details.getSpecialRemoteBranchForHead(targetRemote)
}
}
private suspend fun findTargetRemoteBranch(mapping: GitLabProjectMapping, details: GitLabMergeRequestFullDetails): GitRemoteBranch? {
val targetRemoteDescriptor = details.getTargetRemoteDescriptor(mapping.repository.serverPath)
private suspend fun findTargetRemoteBranch(
gitRepository: GitRepository,
serverPath: GitLabServerPath,
details: GitLabMergeRequestFullDetails
): GitRemoteBranch? {
val targetRemoteDescriptor = details.getTargetRemoteDescriptor(serverPath)
return GitRemoteBranchesUtil.findOrCreateRemoteBranch(mapping.gitRepository, targetRemoteDescriptor, details.targetBranch)
return GitRemoteBranchesUtil.findOrCreateRemoteBranch(gitRepository, targetRemoteDescriptor, details.targetBranch)
}
suspend fun fetchAndCheckoutBranch(mapping: GitLabProjectMapping, details: GitLabMergeRequestFullDetails) {
suspend fun fetchAndCheckoutBranch(
gitRepository: GitRepository,
serverPath: GitLabServerPath,
details: GitLabMergeRequestFullDetails
) {
val localPrefix = if (details.isFork()) {
if (details.sourceProject != null) "${FORK_BRANCH_PREFIX}/${details.sourceProject.ownerPath}"
else "${FORK_BRANCH_PREFIX}/${details.author.username}"
} else null
val remoteBranch = findSourceRemoteBranch(mapping, details) ?: return
GitRemoteBranchesUtil.fetchAndCheckoutRemoteBranch(mapping.gitRepository, remoteBranch, localPrefix)
val remoteBranch = findSourceRemoteBranch(gitRepository, serverPath, details) ?: return
GitRemoteBranchesUtil.fetchAndCheckoutRemoteBranch(gitRepository, remoteBranch, localPrefix)
}
suspend fun fetchAndShowRemoteBranchInLog(mapping: GitLabProjectMapping, details: GitLabMergeRequestFullDetails) {
val sourceRemoteBranch = findSourceRemoteBranch(mapping, details) ?: return
val targetRemoteBranch = findTargetRemoteBranch(mapping, details)
suspend fun fetchAndShowRemoteBranchInLog(
gitRepository: GitRepository,
serverPath: GitLabServerPath,
details: GitLabMergeRequestFullDetails
) {
val sourceRemoteBranch = findSourceRemoteBranch(gitRepository, serverPath, details) ?: return
val targetRemoteBranch = findTargetRemoteBranch(gitRepository, serverPath, details)
GitRemoteBranchesUtil.fetchAndShowRemoteBranchInLog(mapping.gitRepository, sourceRemoteBranch, targetRemoteBranch)
GitRemoteBranchesUtil.fetchAndShowRemoteBranchInLog(gitRepository, sourceRemoteBranch, targetRemoteBranch)
}
}
@@ -22,7 +22,6 @@ import org.jetbrains.plugins.gitlab.GitLabProjectsManager
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabApiManager
import org.jetbrains.plugins.gitlab.api.GitLabProjectConnectionManager
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.request.findProject
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountManager
@@ -34,6 +33,7 @@ import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestState
import org.jetbrains.plugins.gitlab.mergerequest.ui.create.action.GitLabMergeRequestOpenCreateTabNotificationAction
import org.jetbrains.plugins.gitlab.mergerequest.ui.create.action.GitLabOpenMergeRequestExistingTabNotificationAction
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
private val LOG = logger<GitLabPushNotificationCustomizer>()
@@ -48,31 +48,53 @@ internal class GitLabPushNotificationCustomizer(private val project: Project) :
// If we already have a GitLab connection open, make sure it matches
val connection = project.serviceAsync<GitLabProjectConnectionManager>().connectionState.value
if (connection != null && (connection.repo.gitRepository != repository || connection.repo.gitRemote != remoteBranch.remote)) {
return emptyList()
}
val (projectMapping, account) = connection?.let {
it.repo to it.account
} ?: run {
val accountManager = serviceAsync<GitLabAccountManager>()
val savedAccount = project.serviceAsync<GitLabMergeRequestsPreferences>().selectedUrlAndAccountId?.second?.let { savedId ->
accountManager.findAccountOrNull { it.id == savedId }
if (connection != null) {
if (connection.repo.gitRepository != repository || connection.repo.gitRemote != remoteBranch.remote) {
return emptyList()
}
GitPushNotificationUtil.findRepositoryAndAccount(
project.serviceAsync<GitLabProjectsManager>().knownRepositories,
repository, remoteBranch.remote,
accountManager.accountsState.value,
savedAccount,
project.serviceAsync<GitLabProjectDefaultAccountHolder>().account
)
} ?: return emptyList()
return createActionList(connection.account, connection.projectData.projectCoordinates.projectPath, remoteBranch, connection.repo)
}
if (!canCreateReview(projectMapping, account, remoteBranch)) {
val accountManager = serviceAsync<GitLabAccountManager>()
val savedAccount = project.serviceAsync<GitLabMergeRequestsPreferences>().selectedUrlAndAccountId?.second?.let { savedId ->
accountManager.findAccountOrNull { it.id == savedId }
}
val (projectMapping, account) = GitPushNotificationUtil.findRepositoryAndAccount(
project.serviceAsync<GitLabProjectsManager>().knownRepositories,
repository, remoteBranch.remote,
accountManager.accountsState.value,
savedAccount,
project.serviceAsync<GitLabProjectDefaultAccountHolder>().account
) ?: return emptyList()
// be aware, for a renamed project no action will be returned, because of the incorrect path
val projectFullPath = projectMapping.repository.projectPath
return createActionList(account, projectFullPath, remoteBranch, projectMapping)
}
private suspend fun createActionList(
account: GitLabAccount,
projectFullPath: GitLabProjectPath,
remoteBranch: GitRemoteBranch,
projectMapping: GitLabProjectMapping,
): List<AnAction> {
val accountManager = serviceAsync<GitLabAccountManager>()
val token = accountManager.findCredentials(account) ?: return emptyList()
val api = serviceAsync<GitLabApiManager>().getClient(account.server, token)
if (!canCreateReview(api, projectFullPath, remoteBranch)) {
return emptyList()
}
val existingMRs = findExistingMergeRequests(projectMapping, account, remoteBranch)
val existingMRs = findExistingMergeRequests(api, projectFullPath, remoteBranch)
return actions(existingMRs, projectMapping, account)
}
private fun actions(
existingMRs: List<GitLabMergeRequestByBranchDTO>,
projectMapping: GitLabProjectMapping,
account: GitLabAccount,
): List<AnAction> {
return when (existingMRs.size) {
0 -> {
listOf(GitLabMergeRequestOpenCreateTabNotificationAction(project, projectMapping, account))
@@ -93,14 +115,13 @@ internal class GitLabPushNotificationCustomizer(private val project: Project) :
* - The repository must exist
* - The branch cannot be the default branch (we don't allow creating an MR from default -> default)
*/
private suspend fun canCreateReview(projectMapping: GitLabProjectMapping, account: GitLabAccount, branch: GitRemoteBranch): Boolean {
val accountManager = serviceAsync<GitLabAccountManager>()
val token = accountManager.findCredentials(account) ?: return false
val api = serviceAsync<GitLabApiManager>().getClient(account.server, token)
val repository = projectMapping.repository
val repositoryInfo = getRepositoryInfo(api, repository) ?: run {
LOG.warn("Repository not found: $repository")
private suspend fun canCreateReview(
api: GitLabApi,
projectFullPath: GitLabProjectPath,
branch: GitRemoteBranch,
): Boolean {
val repositoryInfo = getRepositoryInfo(api, projectFullPath) ?: run {
LOG.warn("Repository not found: $projectFullPath")
return false
}
@@ -109,15 +130,15 @@ internal class GitLabPushNotificationCustomizer(private val project: Project) :
return repositoryInfo.rootRef != remoteBranchName
}
private suspend fun getRepositoryInfo(api: GitLabApi, project: GitLabProjectCoordinates) =
private suspend fun getRepositoryInfo(api: GitLabApi, projectFullPath: GitLabProjectPath) =
try {
api.graphQL.findProject(project).body()?.repository
api.graphQL.findProject(projectFullPath).body()?.repository
}
catch (ce: CancellationException) {
throw ce
}
catch (e: Exception) {
LOG.warn("Failed to lookup repository $project", e)
LOG.warn("Failed to lookup repository $projectFullPath in $project", e)
null
}
@@ -125,28 +146,22 @@ internal class GitLabPushNotificationCustomizer(private val project: Project) :
* Look up any existing open merge requests on the given remote branch.
*/
private suspend fun findExistingMergeRequests(
projectMapping: GitLabProjectMapping,
account: GitLabAccount,
api: GitLabApi,
targetProjectPath: GitLabProjectPath,
branch: GitRemoteBranch,
): List<GitLabMergeRequestByBranchDTO> {
val accountManager = serviceAsync<GitLabAccountManager>()
val token = accountManager.findCredentials(account) ?: return emptyList()
val api = serviceAsync<GitLabApiManager>().getClient(account.server, token)
val repository = projectMapping.repository
val remoteBranchName = branch.nameForRemoteOperations
return withContext(Dispatchers.IO) {
val targetProjectPath = repository.projectPath.fullPath()
try {
val mrs = api.graphQL.findMergeRequestsByBranch(repository, GitLabMergeRequestState.OPENED, remoteBranchName).body()!!.nodes
mrs.filter { it.targetProject.fullPath == targetProjectPath && it.sourceProject?.fullPath == targetProjectPath }
val mrs = api.graphQL.findMergeRequestsByBranch(targetProjectPath, GitLabMergeRequestState.OPENED, remoteBranchName).body()!!.nodes
mrs.filter { it.targetProject.fullPath == targetProjectPath.fullPath() && it.sourceProject?.fullPath == targetProjectPath.fullPath() }
}
catch (ce: CancellationException) {
throw ce
}
catch (e: Exception) {
LOG.warn("Failed to lookup existing merge requests for branch $remoteBranchName in $repository", e)
LOG.warn("Failed to lookup existing merge requests for branch $remoteBranchName in $targetProjectPath", e)
emptyList()
}
}
@@ -44,9 +44,8 @@ import org.intellij.markdown.parser.sequentialparsers.impl.MathParser
import org.intellij.markdown.parser.sequentialparsers.impl.ReferenceLinkParser
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.NonNls
import org.jetbrains.plugins.gitlab.api.GitLabId
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.api.projectApiUri
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
import java.net.URI
import java.nio.file.InvalidPathException
@@ -59,8 +58,9 @@ import java.nio.file.InvalidPathException
class GitLabMarkdownToHtmlConverter(
private val project: Project,
private val repository: GitRepository,
projectCoordinates: GitLabProjectCoordinates,
projectId: GitLabId,
serverPath: GitLabServerPath,
projectId: String,
private val projectPath: GitLabProjectPath,
) {
companion object {
@@ -70,9 +70,8 @@ class GitLabMarkdownToHtmlConverter(
internal const val OPEN_MR_LINK_PREFIX = "glmergerequest:"
}
private val projectWebUrlBase: String = projectCoordinates.serverPath.toString() + "/-/project/" + projectId.guessRestId()
private val projectApiUri: URI = projectCoordinates.restApiUri
private val projectPath: GitLabProjectPath = projectCoordinates.projectPath
private val projectWebUrlBase: String = "$serverPath/-/project/$projectId"
private val projectApiUri: URI = serverPath.projectApiUri(projectId)
/**
* Makes file links relative to the git repository root or to the external root.
@@ -145,7 +145,7 @@ class GitLabMergeRequestStandaloneDraftNoteViewModelBase internal constructor(
override val author: GitLabUserDTO = note.author
override val createdAt: Date? = note.createdAt
override val isDraft: Boolean = true
override val serverUrl: URL = mr.glProject.serverPath.toURL()
override val serverUrl: URL = mr.serverPath.toURL()
override val actionsVm: GitLabNoteAdminActionsViewModel? =
if (note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, projectData, note) else null
@@ -65,7 +65,7 @@ class GitLabNoteViewModelImpl(
override val author: GitLabUserDTO = note.author
override val createdAt: Date? = note.createdAt
override val isDraft: Boolean = note is GitLabMergeRequestDraftNote
override val serverUrl: URL = projectData.projectMapping.repository.serverPath.toURL()
override val serverUrl: URL = projectData.projectCoordinates.serverPath.toURL()
override val actionsVm: GitLabNoteAdminActionsViewModel? =
if (note is MutableGitLabNote && note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, projectData, note) else null
@@ -4,10 +4,9 @@ package org.jetbrains.plugins.gitlab.upload
import com.intellij.collaboration.api.json.loadJsonValue
import com.intellij.collaboration.util.resolveRelative
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.SinceGitLab
import org.jetbrains.plugins.gitlab.api.dto.GitLabUploadRestDTO
import org.jetbrains.plugins.gitlab.api.restApiUri
import org.jetbrains.plugins.gitlab.api.projectApiUrl
import java.io.InputStream
import java.net.http.HttpRequest.BodyPublishers
import java.net.http.HttpResponse
@@ -16,10 +15,10 @@ import java.util.UUID
@SinceGitLab("15.10")
internal suspend fun GitLabApi.Rest.markdownUploadFile(
project: GitLabProjectCoordinates,
filename: String,
mimeType: String,
fileInputStream: InputStream,
projectId: String,
filename: String,
mimeType: String,
fileInputStream: InputStream,
): HttpResponse<out GitLabUploadRestDTO> {
val boundary = "FormBoundary" + UUID.randomUUID()
val boundaryStart = "--$boundary\r\n" +
@@ -33,7 +32,7 @@ internal suspend fun GitLabApi.Rest.markdownUploadFile(
BodyPublishers.ofString(boundaryEnd)
)
val uri = project.restApiUri.resolveRelative("uploads")
val uri = projectApiUrl(projectId).resolveRelative("uploads")
val httpRequest = request(uri)
.POST(bodyPublisher)
.header("Content-Type", "multipart/form-data; boundary=$boundary")
@@ -3,7 +3,6 @@ package org.jetbrains.plugins.gitlab.util
import com.intellij.openapi.util.NlsSafe
import com.intellij.util.text.nullize
import git4idea.remote.GitRemoteUrlCoordinates
import git4idea.remote.hosting.GitHostingUrlUtil
import kotlinx.serialization.Serializable
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
@@ -17,13 +16,17 @@ data class GitLabProjectPath(val owner: @NlsSafe String, val name: @NlsSafe Stri
override fun toString(): String = "$owner/$name"
companion object {
fun create(server: GitLabServerPath, remote: GitRemoteUrlCoordinates): GitLabProjectPath? {
fun create(server: GitLabServerPath, gitRemoteUrl: String): GitLabProjectPath? {
val serverPath = server.toURI().path
val remotePath = GitHostingUrlUtil.getUriFromRemoteUrl(remote.url)?.path ?: return null
val remotePath = GitHostingUrlUtil.getUriFromRemoteUrl(gitRemoteUrl)?.path ?: return null
if (!remotePath.startsWith(serverPath)) return null
val repositoryPath = remotePath.removePrefix(serverPath).removePrefix("/")
return extractProjectPath(repositoryPath)
}
fun extractProjectPath(repositoryPath: String): GitLabProjectPath? {
val lastSlashIdx = repositoryPath.lastIndexOf('/')
if (lastSlashIdx < 0) return null
@@ -96,7 +96,7 @@ object GitLabStatistics {
//endregion
//region Counters
private val COUNTERS_GROUP = EventLogGroup("vcs.gitlab.counters", version = 30)
private val COUNTERS_GROUP = EventLogGroup("vcs.gitlab.counters", version = 31)
/**
* Server metadata was fetched
@@ -333,6 +333,7 @@ object GitLabStatistics {
enum class GitLabApiRequestName {
REST_GET_CURRENT_USER,
REST_GET_PROJECT,
REST_GET_PROJECT_IS_FORKED,
REST_GET_PROJECT_NAMESPACE,
REST_GET_PROJECT_USERS,
@@ -446,7 +447,7 @@ internal class GitLabMetricsLoader(private val project: Project) {
val nonFork = withContext(Dispatchers.IO) {
knownRepos.firstOrNull { repo ->
val (api, _) = getApi(repo.repository.serverPath) ?: return@firstOrNull false
!api.isProjectForked(repo.repository)
!api.isProjectForked(repo.repository.projectPath)
}
}
if (nonFork != null) return nonFork
@@ -460,7 +461,7 @@ internal class GitLabMetricsLoader(private val project: Project) {
val chosenRepoMapping = chooseRepo() ?: return null
val (api, account) = getApi(chosenRepoMapping.repository.serverPath) ?: return null
return api.graphQL.getMergeRequestMetrics(chosenRepoMapping.repository, account.name).body()
return api.graphQL.getMergeRequestMetrics(chosenRepoMapping.repository.projectPath, account.name).body()
}
catch (e: Exception) {
LOG.warn("Failed to load metrics", e)
@@ -7,16 +7,14 @@ import git4idea.repo.GitRepository
import io.mockk.every
import io.mockk.mockk
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.GitLabRestIdData
import org.jetbrains.plugins.gitlab.api.GitLabServerPath
import org.jetbrains.plugins.gitlab.ui.GitLabMarkdownToHtmlConverter.Companion.OPEN_FILE_LINK_PREFIX
import org.jetbrains.plugins.gitlab.ui.GitLabMarkdownToHtmlConverter.Companion.OPEN_MR_LINK_PREFIX
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath
import org.jetbrains.plugins.gitlab.util.GitLabProjectPath.Companion.extractProjectPath
import java.nio.file.Path
private const val IMAGES_API_BASE = """http://base/url/api/v4/projects/test-account%2Fmr-test"""
private const val WEB_BASE = """http://base/url/-/project/testRestId"""
private const val IMAGES_API_BASE = """http://base/url/api/v4/projects/1"""
private const val WEB_BASE = """http://base/url/-/project/1"""
private const val P_CLASS = """class="custom_image""""
class GitLabMarkdownToHtmlConverterTest : LightPlatformTestCase() {
@@ -318,10 +316,9 @@ class GitLabMarkdownToHtmlConverterTest : LightPlatformTestCase() {
private fun convertToHtml(markdownSource: String): @NlsSafe String {
val serverPath = GitLabServerPath("http://base/url")
val projectPath = GitLabProjectPath("test-account", "mr-test")
val projectCoordinates = GitLabProjectCoordinates(serverPath, projectPath)
val projectId = GitLabRestIdData("testRestId")
val converter = GitLabMarkdownToHtmlConverter(project, gitRepository, projectCoordinates, projectId)
val projectId = "1"
val projectFullPath = extractProjectPath("test-account/mr-test") ?: error("Failed to extract project path")
val converter = GitLabMarkdownToHtmlConverter(project, gitRepository, serverPath, projectId, projectFullPath)
return converter.convertToHtml(markdownSource)
}
}
@@ -0,0 +1,86 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab.util
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class GitLabProjectPathTest {
@Test
fun `extractProjectPath with valid path`() {
val result = GitLabProjectPath.extractProjectPath("owner/project")
assertThat(result).isNotNull
assertThat(result?.owner).isEqualTo("owner")
assertThat(result?.name).isEqualTo("project")
}
@Test
fun `extractProjectPath with nested owner path`() {
val result = GitLabProjectPath.extractProjectPath("group/subgroup/project")
assertThat(result).isNotNull
assertThat(result?.owner).isEqualTo("group/subgroup")
assertThat(result?.name).isEqualTo("project")
}
@Test
fun `extractProjectPath with deeply nested path`() {
val result = GitLabProjectPath.extractProjectPath("org/team/subteam/project")
assertThat(result).isNotNull
assertThat(result?.owner).isEqualTo("org/team/subteam")
assertThat(result?.name).isEqualTo("project")
}
@Test
fun `extractProjectPath with no slash returns null`() {
val result = GitLabProjectPath.extractProjectPath("project")
assertThat(result).isNull()
}
@Test
fun `extractProjectPath with empty string returns null`() {
val result = GitLabProjectPath.extractProjectPath("")
assertThat(result).isNull()
}
@Test
fun `extractProjectPath with only slash returns null`() {
val result = GitLabProjectPath.extractProjectPath("/")
assertThat(result).isNull()
}
@Test
fun `extractProjectPath with trailing slash returns null`() {
val result = GitLabProjectPath.extractProjectPath("owner/project/")
assertThat(result).isNull()
}
@Test
fun `extractProjectPath with leading slash`() {
val result = GitLabProjectPath.extractProjectPath("/owner/project")
assertThat(result).isNotNull
assertThat(result?.owner).isEqualTo("/owner")
assertThat(result?.name).isEqualTo("project")
}
@Test
fun `extractProjectPath with empty owner returns null`() {
val result = GitLabProjectPath.extractProjectPath("/project")
assertThat(result).isNull()
}
@Test
fun `extractProjectPath with whitespace in path`() {
val result = GitLabProjectPath.extractProjectPath("owner with spaces/project name")
assertThat(result).isNotNull
assertThat(result?.owner).isEqualTo("owner with spaces")
assertThat(result?.name).isEqualTo("project name")
}
@Test
fun `extractProjectPath with special characters`() {
val result = GitLabProjectPath.extractProjectPath("owner-name/project.name")
assertThat(result).isNotNull
assertThat(result?.owner).isEqualTo("owner-name")
assertThat(result?.name).isEqualTo("project.name")
}
}
@@ -81,7 +81,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(9, 0)))
requiresAuthentication { api ->
val commits = api.rest.loadMergeRequestCommits(api.getMergeRequestCommitsURI(glTest1Coordinates, "2")).body()
val commits = api.rest.loadMergeRequestCommits(api.rest.getMergeRequestCommitsURI(glTest1ProjectId, "2")).body()
assertIterableEquals(glTest1Mr2CommitShortShas, commits.map { it.shortId })
}
@@ -92,7 +92,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(inRange(v(9, 0), v(15, 7)))
requiresAuthentication { api ->
val changes = api.rest.loadMergeRequestChanges(api.getMergeRequestChangesURI(glTest1Coordinates, "2")).body()
val changes = api.rest.loadMergeRequestChanges(api.rest.getMergeRequestChangesURI(glTest1ProjectId, "2")).body()
assertIterableEquals(glTest1Mr2ChangedFiles, changes.changes.map { it.newPath })
}
@@ -103,7 +103,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(15, 7)))
requiresAuthentication { api ->
val diffs = api.rest.loadMergeRequestDiffs(api.getMergeRequestDiffsURI(glTest1Coordinates, "2", 1)).body()
val diffs = api.rest.loadMergeRequestDiffs(api.rest.getMergeRequestDiffsURI(glTest1ProjectId, "2", 1)).body()
assertIterableEquals(glTest1Mr2ChangedFiles, diffs.map { it.newPath })
}
@@ -114,7 +114,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(7, 0)))
requiresAuthentication { api ->
val diffs = api.rest.loadCommitDiffs(getCommitDiffsURI(glTest1Coordinates, glTest1Mr2CommitShortShas[0])).body()
val diffs = api.rest.loadCommitDiffs(api.rest.getCommitDiffsURI(glTest1ProjectId, glTest1Mr2CommitShortShas[0])).body()
assertIterableEquals(glTest1Mr2ChangedFiles, diffs.map { it.newPath })
}
@@ -125,7 +125,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(7, 0)))
requiresAuthentication { api ->
val commit = api.rest.loadCommit(glTest1Coordinates, glTest1Mr2CommitShortShas[0]).body()
val commit = api.rest.loadCommit(glTest1ProjectId, glTest1Mr2CommitShortShas[0]).body()
assertEquals(glTest1Mr2CommitShas[0], commit.id)
}
@@ -136,7 +136,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(10, 6)))
requiresAuthentication { api ->
val uri = getMergeRequestDiscussionsUri(glTest1Coordinates, "2")
val uri = api.rest.getMergeRequestDiscussionsUri(glTest1ProjectId, "2")
val discussions = ApiPageUtil.createPagesFlowByLinkHeader(uri) {
api.rest.loadUpdatableJsonList<GitLabDiscussionRestDTO>(
GitLabApiRequestName.REST_GET_MERGE_REQUEST_DISCUSSIONS, it
@@ -160,14 +160,14 @@ class GitLabApiTest : GitLabApiTestCase() {
requiresAuthentication { api ->
val randomId = Random.nextLong()
val initialBody = "This is a new comment! ID=$randomId"
val addNoteResult = api.rest.addNote(volatileProjectCoordinates, volatileProjectMr1Iid, initialBody)
val addNoteResult = api.rest.addNote(volatileProjectId, volatileProjectMr1Iid, initialBody)
assertNotNull(addNoteResult)
val addNoteResultValue = addNoteResult.body()
assertNotNull(addNoteResultValue)
val nextBody = "Changed comment! ID=$randomId"
val updateNoteResult = api.rest.updateNote(volatileProjectCoordinates,
val updateNoteResult = api.rest.updateNote(volatileProjectId,
volatileProjectMr1Iid,
addNoteResultValue.id.toString(),
addNoteResultValue.notes.first().id.toString(),
@@ -176,19 +176,19 @@ class GitLabApiTest : GitLabApiTestCase() {
// Check body changed
// NOTE: WILL NOT WORK ON A CONSTANTLY RUNNING SERVER BECAUSE OF PAGINATION
val discussions = api.rest.loadMergeRequestDiscussions(volatileProjectCoordinates, volatileProjectMr1Iid).body()
val discussions = api.rest.loadMergeRequestDiscussions(volatileProjectId, volatileProjectMr1Iid).body()
val updatedNote = discussions.find { it.id.toString() == addNoteResultValue.id.toString() }
assertNotNull(updatedNote)
assertEquals(nextBody, updatedNote.notes[0].body)
val deleteNoteResult = api.rest.deleteNote(volatileProjectCoordinates,
val deleteNoteResult = api.rest.deleteNote(volatileProjectId,
volatileProjectMr1Iid,
updatedNote.id.toString(),
updatedNote.notes.first().id.toString()).body()
assertNotNull(deleteNoteResult)
// Check is deleted
val deletedNote = api.rest.loadMergeRequestDiscussions(volatileProjectCoordinates, volatileProjectMr1Iid).body()
val deletedNote = api.rest.loadMergeRequestDiscussions(volatileProjectId, volatileProjectMr1Iid).body()
.find { it.id.toString() == addNoteResultValue.id.toString() }
assertNull(deletedNote)
@@ -203,14 +203,14 @@ class GitLabApiTest : GitLabApiTestCase() {
val randomId = Random.nextLong()
val initialBody = "This is a diff note! ID=$randomId"
val addNoteResult = api.rest.addDiffNote(
volatileProjectCoordinates,
volatileProjectId,
volatileProjectMr1Iid,
GitLabDiffPositionInput("bd857928", "bd857928", 1, "063282e5", 1,
DiffPathsInputDTO("README.md", null)),
initialBody
).body()
assertNotNull(addNoteResult)
val deleteNoteResult = api.rest.deleteNote(volatileProjectCoordinates,
val deleteNoteResult = api.rest.deleteNote(volatileProjectId,
volatileProjectMr1Iid,
addNoteResult.id.toString(),
addNoteResult.notes[0].id.toString()).body()
@@ -226,7 +226,7 @@ class GitLabApiTest : GitLabApiTestCase() {
val randomId = Random.nextLong()
val initialBody = "This is a note! ID=$randomId"
val addNoteResult = api.rest.addNote(
volatileProjectCoordinates,
volatileProjectId,
volatileProjectMr1Iid,
initialBody
).body()
@@ -234,7 +234,7 @@ class GitLabApiTest : GitLabApiTestCase() {
val replyBody = "This is a reply! ID=$randomId"
val addNoteResult2 = api.rest.createReplyNote(
volatileProjectCoordinates,
volatileProjectId,
volatileProjectMr1Iid,
addNoteResult.id.toString(),
replyBody
@@ -242,18 +242,18 @@ class GitLabApiTest : GitLabApiTestCase() {
assertNotNull(addNoteResult2)
// NOTE: WILL NOT WORK ON A CONSTANTLY RUNNING SERVER BECAUSE OF PAGINATION
val result = api.rest.loadMergeRequestDiscussions(volatileProjectCoordinates, volatileProjectMr1Iid).body()
val result = api.rest.loadMergeRequestDiscussions(volatileProjectId, volatileProjectMr1Iid).body()
assertNotNull(result)
val discussion = result.find { addNoteResult.notes[0].body.contains(randomId.toString()) }
assertNotNull(discussion)
val deleteNoteResult1 =
api.rest.deleteNote(volatileProjectCoordinates, volatileProjectMr1Iid, discussion.id.toString(), discussion.notes[0].id.toString())
api.rest.deleteNote(volatileProjectId, volatileProjectMr1Iid, discussion.id.toString(), discussion.notes[0].id.toString())
.body()
assertNotNull(deleteNoteResult1)
val deleteNoteResult2 =
api.rest.deleteNote(volatileProjectCoordinates, volatileProjectMr1Iid, discussion.id.toString(), discussion.notes[1].id.toString())
api.rest.deleteNote(volatileProjectId, volatileProjectMr1Iid, discussion.id.toString(), discussion.notes[1].id.toString())
.body()
assertNotNull(deleteNoteResult2)
}
@@ -267,7 +267,7 @@ class GitLabApiTest : GitLabApiTestCase() {
val randomId = Random.nextLong()
val initialBody = "This is a diff note! ID=$randomId"
val addNoteResult = api.rest.addDiffNote(
volatileProjectCoordinates,
volatileProjectId,
volatileProjectMr1Iid,
GitLabDiffPositionInput("bd857928", "bd857928", 1, "063282e5", 1,
DiffPathsInputDTO("README.md", null)),
@@ -277,7 +277,7 @@ class GitLabApiTest : GitLabApiTestCase() {
val replyBody = "This is a reply! ID=$randomId"
val addNoteResult2 = api.rest.createReplyNote(
volatileProjectCoordinates,
volatileProjectId,
volatileProjectMr1Iid,
addNoteResult.id.toString(),
replyBody
@@ -285,7 +285,7 @@ class GitLabApiTest : GitLabApiTestCase() {
assertNotNull(addNoteResult2)
// NOTE: WILL NOT WORK ON A CONSTANTLY RUNNING SERVER BECAUSE OF PAGINATION
val result1 = api.rest.loadMergeRequestDiscussions(volatileProjectCoordinates, volatileProjectMr1Iid).body()
val result1 = api.rest.loadMergeRequestDiscussions(volatileProjectId, volatileProjectMr1Iid).body()
assertNotNull(result1)
val discussion1 = result1.find { addNoteResult.notes[0].body.contains(randomId.toString()) }
@@ -293,24 +293,24 @@ class GitLabApiTest : GitLabApiTestCase() {
assertFalse(discussion1.notes[0].resolved)
val resolveNoteResult =
api.rest.changeMergeRequestDiscussionResolve(volatileProjectCoordinates, volatileProjectMr1Iid, discussion1.id.toString(), true)
api.rest.changeMergeRequestDiscussionResolve(volatileProjectId, volatileProjectMr1Iid, discussion1.id.toString(), true)
.body()
assertNotNull(resolveNoteResult)
// Confirm is now resolved
val result2 = api.rest.loadMergeRequestDiscussions(volatileProjectCoordinates, volatileProjectMr1Iid).body()
val result2 = api.rest.loadMergeRequestDiscussions(volatileProjectId, volatileProjectMr1Iid).body()
assertNotNull(result2)
val discussion2 = result2.find { addNoteResult.notes[0].body.contains(randomId.toString()) }
assertNotNull(discussion2)
assertTrue(discussion2.notes[0].resolved)
val deleteNoteResult1 = api.rest.deleteNote(volatileProjectCoordinates,
val deleteNoteResult1 = api.rest.deleteNote(volatileProjectId,
volatileProjectMr1Iid,
discussion2.id.toString(),
discussion2.notes[0].id.toString()).body()
assertNotNull(deleteNoteResult1)
val deleteNoteResult2 = api.rest.deleteNote(volatileProjectCoordinates,
val deleteNoteResult2 = api.rest.deleteNote(volatileProjectId,
volatileProjectMr1Iid,
discussion2.id.toString(),
discussion2.notes[1].id.toString()).body()
@@ -323,7 +323,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(15, 9)))
requiresAuthentication { api ->
val uri = getMergeRequestDraftNotesUri(glTest1Coordinates, glTest1Mr2Iid)
val uri = api.rest.getMergeRequestDraftNotesUri(glTest1ProjectId, glTest1Mr2Iid)
val draftNotes = ApiPageUtil.createPagesFlowByLinkHeader(uri) {
api.rest.loadUpdatableJsonList<GitLabMergeRequestDraftNoteRestDTO>(
GitLabApiRequestName.REST_GET_DRAFT_NOTES, it
@@ -343,7 +343,7 @@ class GitLabApiTest : GitLabApiTestCase() {
requiresAuthentication { api ->
val mrs = api.rest.loadUpdatableJsonList<GitLabMergeRequestShortRestDTO>(
GitLabApiRequestName.REST_GET_MERGE_REQUESTS,
getMergeRequestListURI(glTest1Coordinates, "search=important")
api.rest.getMergeRequestListURI(glTest1ProjectId, "search=important")
).body()
assertNotNull(mrs)
@@ -356,7 +356,8 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(12, 0)))
requiresAuthentication { api ->
val mr = api.graphQL.loadMergeRequest(glTest1Coordinates, "2").body()
val testProject1 = glTest1Coordinates
val mr = api.graphQL.loadMergeRequest(testProject1.projectPath, "2").body()
assertNotNull(mr)
assertEquals("2", mr.iid)
@@ -368,7 +369,8 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(13, 1)))
requiresAuthentication { api ->
val mrs = api.graphQL.findMergeRequestsByBranch(glTest1Coordinates, GitLabMergeRequestState.ALL, "changes-on-b").body()
val testProject1 = glTest1Coordinates
val mrs = api.graphQL.findMergeRequestsByBranch(testProject1.projectPath, GitLabMergeRequestState.ALL, "changes-on-b").body()
assertNotNull(mrs)
assertEquals(listOf("3"), mrs.nodes.map { it.iid })
@@ -382,7 +384,7 @@ class GitLabApiTest : GitLabApiTestCase() {
requiresAuthentication { api ->
val reloadRequest = MutableSharedFlow<Unit>(1).withInitial(Unit)
val loader = startGitLabRestETagListLoaderIn(backgroundScope,
getMergeRequestStateEventsUri(glTest1Coordinates, "1"),
api.rest.getMergeRequestStateEventsUri(glTest1ProjectId, "1"),
{ it.id },
reloadRequest,
shouldTryToLoadAll = false) { uri, eTag ->
@@ -404,7 +406,7 @@ class GitLabApiTest : GitLabApiTestCase() {
requiresAuthentication { api ->
val reloadRequest = MutableSharedFlow<Unit>(1).withInitial(Unit)
val loader = startGitLabRestETagListLoaderIn(backgroundScope,
getMergeRequestLabelEventsUri(glTest1Coordinates, "1"),
api.rest.getMergeRequestLabelEventsUri(glTest1ProjectId, "1"),
{ it.id },
reloadRequest,
shouldTryToLoadAll = false) { uri, eTag ->
@@ -426,7 +428,7 @@ class GitLabApiTest : GitLabApiTestCase() {
requiresAuthentication { api ->
val reloadRequest = MutableSharedFlow<Unit>(1).withInitial(Unit)
val loader = startGitLabRestETagListLoaderIn(backgroundScope,
getMergeRequestMilestoneEventsUri(glTest1Coordinates, "1"),
api.rest.getMergeRequestMilestoneEventsUri(glTest1ProjectId, "1"),
{ it.id },
reloadRequest,
shouldTryToLoadAll = false) { uri, eTag ->
@@ -449,8 +451,8 @@ class GitLabApiTest : GitLabApiTestCase() {
}
requiresAuthentication { api ->
api.rest.mergeRequestApprove(volatileProjectCoordinates, volatileProjectMr2Iid).body()
api.rest.mergeRequestUnApprove(volatileProjectCoordinates, volatileProjectMr2Iid).body()
api.rest.mergeRequestApprove(volatileProjectId, volatileProjectMr2Iid).body()
api.rest.mergeRequestUnApprove(volatileProjectId, volatileProjectMr2Iid).body()
}
}
@@ -459,7 +461,8 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(13, 1)))
requiresAuthentication { api ->
val flow = api.graphQL.createAllProjectLabelsFlow(glTests2Coordinates)
val glTests2Project = glTests2Coordinates.projectPath
val flow = api.graphQL.createAllProjectLabelsFlow(glTests2Project)
val labels = flow.foldToList().sortedBy { it.title }
assertIterableEquals(listOf(testsGroupLabel1, testsGroupLabel2, glTests2Label1, glTests2Label2), labels)
@@ -471,7 +474,7 @@ class GitLabApiTest : GitLabApiTestCase() {
checkVersion(after(v(9, 0)))
requiresAuthentication { api ->
val users = api.rest.getProjectUsers(getProjectUsersURI(glTests2Coordinates)).body()
val users = api.rest.getProjectUsers(api.rest.getProjectUsersURI(glTests2ProjectId)).body()
assertTrue(users.map { it.username }.contains(rootUsername))
}
@@ -560,7 +563,8 @@ class GitLabApiTest : GitLabApiTestCase() {
assertNotNull(it, "test-png.png resource should exist")
val name = "test-image"
val filename = "$name.png"
val uploadResult = api.rest.markdownUploadFile(glTest1Coordinates, filename, "image/png", it).body()
val uploadResult = api.rest.markdownUploadFile(glTest1ProjectId, filename, "image/png", it).body()
assertNotNull(uploadResult)
val markdown = uploadResult.markdown
assertTrue(markdown.startsWith("![$name](/uploads/"),
@@ -68,20 +68,20 @@ abstract class GitLabApiTestCase {
val glTests2Coordinates = GitLabProjectCoordinates(server, glTests2Project)
val glTests2Label1 = GitLabLabelGQLDTO("label1", "#FFFFFF")
val glTests2Label2 = GitLabLabelGQLDTO("label2", "#FFFFFF")
val glTests2ProjectId = "6"
val glTest1Project = GitLabProjectPath("root", "gl-test-1")
val glTest1Coordinates = GitLabProjectCoordinates(server, glTest1Project)
val glTest1ProjectId = "2"
val glTest1Mr2Iid = "2"
val glTest1Mr2CommitShas = listOf("6a7d70b5df2d77ad792bca08c6fb14e29ae4ad04")
val glTest1Mr2CommitShortShas = listOf("6a7d70b5")
val glTest1Mr2ChangedFiles = listOf("a/important.txt")
val volatileProject = GitLabProjectPath("volatile", "volatile-project")
val volatileProjectCoordinates = GitLabProjectCoordinates(server, volatileProject)
val volatileProjectMr1Iid = "1"
val volatileProjectMr1Gid = "gid://gitlab/MergeRequest/5"
val volatileProjectMr2Iid = "2"
val volatileProjectId = "4"
}
companion object {
@@ -38,7 +38,6 @@ import kotlinx.coroutines.flow.transformLatest
import org.jetbrains.plugins.gitlab.GitLabServersManager
import org.jetbrains.plugins.gitlab.api.GitLabApi
import org.jetbrains.plugins.gitlab.api.GitLabApiManager
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
import org.jetbrains.plugins.gitlab.api.dto.WithGitLabNamespace
import org.jetbrains.plugins.gitlab.api.request.findProject
import org.jetbrains.plugins.gitlab.api.request.getMemberNamespacesForShare
@@ -164,7 +163,7 @@ internal class GitLabShareProjectDialogViewModel(
if (account == null || api == null || namespace == null) return@combine false
LOG.info("Checking for existing repositories at coordinates: ${account.server}/${namespace.fullPath}/$name")
api.graphQL.findProject(GitLabProjectCoordinates(account.server, GitLabProjectPath(namespace.fullPath, name))).body() != null
api.graphQL.findProject(GitLabProjectPath(namespace.fullPath, name)).body() != null
}.stateIn(cs, SharingStarted.Eagerly, false)
private val accountValidationError: StateFlow<@NlsContexts.DialogMessage String?> =