Merge pull request #1140 from gitbucket/separate_api_controller

Separate API controller to improve routing performance
This commit is contained in:
Naoki Takezoe
2016-03-10 01:34:56 +09:00
18 changed files with 631 additions and 556 deletions

View File

@@ -27,9 +27,10 @@ class ScalatraBootstrap extends LifeCycle {
}
context.mount(new IndexController, "/")
context.mount(new ApiController, "/api/v3")
context.mount(new FileUploadController, "/upload")
context.mount(new SystemSettingsController, "/admin")
context.mount(new DashboardController, "/*")
context.mount(new SystemSettingsController, "/*")
context.mount(new AccountController, "/*")
context.mount(new RepositoryViewerController, "/*")
context.mount(new WikiController, "/*")

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller
import gitbucket.core.account.html
import gitbucket.core.api._
import gitbucket.core.helper
import gitbucket.core.model.GroupMember
import gitbucket.core.service._
@@ -14,22 +13,19 @@ import gitbucket.core.util._
import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
import org.scalatra.i18n.Messages
class AccountController extends AccountControllerBase
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService
with AccessTokenService with WebHookService with RepositoryCreationService
trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService =>
with AccessTokenService with WebHookService with RepositoryCreationService =>
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
url: Option[String], fileId: Option[String])
@@ -156,25 +152,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
}
/**
* https://developer.github.com/v3/users/#get-a-single-user
*/
get("/api/v3/users/:userName") {
getAccountByUserName(params("userName")).map { account =>
JsonFormat(ApiUser(account))
} getOrElse NotFound
}
/**
* https://developer.github.com/v3/users/#get-the-authenticated-user
*/
get("/api/v3/user") {
context.loginAccount.map { account =>
JsonFormat(ApiUser(account))
} getOrElse Unauthorized
}
get("/:userName/_edit")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map { x =>
@@ -367,7 +344,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/new", newRepositoryForm)(usersOnly { form =>
LockUtil.lock(s"${form.owner}/${form.name}"){
if(getRepository(form.owner, form.name).isEmpty){
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme)
createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.createReadme)
}
// redirect to the repository
@@ -375,54 +352,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
})
/**
* Create user repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/user/repos")(usersOnly {
val owner = context.loginAccount.get.userName
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${owner}/${data.name}") {
if(getRepository(owner, data.name).isEmpty){
createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(owner, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else {
ApiError(
"A repository with this name already exists on this account",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
/**
* Create group repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/orgs/:org/repos")(managersOnly {
val groupName = params("org")
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${groupName}/${data.name}") {
if(getRepository(groupName, data.name).isEmpty){
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(groupName, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else {
ApiError(
"A repository with this name already exists for this group",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
@@ -456,7 +385,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
createRepository(
insertRepository(
repositoryName = repository.name,
userName = accountName,
description = repository.repository.description,
@@ -496,68 +425,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
})
private def createRepository(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) {
val ownerAccount = getAccountByUserName(owner).get
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
// Insert to the database at first
createRepository(name, owner, description, isPrivate)
// Add collaborators for group repository
if(ownerAccount.isGroupAccount){
getGroupMembers(owner).foreach { member =>
addCollaborator(owner, name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(owner, name)
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir)
if(createReadme){
using(Git.open(gitdir)){ git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
val content = if(description.nonEmpty){
name + "\n" +
"===============\n" +
"\n" +
description.get
} else {
name + "\n" +
"===============\n"
}
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
builder.finish()
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
}
}
// Create Wiki repository
createWikiRepository(loginAccount, owner, name)
// Record activity
recordCreateRepositoryActivity(owner, name, loginUserName)
}
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")
createLabel(userName, repositoryName, "enhancement", "84b6eb")
createLabel(userName, repositoryName, "invalid", "e6e6e6")
createLabel(userName, repositoryName, "question", "cc317c")
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
private def existsAccount: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None

View File

@@ -0,0 +1,389 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.model._
import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.PullRequestService._
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util._
import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git
import org.scalatra.{NoContent, UnprocessableEntity, Created}
import scala.collection.JavaConverters._
class ApiController extends ApiControllerBase
with RepositoryService
with AccountService
with ProtectedBranchService
with IssuesService
with LabelsService
with PullRequestService
with CommitStatusService
with RepositoryCreationService
with HandleCommentService
with WebHookService
with WebHookPullRequestService
with WebHookIssueCommentService
with WikiService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with CollaboratorsAuthenticator
trait ApiControllerBase extends ControllerBase {
self: RepositoryService
with AccountService
with ProtectedBranchService
with IssuesService
with LabelsService
with PullRequestService
with CommitStatusService
with RepositoryCreationService
with HandleCommentService
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with CollaboratorsAuthenticator =>
/**
* https://developer.github.com/v3/users/#get-a-single-user
*/
get("/api/v3/users/:userName") {
getAccountByUserName(params("userName")).map { account =>
JsonFormat(ApiUser(account))
} getOrElse NotFound
}
/**
* https://developer.github.com/v3/users/#get-the-authenticated-user
*/
get("/api/v3/user") {
context.loginAccount.map { account =>
JsonFormat(ApiUser(account))
} getOrElse Unauthorized
}
/**
* Create user repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/user/repos")(usersOnly {
val owner = context.loginAccount.get.userName
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${owner}/${data.name}") {
if(getRepository(owner, data.name).isEmpty){
createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(owner, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else {
ApiError(
"A repository with this name already exists on this account",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
/**
* Create group repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/orgs/:org/repos")(managersOnly {
val groupName = params("org")
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${groupName}/${data.name}") {
if(getRepository(groupName, data.name).isEmpty){
createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(groupName, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else {
ApiError(
"A repository with this name already exists for this group",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
import gitbucket.core.api._
(for{
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
} yield {
if(protection.enabled){
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
} else {
disableBranchProtection(repository.owner, repository.name, branch)
}
JsonFormat(ApiBranch(branch, protection)(RepositoryName(repository)))
}) getOrElse NotFound
})
/**
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
* but not enabled.
*/
get("/api/v3/rate_limit"){
contentType = formats("json")
// this message is same as github enterprise...
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
}
/**
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
*/
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
} yield {
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}).getOrElse(NotFound)
})
/**
* https://developer.github.com/v3/issues/comments/#create-a-comment
*/
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
issue <- getIssue(repository.owner, repository.name, issueId.toString)
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
(issue, id) <- handleComment(issue, Some(body), repository, action)
issueComment <- getComment(repository.owner, repository.name, id.toString())
} yield {
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
}) getOrElse NotFound
})
/**
* List all labels for this repository
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
*/
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
ApiLabel(label, RepositoryName(repository))
})
})
/**
* Get a single label
* https://developer.github.com/v3/issues/labels/#get-a-single-label
*/
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
JsonFormat(ApiLabel(label, RepositoryName(repository)))
} getOrElse NotFound()
})
/**
* Create a label
* https://developer.github.com/v3/issues/labels/#create-a-label
*/
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
getLabel(repository.owner, repository.name, labelId).map { label =>
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
} getOrElse NotFound()
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
))
}
}
}) getOrElse NotFound()
})
/**
* Update a label
* https://developer.github.com/v3/issues/labels/#update-a-label
*/
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat(ApiLabel(
getLabel(repository.owner, repository.name, label.labelId).get,
RepositoryName(repository)))
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
}
} getOrElse NotFound()
}
}) getOrElse NotFound()
})
/**
* Delete a label
* https://developer.github.com/v3/issues/labels/#delete-a-label
*/
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
deleteLabel(repository.owner, repository.name, label.labelId)
NoContent()
} getOrElse NotFound()
}
})
/**
* https://developer.github.com/v3/pulls/#list-pull-requests
*/
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
val issues:List[(Issue, Account, Int, PullRequest, Repository, Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name)
JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)) })
})
/**
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set())
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
JsonFormat(ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)))
}).getOrElse(NotFound)
})
/**
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
params("id").toIntOpt.flatMap{ issueId =>
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))){ git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository)
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
JsonFormat(commits)
}
}
} getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/#get
*/
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
})
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
*/
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
(for{
ref <- params.get("sha")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
data <- extractFromJsonBody[CreateAStatus] if data.isValid
creator <- context.loginAccount
state <- CommitState.valueOf(data.state)
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
state, data.target_url, data.description, new java.util.Date(), creator)
status <- getCommitStatus(repository.owner, repository.name, statusId)
} yield {
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* legacy route
*/
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
listStatusesRoute.action()
}
/**
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
}) getOrElse NotFound
})
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
}

View File

@@ -121,16 +121,6 @@ trait IndexControllerBase extends ControllerBase {
getAccountByUserName(params("userName")).isDefined
})
/**
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
* but not enabled.
*/
get("/api/v3/rate_limit"){
contentType = formats("json")
// this message is same as github enterprise...
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
}
// TODO Move to RepositoryViwerController?
post("/search", searchForm){ form =>
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")

View File

@@ -1,8 +1,6 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.issues.html
import gitbucket.core.model.Issue
import gitbucket.core.service.IssuesService._
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
@@ -16,11 +14,11 @@ import org.scalatra.Ok
class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
trait IssuesControllerBase extends ControllerBase {
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
case class IssueCreateForm(title: String, content: Option[String],
@@ -78,18 +76,6 @@ trait IssuesControllerBase extends ControllerBase {
}
})
/**
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
*/
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
} yield {
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}).getOrElse(NotFound)
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
html.create(
@@ -128,7 +114,7 @@ trait IssuesControllerBase extends ControllerBase {
getIssue(owner, name, issueId.toString).foreach { issue =>
// extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
// call web hooks
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
@@ -150,7 +136,7 @@ trait IssuesControllerBase extends ControllerBase {
// update issue
updateIssue(owner, name, issue.issueId, title, issue.content)
// extract references and create refer comment
createReferComment(owner, name, issue.copy(title = title), title)
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized
@@ -165,7 +151,7 @@ trait IssuesControllerBase extends ControllerBase {
// update issue
updateIssue(owner, name, issue.issueId, issue.title, content)
// extract references and create refer comment
createReferComment(owner, name, issue, content.getOrElse(""))
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized
@@ -174,30 +160,22 @@ trait IssuesControllerBase extends ControllerBase {
})
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
}
} getOrElse NotFound
})
/**
* https://developer.github.com/v3/issues/comments/#create-a-comment
*/
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
(issue, id) <- handleComment(issueId, Some(body), repository)()
issueComment <- getComment(repository.owner, repository.name, id.toString())
} yield {
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
}) getOrElse NotFound
})
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
}
} getOrElse NotFound
})
@@ -315,8 +293,16 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
defining(params.get("value")){ action =>
action match {
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) }
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) }
case Some("open") => executeBatch(repository) { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("reopen"))
}
}
case Some("close") => executeBatch(repository) { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("close"))
}
}
case _ => // TODO BadRequest
}
}
@@ -373,99 +359,6 @@ trait IssuesControllerBase extends ControllerBase {
}
}
// TODO Same method exists in PullRequestController. Should it moved to IssueService?
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
}
}
}
}
/**
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
(getAction: Issue => Option[String] =
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
defining(repository.owner, repository.name){ case (owner, name) =>
val userName = context.loginAccount.get.userName
getIssue(owner, name, issueId.toString) flatMap { issue =>
val (action, recordActivity) =
getAction(issue)
.collect {
case "close" if(!issue.closed) => true ->
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
case "reopen" if(issue.closed) => false ->
(Some("reopen") -> Some(recordReopenIssueActivity _))
}
.map { case (closed, t) =>
updateClosed(owner, name, issueId, closed)
t
}
.getOrElse(None -> None)
val commentId = (content, action) match {
case (None, None) => None
case (None, Some(action)) => Some(createComment(owner, name, userName, issueId, action.capitalize, action))
case (Some(content), _) => Some(createComment(owner, name, userName, issueId, content, action.map(_+ "_comment").getOrElse("comment")))
}
// record comment activity if comment is entered
content foreach {
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
(owner, name, userName, issueId, _)
}
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
// extract references and create refer comment
content.map { content =>
createReferComment(owner, name, issue, content)
}
// call web hooks
action match {
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
case Some(act) => val webHookAction = act match {
case "open" => "opened"
case "reopen" => "reopened"
case "close" => "closed"
case _ => act
}
if(issue.isPullRequest){
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
} else {
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
}
}
// notifications
Notifier() match {
case f =>
content foreach {
f.toNotify(repository, issue, _){
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
}
}
action foreach {
f.toNotify(repository, issue, _){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
}
}
commentId.map( issue -> _ )
}
}
}
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name){ case (owner, repoName) =>
val page = IssueSearchCondition.page(request)

View File

@@ -1,13 +1,12 @@
package gitbucket.core.controller
import gitbucket.core.api.{ApiError, CreateALabel, ApiLabel, JsonFormat}
import gitbucket.core.issues.labels.html
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
import gitbucket.core.util.{LockUtil, RepositoryName, ReferrerAuthenticator, CollaboratorsAuthenticator}
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
import gitbucket.core.util.Implicits._
import io.github.gitbucket.scalatra.forms._
import org.scalatra.i18n.Messages
import org.scalatra.{NoContent, UnprocessableEntity, Created, Ok}
import org.scalatra.Ok
class LabelsController extends LabelsControllerBase
with LabelsService with IssuesService with RepositoryService with AccountService
@@ -32,26 +31,6 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
/**
* List all labels for this repository
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
*/
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
ApiLabel(label, RepositoryName(repository))
})
})
/**
* Get a single label
* https://developer.github.com/v3/issues/labels/#get-a-single-label
*/
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
JsonFormat(ApiLabel(label, RepositoryName(repository)))
} getOrElse NotFound()
})
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
html.edit(None, repository)
})
@@ -66,31 +45,6 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
/**
* Create a label
* https://developer.github.com/v3/issues/labels/#create-a-label
*/
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
getLabel(repository.owner, repository.name, labelId).map { label =>
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
} getOrElse NotFound()
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
))
}
}
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
html.edit(Some(label), repository)
@@ -107,50 +61,11 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
/**
* Update a label
* https://developer.github.com/v3/issues/labels/#update-a-label
*/
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat(ApiLabel(
getLabel(repository.owner, repository.name, label.labelId).get,
RepositoryName(repository)))
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
}
} getOrElse NotFound()
}
}) getOrElse NotFound()
})
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
Ok()
})
/**
* Delete a label
* https://developer.github.com/v3/issues/labels/#delete-a-label
*/
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
deleteLabel(repository.owner, repository.name, label.labelId)
NoContent()
} getOrElse NotFound()
}
})
/**
* Constraint for the identifier such as user name, repository name or page name.
*/

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.model.{Account, CommitStatus, CommitState, Repository, PullRequest, Issue, WebHook}
import gitbucket.core.model.WebHook
import gitbucket.core.pulls.html
import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService
@@ -82,24 +81,6 @@ trait PullRequestsControllerBase extends ControllerBase {
}
})
/**
* https://developer.github.com/v3/pulls/#list-pull-requests
*/
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
val issues:List[(Issue, Account, Int, PullRequest, Repository, Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name)
JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)) })
})
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap{ issueId =>
val owner = repository.owner
@@ -126,47 +107,6 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound
})
/**
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set())
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
JsonFormat(ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)))
}).getOrElse(NotFound)
})
/**
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
params("id").toIntOpt.flatMap{ issueId =>
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))){ git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository)
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
JsonFormat(commits)
}
}
} getOrElse NotFound
})
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
params("id").toIntOpt.flatMap{ issueId =>
val owner = repository.owner
@@ -523,7 +463,7 @@ trait PullRequestsControllerBase extends ControllerBase {
getIssue(owner, name, issueId.toString) foreach { issue =>
// extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
// notifications
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
@@ -535,19 +475,6 @@ trait PullRequestsControllerBase extends ControllerBase {
}
})
// TODO Same method exists in IssueController. Should it moved to IssueService?
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
}
}
}
}
/**
* Parses branch identifier and extracts owner and branch name as tuple.
*
@@ -611,14 +538,4 @@ trait PullRequestsControllerBase extends ControllerBase {
hasWritePermission(owner, repoName, context.loginAccount))
}
// TODO: same as gitbucket.core.servlet.CommitLogHook ...
private def createIssueComment(owner: String, repository: String, commit: CommitInfo) = {
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
}
}
}
}
}

View File

@@ -142,22 +142,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
}
})
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
import gitbucket.core.api._
(for{
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
} yield {
if(protection.enabled){
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
} else {
disableBranchProtection(repository.owner, repository.name, branch)
}
JsonFormat(ApiBranch(branch, protection)(RepositoryName(repository)))
}) getOrElse NotFound
})
/**
* Display the Collaborators page.
*/

