gitbucket.core.controller.PullRequestsController.scala Maven / Gradle / Ivy
The newest version!
package gitbucket.core.controller
import gitbucket.core.model.activity.DeleteBranchInfo
import gitbucket.core.pulls.html
import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService
import gitbucket.core.service.IssuesService._
import gitbucket.core.service.PullRequestService._
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util._
import org.scalatra.forms._
import org.eclipse.jgit.api.Git
import org.scalatra.BadRequest
import scala.util.Using
class PullRequestsController
extends PullRequestsControllerBase
with RepositoryService
with AccountService
with IssuesService
with PullRequestService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with CommitStatusService
with MergeService
with ProtectedBranchService
with PrioritiesService
with RequestCache
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService
with AccountService
with IssuesService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with PullRequestService
with WebHookPullRequestService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with CommitStatusService
with MergeService
with ProtectedBranchService
with PrioritiesService =>
val pullRequestForm = mapping(
"title" -> trim(label("Title", text(required))),
"content" -> trim(label("Content", optional(text()))),
"targetUserName" -> trim(text(required, maxlength(100))),
"targetBranch" -> trim(text(required, maxlength(100))),
"requestUserName" -> trim(text(required, maxlength(100))),
"requestRepositoryName" -> trim(text(required, maxlength(100))),
"requestBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40))),
"isDraft" -> trim(boolean(required)),
"assigneeUserNames" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
)(PullRequestForm.apply)
val mergeForm = mapping(
"message" -> trim(label("Message", text(required))),
"strategy" -> trim(label("Strategy", text(required))),
"isDraft" -> trim(boolean(required))
)(MergeForm.apply)
case class PullRequestForm(
title: String,
content: Option[String],
targetUserName: String,
targetBranch: String,
requestUserName: String,
requestRepositoryName: String,
requestBranch: String,
commitIdFrom: String,
commitIdTo: String,
isDraft: Boolean,
assigneeUserNames: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
)
case class MergeForm(message: String, strategy: String, isDraft: Boolean)
get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q")
Option(q) match {
case Some(filter) if filter.contains("is:issue") =>
redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}")
case Some(filter) =>
val condition = IssueSearchCondition(filter)
if (condition.isEmpty) {
// Redirect to keyword search
redirect(s"/${repository.owner}/${repository.name}/search?q=${StringUtil.urlEncode(q)}&type=pulls")
} else {
searchPullRequests(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
}
case None =>
searchPullRequests(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
}
})
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo,
context.settings
)
html.conversation(
issue,
pullreq,
commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
getIssueLabels(repository.owner, repository.name, issueId),
getIssueAssignees(repository.owner, repository.name, issueId),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests),
isEditable(repository),
isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
flash.iterator.map(f => f._1 -> f._2.toString).toMap
)
}
} getOrElse NotFound()
})
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo,
context.settings
)
val commitsWithStatus = commits.map { day =>
day.map { commit =>
(commit, getCommitStatusWithSummary(repository.owner, repository.name, commit.id))
}
}
html.commits(
issue,
pullreq,
commitsWithStatus,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
isManageable(repository),
repository
)
}
} getOrElse NotFound()
})
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo,
context.settings
)
html.files(
issue,
pullreq,
diffs,
commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
isManageable(repository),
repository
)
}
} getOrElse NotFound()
})
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
}
val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
val mergeStatus = PullRequestService.MergeStatus(
conflictMessage = conflictMessage,
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
branchProtection = branchProtection,
branchIsOutOfDate =
!JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch).contains(pullreq.commitIdFrom),
needStatusCheck = context.loginAccount.forall { u =>
branchProtection.needStatusCheck(u.userName)
},
hasUpdatePermission = hasDeveloperRole(
pullreq.requestUserName,
pullreq.requestRepositoryName,
context.loginAccount
) &&
context.loginAccount.exists { u =>
!getProtectedBranchInfo(
pullreq.requestUserName,
pullreq.requestRepositoryName,
pullreq.requestBranch
).needStatusCheck(u.userName)
},
hasMergePermission = hasMergePermission,
commitIdTo = pullreq.commitIdTo
)
html.mergeguide(
mergeStatus,
issue,
pullreq,
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get
)
}
} getOrElse NotFound()
})
get("/:owner/:repository/pull/:id/delete_branch")(readableUsersOnly { baseRepository =>
(for {
issueId <- params("id").toIntOpt
loginAccount <- context.loginAccount
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
owner = pullreq.requestUserName
name = pullreq.requestRepositoryName
if hasDeveloperRole(owner, name, context.loginAccount)
} yield {
val repository = getRepository(owner, name).get
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
if (branchProtection.enabled) {
flash.update("error", s"branch ${pullreq.requestBranch} is protected.")
} else {
if (repository.repository.defaultBranch != pullreq.requestBranch) {
val userName = context.loginAccount.get.userName
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call()
val deleteBranchInfo = DeleteBranchInfo(repository.owner, repository.name, userName, pullreq.requestBranch)
recordActivity(deleteBranchInfo)
}
createComment(
baseRepository.owner,
baseRepository.name,
userName,
issueId,
pullreq.requestBranch,
"delete_branch"
)
} else {
flash.update("error", s"""Can't delete the default branch "${pullreq.requestBranch}".""")
}
}
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
}) getOrElse NotFound()
})
post("/:owner/:repository/pull/:id/update_branch")(readableUsersOnly { baseRepository =>
(for {
issueId <- params("id").toIntOpt
loginAccount <- context.loginAccount
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
repository <- getRepository(pullreq.requestUserName, pullreq.requestRepositoryName)
remoteRepository <- getRepository(pullreq.userName, pullreq.repositoryName)
owner = pullreq.requestUserName
name = pullreq.requestRepositoryName
if hasDeveloperRole(owner, name, context.loginAccount)
} yield {
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
if (branchProtection.needStatusCheck(loginAccount.userName)) {
flash.update("error", s"branch ${pullreq.requestBranch} is protected need status check.")
} else {
LockUtil.lock(s"${owner}/${name}") {
val alias =
if (
pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName
) {
pullreq.branch
} else {
s"${pullreq.userName}:${pullreq.branch}"
}
val existIds = Using
.resource(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
JGitUtil.getAllCommitIds(git)
}
.toSet
pullRemote(
repository,
pullreq.requestBranch,
remoteRepository,
pullreq.branch,
loginAccount,
s"Merge branch '${alias}' into ${pullreq.requestBranch}",
Some(pullreq),
context.settings
) match {
case None => // conflict
flash.update("error", s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}.")
case Some(oldId) =>
// update pull request
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize", context.settings)
flash.update("info", s"Merge branch '${alias}' into ${pullreq.requestBranch}")
}
}
}
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
}) getOrElse NotFound()
})
post("/:owner/:repository/pull/:id/update_draft")(readableUsersOnly { baseRepository =>
(for {
issueId <- params("id").toIntOpt
(_, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
owner = pullreq.requestUserName
name = pullreq.requestRepositoryName
if hasDeveloperRole(owner, name, context.loginAccount)
} yield {
updateDraftToPullRequest(baseRepository.owner, baseRepository.name, issueId)
}) getOrElse NotFound()
})
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
params("id").toIntOpt.flatMap { issueId =>
mergePullRequest(
repository,
issueId,
loginAccount,
form.message,
form.strategy,
form.isDraft,
context.settings
) match {
case Right(objectId) => redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
case Left(message) => Some(BadRequest(message))
}
} getOrElse NotFound()
}
})
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
val headBranch = params.get("head")
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) =>
getRepository(originUserName, originRepositoryName).map { originRepository =>
Using.resources(
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
) { (oldGit, newGit) =>
val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2)
val oldBranch = originRepository.branchList
.find(_ == newBranch)
.getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2)
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}"
)
}
} getOrElse NotFound()
case _ =>
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}"
)
} getOrElse {
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
}
}
}
})
private def getOriginRepositoryName(
originOwner: String,
forkedOwner: String,
forkedRepository: RepositoryInfo
): Option[String] = {
if (originOwner == forkedOwner) {
// Self repository
Some(forkedRepository.name)
} else if (forkedRepository.repository.originUserName.isEmpty) {
// when ForkedRepository is the original repository
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
} else if (Some(originOwner) == forkedRepository.repository.originUserName) {
// Original repository
forkedRepository.repository.originRepositoryName
} else {
// Sibling repository
getUserRepositories(originOwner)
.find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}
.map(_.repository.repositoryName)
}
}
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (
originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
(oldId, newId) match {
case (Some(oldId), Some(newId)) =>
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner,
originRepository.name,
oldId.getName,
forkedRepository.owner,
forkedRepository.name,
newId.getName,
context.settings
)
val title = if (commits.flatten.length == 1) {
commits.flatten.head.shortMessage
} else {
val text = forkedId.replaceAll("[\\-_]", " ")
text.substring(0, 1).toUpperCase + text.substring(1)
}
html.compare(
title,
commits,
diffs,
((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(userName), Some(repositoryName)) =>
getRepository(userName, repositoryName) match {
case Some(x) => x.repository :: getForkedRepositories(userName, repositoryName)
case None => getForkedRepositories(userName, repositoryName)
}
case _ =>
forkedRepository.repository :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
}).map { repository =>
(repository.userName, repository.repositoryName, repository.defaultBranch)
},
commits.flatten
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
.toList,
originId,
forkedId,
oldId.getName,
newId.getName,
getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"),
forkedRepository,
originRepository,
forkedRepository,
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
getAssignableUserNames(originRepository.owner, originRepository.name),
getMilestones(originRepository.owner, originRepository.name),
getPriorities(originRepository.owner, originRepository.name),
getDefaultPriority(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name),
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests)
)
case (oldId, newId) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
)
}
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/diff/:id")(referrersOnly { repository =>
(for {
commitId <- params.get("id")
path <- params.get("path")
diff <- getSingleDiff(repository.owner, repository.name, commitId, path)
} yield {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"oldContent" -> diff.oldContent,
"newContent" -> diff.newContent
)
)
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/diff/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for {
path <- params.get("path")
originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository)
originRepository <- getRepository(originOwner, originRepositoryName)
(oldId, newId) = getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
oldId <- oldId
newId <- newId
diff <- getSingleDiff(
originRepository.owner,
originRepository.name,
oldId.getName,
forkedRepository.owner,
forkedRepository.name,
newId.getName,
path
)
} yield {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"oldContent" -> diff.oldContent,
"newContent" -> diff.newContent
)
)
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (
originRepositoryName <-
if (originOwner == forkedOwner) {
Some(forkedRepository.name)
} else {
forkedRepository.repository.originRepositoryName.orElse {
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
}
};
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
Using.resources(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
) { case (oldGit, newGit) =>
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}") {
checkConflict(
originRepository.owner,
originRepository.name,
originBranch,
forkedRepository.owner,
forkedRepository.name,
forkedBranch
)
}
html.mergecheck(conflict.isDefined)
}
}) getOrElse NotFound()
})
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
val manageable = isManageable(repository)
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginAccount.userName,
title = form.title,
content = form.content,
milestoneId = if (manageable) form.milestoneId else None,
priorityId = if (manageable) form.priorityId else None,
isPullRequest = true
)
createPullRequest(
originRepository = repository,
issueId = issueId,
originBranch = form.targetBranch,
requestUserName = form.requestUserName,
requestRepositoryName = form.requestRepositoryName,
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo,
isDraft = form.isDraft,
loginAccount = loginAccount,
settings = context.settings
)
if (manageable) {
// insert assignees
form.assigneeUserNames.foreach { value =>
value.split(",").foreach { userName =>
registerIssueAssignee(repository.owner, repository.name, issueId, userName)
}
}
// insert labels
form.labelNames.foreach { value =>
val labels = getLabels(repository.owner, repository.name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
}
}
}
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
}
})
ajaxGet("/:owner/:repository/pulls/proposals")(readableUsersOnly { repository =>
val thresholdTime = System.currentTimeMillis() - (1000 * 60 * 60)
val mailAddresses =
context.loginAccount.map(x => Seq(x.mailAddress) ++ getAccountExtraMailAddresses(x.userName)).getOrElse(Nil)
val branches =
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JGitUtil
.getBranches(
git = git,
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.filter { x =>
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
x.commitTime.getTime > thresholdTime &&
mailAddresses.contains(x.committerEmailAddress)
}
.sortBy { br =>
(br.mergeInfo.isEmpty, br.commitTime)
}
.map(_.name)
.reverse
}
val targetRepository = (for {
parentUserName <- repository.repository.parentUserName
parentRepoName <- repository.repository.parentRepositoryName
parentRepository <- getRepository(parentUserName, parentRepoName)
} yield {
parentRepository
}).getOrElse {
repository
}
val proposedBranches = branches.filter { branch =>
getPullRequestsByRequest(repository.owner, repository.name, branch, None).isEmpty
}
html.proposals(proposedBranches, targetRepository, repository)
})
private def searchPullRequests(
repository: RepositoryService.RepositoryInfo,
condition: IssueSearchCondition,
page: Int
) = {
// search issues
val issues = searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
repository.owner -> repository.name
)
// commit status
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(repository.owner, repository.name, commitId)
}
}
gitbucket.core.issues.html.list(
"pulls",
issues.zip(status),
page,
getAssignableUserNames(repository.owner, repository.name),
getMilestones(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
condition,
repository,
isEditable(repository),
isManageable(repository)
)
}
/**
* Tests whether an logged-in user can manage pull requests.
*/
private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
}
/**
* Tests whether an logged-in user can post pull requests.
*/
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
repository.repository.options.issuesOption match {
case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
case "DISABLE" => false
}
}
}