diff --git a/plugins/github/resources/META-INF/plugin.xml b/plugins/github/resources/META-INF/plugin.xml index a288610ff676..f19b5d26e38a 100644 --- a/plugins/github/resources/META-INF/plugin.xml +++ b/plugins/github/resources/META-INF/plugin.xml @@ -113,6 +113,8 @@ + + diff --git a/plugins/github/resources/graphql/query/getProtectionRules.graphql b/plugins/github/resources/graphql/query/getProtectionRules.graphql new file mode 100644 index 000000000000..f945710522fb --- /dev/null +++ b/plugins/github/resources/graphql/query/getProtectionRules.graphql @@ -0,0 +1,13 @@ +query($repoOwner: String!, $repoName: String!, $pageSize: Int = 100, $cursor: String) { + repository(owner: $repoOwner, name: $repoName) { + branchProtectionRules(first: $pageSize, after: $cursor) { + pageInfo { + ...pageInfo + } + nodes { + ...nodeInfo + pattern + } + } + } +} diff --git a/plugins/github/resources/messages/GithubBundle.properties b/plugins/github/resources/messages/GithubBundle.properties index 3b1c041aec26..7213b0883735 100644 --- a/plugins/github/resources/messages/GithubBundle.properties +++ b/plugins/github/resources/messages/GithubBundle.properties @@ -391,3 +391,5 @@ pull.request.review.not.supported.non.linear=By-commit review is not supported f issue.state.open=Open issue.state.closed=Closed +#protected branches +progress.text.loading.protected.branches=Loading GitHub branch protection rules diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLQueries.kt b/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLQueries.kt index 5e91bf174bbc..b6a2726efff2 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLQueries.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLQueries.kt @@ -4,6 +4,7 @@ package org.jetbrains.plugins.github.api object GHGQLQueries { const val findOrganizationTeams = "findOrganizationTeams" const val findRepositoryPermission = "findRepositoryPermission" + const val getProtectionRules = "getProtectionRules" const val commentBody = "commentBody" const val updateIssueComment = "updateIssueComment" const val deleteIssueComment = "deleteIssueComment" @@ -25,4 +26,4 @@ object GHGQLQueries { const val updateReviewComment = "updateReviewComment" const val resolveReviewThread = "resolveReviewThread" const val unresolveReviewThread = "unresolveReviewThread" -} \ No newline at end of file +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLRequests.kt b/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLRequests.kt index ddb23f9392b4..4892884f7505 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLRequests.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/api/GHGQLRequests.kt @@ -50,6 +50,20 @@ object GHGQLRequests { GHRepositoryPermission::class.java, "repository") } + + fun getProtectionRules(repository: GHRepositoryCoordinates, + pagination: GHGQLRequestPagination? = null): GQLQuery> { + return GQLQuery.TraversedParsed(repository.serverPath.toGraphQLUrl(), GHGQLQueries.getProtectionRules, + mapOf("repoOwner" to repository.repositoryPath.owner, + "repoName" to repository.repositoryPath.repository, + "pageSize" to pagination?.pageSize, + "cursor" to pagination?.afterCursor), + ProtectedRulesConnection::class.java, + "repository", "branchProtectionRules") + } + + private class ProtectedRulesConnection(pageInfo: GHGQLPageInfo, nodes: List) + : GHConnection(pageInfo, nodes) } object Comment { @@ -274,4 +288,4 @@ object GHGQLRequests { "unresolveReviewThread", "thread") } } -} \ No newline at end of file +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/data/GHBranchProtectionRule.kt b/plugins/github/src/org/jetbrains/plugins/github/api/data/GHBranchProtectionRule.kt new file mode 100644 index 000000000000..cc1b1023298d --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/api/data/GHBranchProtectionRule.kt @@ -0,0 +1,5 @@ +// 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 GHBranchProtectionRule(id: String, + val pattern: String): GHNode(id) diff --git a/plugins/github/src/org/jetbrains/plugins/github/extensions/GHProtectedBranchRulesLoader.kt b/plugins/github/src/org/jetbrains/plugins/github/extensions/GHProtectedBranchRulesLoader.kt new file mode 100644 index 000000000000..1a8eb33bd0c0 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/extensions/GHProtectedBranchRulesLoader.kt @@ -0,0 +1,83 @@ +// 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.extensions + +import com.intellij.concurrency.SensitiveProgressWrapper +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.project.Project +import com.intellij.util.PatternUtil +import git4idea.config.GitSharedSettings +import git4idea.fetch.GitFetchHandler +import git4idea.repo.GitRemote +import git4idea.repo.GitRepository +import org.jetbrains.plugins.github.api.GHGQLRequests +import org.jetbrains.plugins.github.api.GithubApiRequestExecutorManager +import org.jetbrains.plugins.github.api.util.SimpleGHGQLPagesLoader +import org.jetbrains.plugins.github.authentication.GithubAuthenticationManager +import org.jetbrains.plugins.github.i18n.GithubBundle +import org.jetbrains.plugins.github.util.GHProjectRepositoriesManager +import org.jetbrains.plugins.github.util.GithubSharedProjectSettings + +private val LOG = logger() + +internal class GHProtectedBranchRulesLoader : GitFetchHandler { + + override fun doAfterSuccessfulFetch(project: Project, fetches: Map>, indicator: ProgressIndicator) { + try { + loadProtectionRules(indicator, fetches, project) + } + catch (e: Exception) { + if (e is ProcessCanceledException) { + throw e + } + LOG.info("Error occurred while trying to load branch protection rules", e) + } + } + + private fun loadProtectionRules(indicator: ProgressIndicator, + fetches: Map>, + project: Project) { + val githubAuthenticationManager = GithubAuthenticationManager.getInstance() + if (!GitSharedSettings.getInstance(project).isSynchronizeBranchProtectionRules || !githubAuthenticationManager.hasAccounts()) { + runInEdt { + GithubSharedProjectSettings.getInstance(project).branchProtectionPatterns = arrayListOf() + } + return + } + + indicator.text = GithubBundle.message("progress.text.loading.protected.branches") + + val branchProtectionPatterns = mutableSetOf() + for ((repository, remotes) in fetches) { + indicator.checkCanceled() + + for (remote in remotes) { + indicator.checkCanceled() + + val account = + githubAuthenticationManager.getAccounts().find { it.server.matches(remote.firstUrl.orEmpty()) } ?: continue + + val requestExecutor = GithubApiRequestExecutorManager.getInstance().getExecutor(account) + + val githubRepositoryMapping = + project.service().findKnownRepositories(repository).find { it.gitRemote.remote == remote } + ?: continue + + val repositoryCoordinates = githubRepositoryMapping.repository + + SimpleGHGQLPagesLoader(requestExecutor, { GHGQLRequests.Repo.getProtectionRules(repositoryCoordinates) }) + .loadAll(SensitiveProgressWrapper((indicator))) + .forEach { rule -> branchProtectionPatterns.add(PatternUtil.convertToRegex(rule.pattern)) } + } + + } + + runInEdt { + GithubSharedProjectSettings.getInstance(project).branchProtectionPatterns = branchProtectionPatterns.toMutableList() + } + } + +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSharedProjectSettings.kt b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSharedProjectSettings.kt index a7f06a6934d0..a47970e8f4cb 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSharedProjectSettings.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSharedProjectSettings.kt @@ -3,6 +3,8 @@ package org.jetbrains.plugins.github.util import com.intellij.openapi.components.* import com.intellij.openapi.project.Project +import git4idea.config.GitProtectedBranchProvider +import java.util.* @State(name = "GithubSharedProjectSettings", storages = [Storage("vcs.xml")]) class GithubSharedProjectSettings : PersistentStateComponentWithModificationTracker { @@ -10,6 +12,7 @@ class GithubSharedProjectSettings : PersistentStateComponentWithModificationTrac class SettingsState : BaseState() { var pullRequestMergeForbidden by property(false) + var branchProtectionPatterns by list() } var pullRequestMergeForbidden: Boolean @@ -18,9 +21,15 @@ class GithubSharedProjectSettings : PersistentStateComponentWithModificationTrac state.pullRequestMergeForbidden = value } + var branchProtectionPatterns: MutableList + get() = Collections.unmodifiableList(state.branchProtectionPatterns) + set(value) { + state.branchProtectionPatterns = value + } + override fun getStateModificationCount() = state.modificationCount override fun getState() = state - override fun loadState(state: GithubSharedProjectSettings.SettingsState) { + override fun loadState(state: SettingsState) { this.state = state } @@ -29,3 +38,10 @@ class GithubSharedProjectSettings : PersistentStateComponentWithModificationTrac fun getInstance(project: Project) = project.service() } } + +internal class GithubProtectedBranchProvider : GitProtectedBranchProvider { + + override fun doGetProtectedBranchPatterns(project: Project): List { + return GithubSharedProjectSettings.getInstance(project).branchProtectionPatterns + } +}