View File

@@ -2,7 +2,6 @@ package gitbucket.core.controller
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import gitbucket.core.api._
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.repo.html
import gitbucket.core.helper
@@ -13,7 +12,7 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, WebHook}
import gitbucket.core.model.{Account, WebHook}
import gitbucket.core.service.WebHookService._
import gitbucket.core.view
import gitbucket.core.view.helpers
@@ -122,13 +121,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
fileList(_)
})
/**
* https://developer.github.com/v3/repos/#get
*/
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
})
/**
* Displays the file list of the specified path and branch.
*/
@@ -160,65 +152,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
})
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
*/
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
(for{
ref <- params.get("sha")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
data <- extractFromJsonBody[CreateAStatus] if data.isValid
creator <- context.loginAccount
state <- CommitState.valueOf(data.state)
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
state, data.target_url, data.description, new java.util.Date(), creator)
status <- getCommitStatus(repository.owner, repository.name, statusId)
} yield {
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* legacy route
*/
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
listStatusesRoute.action()
}
/**
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
}) getOrElse NotFound
})
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)

View File

@@ -0,0 +1,91 @@
package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.model.Issue
import gitbucket.core.model.Profile._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Notifier
import profile.simple._
trait HandleCommentService {
self: RepositoryService with IssuesService with ActivityService
with WebHookService with WebHookIssueCommentService with WebHookPullRequestService =>
/**
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/
def handleComment(issue: Issue, content: Option[String], repository: RepositoryService.RepositoryInfo, actionOpt: Option[String])
(implicit context: Context, s: Session) = {
defining(repository.owner, repository.name){ case (owner, name) =>
val userName = context.loginAccount.get.userName
val (action, recordActivity) = actionOpt
.collect {
case "close" if(!issue.closed) => true ->
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
case "reopen" if(issue.closed) => false ->
(Some("reopen") -> Some(recordReopenIssueActivity _))
}
.map { case (closed, t) =>
updateClosed(owner, name, issue.issueId, closed)
t
}
.getOrElse(None -> None)
val commentId = (content, action) match {
case (None, None) => None
case (None, Some(action)) => Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
case (Some(content), _) => Some(createComment(owner, name, userName, issue.issueId, content, action.map(_+ "_comment").getOrElse("comment")))
}
// record comment activity if comment is entered
content foreach {
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
(owner, name, userName, issue.issueId, _)
}
recordActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) )
// extract references and create refer comment
content.map { content =>
createReferComment(owner, name, issue, content, context.loginAccount.get)
}
// call web hooks
action match {
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
case Some(act) => val webHookAction = act match {
case "open" => "opened"
case "reopen" => "reopened"
case "close" => "closed"
case _ => act
}
if(issue.isPullRequest){
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
} else {
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
}
}
// notifications
Notifier() match {
case f =>
content foreach {
f.toNotify(repository, issue, _){
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issue.issueId}#comment-${commentId.get}")
}
}
action foreach {
f.toNotify(repository, issue, _){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issue.issueId}")
}
}
}
commentId.map( issue -> _ )
}
}
}

View File

@@ -1,6 +1,8 @@
package gitbucket.core.service
import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.StringUtil
import profile.simple._
import gitbucket.core.util.StringUtil._
@@ -12,6 +14,7 @@ import Q.interpolation
trait IssuesService {
self: AccountService =>
import IssuesService._
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
@@ -394,6 +397,29 @@ trait IssuesService {
}
}
}
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(implicit s: Session) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer")
}
}
}
}
def createIssueComment(owner: String, repository: String, commit: CommitInfo)(implicit s: Session) = {
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
}
}
}
}
}
object IssuesService {

View File

@@ -0,0 +1,79 @@
package gitbucket.core.service
import gitbucket.core.model.Profile._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.JGitUtil
import gitbucket.core.model.Account
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
import profile.simple._
trait RepositoryCreationService {
self: AccountService with RepositoryService with LabelsService with WikiService with ActivityService =>
def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
(implicit s: Session) {
val ownerAccount = getAccountByUserName(owner).get
val loginUserName = loginAccount.userName
// Insert to the database at first
insertRepository(name, owner, description, isPrivate)
// Add collaborators for group repository
if(ownerAccount.isGroupAccount){
getGroupMembers(owner).foreach { member =>
addCollaborator(owner, name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(owner, name)
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir)
if(createReadme){
using(Git.open(gitdir)){ git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
val content = if(description.nonEmpty){
name + "\n" +
"===============\n" +
"\n" +
description.get
} else {
name + "\n" +
"===============\n"
}
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
builder.finish()
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
}
}
// Create Wiki repository
createWikiRepository(loginAccount, owner, name)
// Record activity
recordCreateRepositoryActivity(owner, name, loginUserName)
}
def insertDefaultLabels(userName: String, repositoryName: String)(implicit s: Session): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")
createLabel(userName, repositoryName, "enhancement", "84b6eb")
createLabel(userName, repositoryName, "invalid", "e6e6e6")
createLabel(userName, repositoryName, "question", "cc317c")
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
}

View File

@@ -19,7 +19,7 @@ trait RepositoryService { self: AccountService =>
* @param originRepositoryName specify for the forked repository. (default is None)
* @param originUserName specify for the forked repository. (default is None)
*/
def createRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean,
def insertRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean,
originRepositoryName: Option[String] = None, originUserName: Option[String] = None,
parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None)
(implicit s: Session): Unit = {

View File

@@ -10,7 +10,6 @@ import gitbucket.core.service.WebHookService._
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util._
import org.eclipse.jgit.api.Git
@@ -168,7 +167,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) {
if (issueCount > 0) {
pushedIds.add(commit.id)
createIssueComment(commit)
createIssueComment(owner, repository, commit)
// close issues
if(refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE){
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository)
@@ -230,13 +229,4 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
}
}
private def createIssueComment(commit: CommitInfo) = {
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
}
}
}
}
}

