mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:10:43 +07:00
[github] use ref update rules calculate PR mergeability and update min API version to 3.0
this allows us to avoid loading all user teams (costly) and avoid loading branch protection rules (fails for non-admins) #Fixed IDEA-336300 GitOrigin-RevId: 79416686cf100b391a446ba26e218f457387eb2b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a83debdab2
commit
cf4dbb64ff
@@ -24,6 +24,12 @@ fragment pullRequestInfo on PullRequest {
|
||||
isFork
|
||||
}
|
||||
|
||||
baseRef {
|
||||
refUpdateRule {
|
||||
...refUpdateRule
|
||||
}
|
||||
}
|
||||
|
||||
headRefName
|
||||
headRefOid
|
||||
headRepository {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
fragment refUpdateRule on RefUpdateRule {
|
||||
allowsDeletions
|
||||
allowsForcePushes
|
||||
pattern
|
||||
requiredApprovingReviewCount
|
||||
requiredStatusCheckContexts
|
||||
requiresLinearHistory
|
||||
requiresSignatures
|
||||
viewerCanPush
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package org.jetbrains.plugins.github.api
|
||||
|
||||
object GHEServerVersionChecker {
|
||||
|
||||
private const val REQUIRED_VERSION_MAJOR = 2
|
||||
private const val REQUIRED_VERSION_MINOR = 21
|
||||
private const val REQUIRED_VERSION_MAJOR = 3
|
||||
private const val REQUIRED_VERSION_MINOR = 0
|
||||
|
||||
const val ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.intellij.util.ThrowableConvertor
|
||||
import org.jetbrains.plugins.github.api.GithubApiRequest.*
|
||||
import org.jetbrains.plugins.github.api.data.*
|
||||
import org.jetbrains.plugins.github.api.data.request.*
|
||||
import org.jetbrains.plugins.github.api.util.GHSchemaPreview
|
||||
import org.jetbrains.plugins.github.api.util.GithubApiPagesLoader
|
||||
import org.jetbrains.plugins.github.api.util.GithubApiSearchQueryBuilder
|
||||
import org.jetbrains.plugins.github.api.util.GithubApiUrlQueryBuilder
|
||||
@@ -142,10 +141,6 @@ object GithubApiRequests {
|
||||
|
||||
@JvmStatic
|
||||
fun get(url: String) = Get.jsonPage<GithubBranch>(url).withOperationName("get branches")
|
||||
|
||||
@JvmStatic
|
||||
fun getProtection(repository: GHRepositoryCoordinates, branchName: String): GithubApiRequest<GHBranchProtectionRules> =
|
||||
Get.json(getUrl(repository, urlSuffix, "/$branchName", "/protection"), GHSchemaPreview.BRANCH_PROTECTION.mimeType)
|
||||
}
|
||||
|
||||
object Commits : Entity("/commits") {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.github.api.data
|
||||
|
||||
class GHBranchProtectionRules(val requiredStatusChecks: RequiredStatusChecks?,
|
||||
val enforceAdmins: EnforceAdmins?,
|
||||
val requiredPullRequestReviews: RequiredPullRequestReviews?,
|
||||
val restrictions: Restrictions?) {
|
||||
|
||||
class RequiredStatusChecks(val strict: Boolean, val contexts: List<String>)
|
||||
|
||||
class EnforceAdmins(val enabled: Boolean)
|
||||
|
||||
class RequiredPullRequestReviews(val requiredApprovingReviewCount: Int)
|
||||
|
||||
class Restrictions(val users: List<UserLogin>?, val teams: List<TeamSlug>?)
|
||||
|
||||
class UserLogin(val login: String)
|
||||
|
||||
class TeamSlug(val slug: String)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.github.api.data
|
||||
|
||||
import com.intellij.collaboration.api.dto.GraphQLFragment
|
||||
|
||||
@GraphQLFragment("graphql/fragment/refUpdateRule.graphql")
|
||||
data class GHRefUpdateRule(
|
||||
//Can this branch be deleted.
|
||||
val allowsDeletions: Boolean,
|
||||
//Are force pushes allowed on this branch.
|
||||
val allowsForcePushes: Boolean,
|
||||
//Identifies the protection rule pattern.
|
||||
val pattern: String,
|
||||
//Number of approving reviews required to update matching branches.
|
||||
val requiredApprovingReviewCount: Int?,
|
||||
//List of required status check contexts that must pass for commits to be accepted to matching branches.
|
||||
val requiredStatusCheckContexts: List<String?>,
|
||||
//Are merge commits prohibited from being pushed to this branch.
|
||||
val requiresLinearHistory: Boolean,
|
||||
//Are commits required to be signed.
|
||||
val requiresSignatures: Boolean,
|
||||
//Can the viewer push to the branch
|
||||
val viewerCanPush: Boolean
|
||||
)
|
||||
@@ -8,6 +8,7 @@ import com.intellij.collaboration.api.dto.GraphQLNodesDTO
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import org.jetbrains.plugins.github.api.data.GHActor
|
||||
import org.jetbrains.plugins.github.api.data.GHLabel
|
||||
import org.jetbrains.plugins.github.api.data.GHRefUpdateRule
|
||||
import org.jetbrains.plugins.github.api.data.GHUser
|
||||
import java.util.*
|
||||
|
||||
@@ -33,6 +34,7 @@ class GHPullRequest(id: String,
|
||||
val baseRefName: String,
|
||||
val baseRefOid: String,
|
||||
val baseRepository: Repository?,
|
||||
baseRef: BaseRef?,
|
||||
val headRefName: String,
|
||||
val headRefOid: String,
|
||||
val headRepository: HeadRepository?)
|
||||
@@ -42,6 +44,9 @@ class GHPullRequest(id: String,
|
||||
@JsonIgnore
|
||||
val reviews: List<GHPullRequestReview> = reviews.nodes
|
||||
|
||||
@JsonIgnore
|
||||
val baseRefUpdateRule: GHRefUpdateRule? = baseRef?.refUpdateRule
|
||||
|
||||
open class Repository(val owner: Owner, val isFork: Boolean)
|
||||
|
||||
class HeadRepository(owner: Owner, isFork: Boolean,
|
||||
@@ -50,6 +55,8 @@ class GHPullRequest(id: String,
|
||||
val sshUrl: @NlsSafe String)
|
||||
: Repository(owner, isFork)
|
||||
|
||||
data class BaseRef(val refUpdateRule: GHRefUpdateRule?)
|
||||
|
||||
class Owner(val login: String)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.jetbrains.plugins.github.api.GHGQLRequests
|
||||
import org.jetbrains.plugins.github.api.GHRepositoryCoordinates
|
||||
import org.jetbrains.plugins.github.api.GithubApiRequestExecutor
|
||||
import org.jetbrains.plugins.github.api.GithubApiRequests
|
||||
import org.jetbrains.plugins.github.api.data.GHRepositoryOwnerName
|
||||
import org.jetbrains.plugins.github.api.data.GHUser
|
||||
import org.jetbrains.plugins.github.api.util.SimpleGHGQLPagesLoader
|
||||
import org.jetbrains.plugins.github.authentication.accounts.GithubAccount
|
||||
@@ -102,25 +101,13 @@ internal class GHPRDataContextRepository(private val project: Project, parentCs:
|
||||
accountDetails.name)
|
||||
|
||||
|
||||
val repoOwner = repositoryInfo.owner
|
||||
val currentUserTeams = if (repoOwner is GHRepositoryOwnerName.Organization) {
|
||||
suspendingApiCall { indicator ->
|
||||
SimpleGHGQLPagesLoader(requestExecutor, {
|
||||
GHGQLRequests.Organization.Team.findByUserLogins(account.server, repoOwner.login, listOf(currentUser.login), it)
|
||||
}).loadAll(indicator)
|
||||
}
|
||||
}
|
||||
else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
// repository might have been renamed/moved
|
||||
val apiRepositoryPath = repositoryInfo.path
|
||||
val apiRepositoryCoordinates = GHRepositoryCoordinates(account.server, apiRepositoryPath)
|
||||
|
||||
val securityService = GHPRSecurityServiceImpl(GithubSharedProjectSettings.getInstance(project),
|
||||
ghostUserDetails,
|
||||
account, currentUser, currentUserTeams,
|
||||
account, currentUser,
|
||||
repositoryInfo)
|
||||
val detailsService = GHPRDetailsServiceImpl(ProgressManager.getInstance(), requestExecutor, apiRepositoryCoordinates)
|
||||
val stateService = GHPRStateServiceImpl(ProgressManager.getInstance(), project, securityService,
|
||||
@@ -150,7 +137,7 @@ internal class GHPRDataContextRepository(private val project: Project, parentCs:
|
||||
|
||||
val repoDataService = GHPRRepositoryDataServiceImpl(ProgressManager.getInstance(), requestExecutor,
|
||||
remoteCoordinates, apiRepositoryCoordinates,
|
||||
repoOwner,
|
||||
repositoryInfo.owner,
|
||||
repositoryInfo.id, repositoryInfo.defaultBranch, repositoryInfo.isFork)
|
||||
|
||||
val iconsScope = contextScope.childScope(Dispatchers.Main)
|
||||
|
||||
@@ -3,16 +3,13 @@ package org.jetbrains.plugins.github.pullrequest.data
|
||||
|
||||
import com.intellij.collaboration.ui.codereview.details.data.CodeReviewCIJob
|
||||
import com.intellij.collaboration.ui.codereview.details.data.CodeReviewCIJobState
|
||||
import com.intellij.util.containers.nullize
|
||||
import org.jetbrains.plugins.github.api.data.GHBranchProtectionRules
|
||||
import org.jetbrains.plugins.github.api.data.GHCommitCheckSuiteConclusion
|
||||
import org.jetbrains.plugins.github.api.data.GHCommitStatusContextState
|
||||
import org.jetbrains.plugins.github.api.data.GHRepositoryPermissionLevel
|
||||
import org.jetbrains.plugins.github.api.data.GHRefUpdateRule
|
||||
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequestMergeStateStatus
|
||||
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequestMergeabilityData
|
||||
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequestMergeableState
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRMergeabilityState.ChecksState
|
||||
import org.jetbrains.plugins.github.pullrequest.data.service.GHPRSecurityService
|
||||
|
||||
class GHPRMergeabilityStateBuilder(private val headRefOid: String, private val prHtmlUrl: String,
|
||||
private val mergeabilityData: GHPullRequestMergeabilityData) {
|
||||
@@ -22,18 +19,12 @@ class GHPRMergeabilityStateBuilder(private val headRefOid: String, private val p
|
||||
private var isRestricted = false
|
||||
private var requiredApprovingReviewsCount = 0
|
||||
|
||||
fun withRestrictions(securityService: GHPRSecurityService, baseBranchProtectionRules: GHBranchProtectionRules) {
|
||||
canOverrideAsAdmin = baseBranchProtectionRules.enforceAdmins?.enabled == false &&
|
||||
securityService.currentUserHasPermissionLevel(GHRepositoryPermissionLevel.ADMIN)
|
||||
requiredContexts = baseBranchProtectionRules.requiredStatusChecks?.contexts.orEmpty()
|
||||
|
||||
val restrictions = baseBranchProtectionRules.restrictions
|
||||
val allowedLogins = restrictions?.users?.map { it.login }.nullize()
|
||||
val allowedTeams = restrictions?.teams?.map { it.slug }.nullize()
|
||||
isRestricted = (allowedLogins != null && !allowedLogins.contains(securityService.currentUser.login)) ||
|
||||
(allowedTeams != null && !securityService.isUserInAnyTeam(allowedTeams))
|
||||
|
||||
requiredApprovingReviewsCount = baseBranchProtectionRules.requiredPullRequestReviews?.requiredApprovingReviewCount ?: 0
|
||||
fun withRestrictions(currentUserIsAdmin: Boolean, refUpdateRule: GHRefUpdateRule) {
|
||||
// TODO: load via PullRequest.viewerCanMergeAsAdmin when we update the min version
|
||||
canOverrideAsAdmin = /*baseBranchProtectionRules.enforceAdmins?.enabled == false &&*/currentUserIsAdmin
|
||||
requiredContexts = refUpdateRule.requiredStatusCheckContexts.filterNotNull()
|
||||
isRestricted = !refUpdateRule.viewerCanPush
|
||||
requiredApprovingReviewsCount = refUpdateRule.requiredApprovingReviewCount ?: 0
|
||||
}
|
||||
|
||||
fun build(): GHPRMergeabilityState {
|
||||
|
||||
@@ -35,7 +35,6 @@ class GHPRStateDataProviderImpl(private val stateService: GHPRStateService,
|
||||
val details = detailsData.loadedDetails ?: return@addDetailsLoadedListener
|
||||
|
||||
if (lastKnownBaseBranch != null && lastKnownBaseBranch != details.baseRefName) {
|
||||
baseBranchProtectionRulesRequestValue.drop()
|
||||
reloadMergeabilityState()
|
||||
}
|
||||
lastKnownBaseBranch = details.baseRefName
|
||||
@@ -50,18 +49,9 @@ class GHPRStateDataProviderImpl(private val stateService: GHPRStateService,
|
||||
}
|
||||
}
|
||||
|
||||
private val baseBranchProtectionRulesRequestValue = LazyCancellableBackgroundProcessValue.create { indicator ->
|
||||
detailsData.loadDetails().thenCompose {
|
||||
stateService.loadBranchProtectionRules(indicator, pullRequestId, it.baseRefName)
|
||||
}
|
||||
}
|
||||
private val mergeabilityStateRequestValue = LazyCancellableBackgroundProcessValue.create { indicator ->
|
||||
val baseBranchProtectionRulesRequest = baseBranchProtectionRulesRequestValue.value
|
||||
detailsData.loadDetails().thenCompose { details ->
|
||||
|
||||
baseBranchProtectionRulesRequest.thenCompose {
|
||||
stateService.loadMergeabilityState(indicator, pullRequestId, details.headRefOid, details.url, it)
|
||||
}
|
||||
stateService.loadMergeabilityState(indicator, pullRequestId, details.headRefOid, details.url, details.baseRefUpdateRule)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +79,6 @@ class GHPRStateDataProviderImpl(private val stateService: GHPRStateService,
|
||||
}
|
||||
|
||||
override fun reloadMergeabilityState() {
|
||||
if (baseBranchProtectionRulesRequestValue.lastLoadedValue == null)
|
||||
baseBranchProtectionRulesRequestValue.drop()
|
||||
mergeabilityStateRequestValue.drop()
|
||||
}
|
||||
|
||||
@@ -122,6 +110,5 @@ class GHPRStateDataProviderImpl(private val stateService: GHPRStateService,
|
||||
|
||||
override fun dispose() {
|
||||
mergeabilityStateRequestValue.drop()
|
||||
baseBranchProtectionRulesRequestValue.drop()
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,4 @@ interface GHPRSecurityService {
|
||||
fun isSquashMergeAllowed(): Boolean
|
||||
|
||||
fun isMergeForbiddenForProject(): Boolean
|
||||
fun isUserInAnyTeam(slugs: List<String>): Boolean
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import org.jetbrains.plugins.github.api.data.GHRepository
|
||||
import org.jetbrains.plugins.github.api.data.GHRepositoryPermissionLevel
|
||||
import org.jetbrains.plugins.github.api.data.GHUser
|
||||
import org.jetbrains.plugins.github.api.data.GithubUser
|
||||
import org.jetbrains.plugins.github.api.data.pullrequest.GHTeam
|
||||
import org.jetbrains.plugins.github.authentication.accounts.GithubAccount
|
||||
import org.jetbrains.plugins.github.util.GithubSharedProjectSettings
|
||||
|
||||
@@ -13,15 +12,12 @@ class GHPRSecurityServiceImpl(private val sharedProjectSettings: GithubSharedPro
|
||||
override val ghostUser: GHUser,
|
||||
override val account: GithubAccount,
|
||||
override val currentUser: GHUser,
|
||||
private val currentUserTeams: List<GHTeam>,
|
||||
private val repo: GHRepository) : GHPRSecurityService {
|
||||
override fun isCurrentUser(user: GithubUser) = user.nodeId == currentUser.id
|
||||
|
||||
override fun currentUserHasPermissionLevel(level: GHRepositoryPermissionLevel) =
|
||||
(repo.viewerPermission?.ordinal ?: -1) >= level.ordinal
|
||||
|
||||
override fun isUserInAnyTeam(slugs: List<String>) = currentUserTeams.any { slugs.contains(it.slug) }
|
||||
|
||||
override fun isMergeAllowed() = repo.mergeCommitAllowed
|
||||
override fun isRebaseMergeAllowed() = repo.rebaseMergeAllowed
|
||||
override fun isSquashMergeAllowed() = repo.squashMergeAllowed
|
||||
|
||||
@@ -3,23 +3,19 @@ package org.jetbrains.plugins.github.pullrequest.data.service
|
||||
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import org.jetbrains.annotations.CalledInAny
|
||||
import org.jetbrains.plugins.github.api.data.GHBranchProtectionRules
|
||||
import org.jetbrains.plugins.github.api.data.GHRefUpdateRule
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRIdentifier
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRMergeabilityState
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
interface GHPRStateService {
|
||||
|
||||
@CalledInAny
|
||||
fun loadBranchProtectionRules(progressIndicator: ProgressIndicator, pullRequestId: GHPRIdentifier, baseBranch: String)
|
||||
: CompletableFuture<GHBranchProtectionRules?>
|
||||
|
||||
@CalledInAny
|
||||
fun loadMergeabilityState(progressIndicator: ProgressIndicator,
|
||||
pullRequestId: GHPRIdentifier,
|
||||
headRefOid: String,
|
||||
prHtmlUrl: String,
|
||||
baseBranchProtectionRules: GHBranchProtectionRules?): CompletableFuture<GHPRMergeabilityState>
|
||||
baseRefUpdateRule: GHRefUpdateRule?): CompletableFuture<GHPRMergeabilityState>
|
||||
|
||||
|
||||
@CalledInAny
|
||||
|
||||
@@ -3,17 +3,17 @@ package org.jetbrains.plugins.github.pullrequest.data.service
|
||||
|
||||
import com.intellij.collaboration.async.CompletableFutureUtil.submitIOTask
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.plugins.github.api.*
|
||||
import org.jetbrains.plugins.github.api.data.GHBranchProtectionRules
|
||||
import org.jetbrains.plugins.github.api.data.GHRefUpdateRule
|
||||
import org.jetbrains.plugins.github.api.data.GHRepositoryPermissionLevel
|
||||
import org.jetbrains.plugins.github.api.data.GithubIssueState
|
||||
import org.jetbrains.plugins.github.api.data.GithubPullRequestMergeMethod
|
||||
import org.jetbrains.plugins.github.pullrequest.GHPRStatisticsCollector
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRIdentifier
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRMergeabilityState
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GHPRMergeabilityStateBuilder
|
||||
import org.jetbrains.plugins.github.pullrequest.data.service.GHServiceUtil.logError
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@@ -28,35 +28,17 @@ class GHPRStateServiceImpl internal constructor(private val progressManager: Pro
|
||||
|
||||
private val repository = GHRepositoryCoordinates(serverPath, repoPath)
|
||||
|
||||
override fun loadBranchProtectionRules(progressIndicator: ProgressIndicator,
|
||||
pullRequestId: GHPRIdentifier,
|
||||
baseBranch: String): CompletableFuture<GHBranchProtectionRules?> {
|
||||
if (!securityService.currentUserHasPermissionLevel(GHRepositoryPermissionLevel.WRITE)) return CompletableFuture.completedFuture(null)
|
||||
|
||||
return progressManager.submitIOTask(progressIndicator) {
|
||||
try {
|
||||
requestExecutor.execute(it, GithubApiRequests.Repos.Branches.getProtection(repository, baseBranch))
|
||||
}
|
||||
catch (e: Exception) {
|
||||
// assume there are no restrictions
|
||||
if (e !is ProcessCanceledException) LOG.info("Error occurred while loading branch protection rules for $baseBranch", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadMergeabilityState(progressIndicator: ProgressIndicator,
|
||||
pullRequestId: GHPRIdentifier,
|
||||
headRefOid: String,
|
||||
prHtmlUrl: String,
|
||||
baseBranchProtectionRules: GHBranchProtectionRules?) =
|
||||
baseRefUpdateRule: GHRefUpdateRule?): CompletableFuture<GHPRMergeabilityState> =
|
||||
progressManager.submitIOTask(progressIndicator) {
|
||||
val mergeabilityData = requestExecutor.execute(it, GHGQLRequests.PullRequest.mergeabilityData(repository, pullRequestId.number))
|
||||
?: error("Could not find pull request $pullRequestId.number")
|
||||
val builder = GHPRMergeabilityStateBuilder(headRefOid, prHtmlUrl,
|
||||
mergeabilityData)
|
||||
if (baseBranchProtectionRules != null) {
|
||||
builder.withRestrictions(securityService, baseBranchProtectionRules)
|
||||
val builder = GHPRMergeabilityStateBuilder(headRefOid, prHtmlUrl, mergeabilityData)
|
||||
if (baseRefUpdateRule != null) {
|
||||
builder.withRestrictions(securityService.currentUserHasPermissionLevel(GHRepositoryPermissionLevel.ADMIN), baseRefUpdateRule)
|
||||
}
|
||||
builder.build()
|
||||
}.logError(LOG, "Error occurred while loading mergeability state data for PR ${pullRequestId.number}")
|
||||
|
||||
Reference in New Issue
Block a user