View File

@@ -33,7 +33,7 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
now = fixture1.registeredDate)
test("createCommitState can insert and update") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
insertRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
// other one can update
@@ -60,14 +60,14 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
test("getCommitStatus can find by commitId and context") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
insertRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(fixture1.copy(commitStatusId=id)))
}}
test("getCommitStatus can find by commitStatusId") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
insertRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
}}

View File

@@ -3,7 +3,7 @@ package gitbucket.core.service
import gitbucket.core.model._
import org.scalatest.FunSpec
class PullRequestServiceSpec extends FunSpec with ServiceSpecBase with PullRequestService with IssuesService {
class PullRequestServiceSpec extends FunSpec with ServiceSpecBase with PullRequestService with IssuesService with AccountService {
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)

View File

@@ -6,7 +6,7 @@ import org.scalatest.FunSuite
class RepositoryServiceSpec extends FunSuite with ServiceSpecBase with RepositoryService with AccountService{
test("renameRepository can rename CommitState, ProtectedBranches") { withTestDB { implicit session =>
val tester = generateNewAccount("tester")
createRepository("repo", "root", None, false)
insertRepository("repo", "root", None, false)
val service = new CommitStatusService with ProtectedBranchService {}
val id = service.createCommitStatus(
userName = "root",

View File

@@ -42,7 +42,7 @@ trait ServiceSpecBase {
def generateNewUserWithDBRepository(userName:String, repositoryName:String)(implicit s:Session):Account = {
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))
dummyService.createRepository(repositoryName, userName, None, false)
dummyService.insertRepository(repositoryName, userName, None, false)
ac
}