diff --git a/src/main/scala/gitbucket/core/api/CreateAPullRequest.scala b/src/main/scala/gitbucket/core/api/CreateAPullRequest.scala new file mode 100644 index 0000000..aa253b8 --- /dev/null +++ b/src/main/scala/gitbucket/core/api/CreateAPullRequest.scala @@ -0,0 +1,16 @@ +package gitbucket.core.api + +case class CreateAPullRequest( + title: String, + head: String, + base: String, + body: Option[String], + maintainer_can_modify: Option[Boolean] +) + +case class CreateAPullRequestAlt( + issue: Integer, + head: String, + base: String, + maintainer_can_modify: Option[Boolean] +) diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index c505fba..6d7262f 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -15,7 +15,7 @@ import gitbucket.core.util._ import org.scalatra.forms._ import org.eclipse.jgit.api.Git -import org.eclipse.jgit.lib.PersonIdent +import org.eclipse.jgit.lib.{ObjectId, PersonIdent} import org.eclipse.jgit.revwalk.RevWalk import org.scalatra.BadRequest @@ -579,97 +579,68 @@ .map(_.repository.repositoryName) }; originRepository <- getRepository(originOwner, originRepositoryName)) yield { - using( - Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), - Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) - ) { - case (oldGit, newGit) => - val (oldId, newId) = - if (originRepository.branchList.contains(originId)) { - val forkedId2 = - forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) + val (oldId, newId) = + getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId) - val originId2 = JGitUtil.getForkedCommitId( - oldGit, - newGit, - originRepository.owner, - originRepository.name, - originId, - forkedRepository.owner, - forkedRepository.name, - forkedId2 - ) + (oldId, newId) match { + case (Some(oldId), Some(newId)) => { + val (commits, diffs) = getRequestCompareInfo( + originRepository.owner, + originRepository.name, + oldId.getName, + forkedRepository.owner, + forkedRepository.name, + newId.getName + ) - (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) - - } else { - val originId2 = - originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId) - val forkedId2 = - forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) - - (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) - } - - (oldId, newId) match { - case (Some(oldId), Some(newId)) => { - val (commits, diffs) = getRequestCompareInfo( - originRepository.owner, - originRepository.name, - oldId.getName, - forkedRepository.owner, - forkedRepository.name, - newId.getName - ) - - 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 - .map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) - .flatten - .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), - getLabels(originRepository.owner, originRepository.name) - ) - } - 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)}" - ) + 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 + .map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) + .flatten + .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), + getLabels(originRepository.owner, originRepository.name) + ) + } + 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() }) @@ -820,20 +791,6 @@ html.proposals(proposedBranches, targetRepository, repository) }) - /** - * Parses branch identifier and extracts owner and branch name as tuple. - * - * - "owner:branch" to ("owner", "branch") - * - "branch" to ("defaultOwner", "branch") - */ - private def parseCompareIdentifier(value: String, defaultOwner: String): (String, String) = - if (value.contains(':')) { - val array = value.split(":") - (array(0), array(1)) - } else { - (defaultOwner, value) - } - private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = defining(repository.owner, repository.name) { case (owner, repoName) => diff --git a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala index d2d8357..b4c45bc 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala @@ -2,19 +2,28 @@ import gitbucket.core.api._ import gitbucket.core.controller.ControllerBase import gitbucket.core.model.{Account, Issue, PullRequest, Repository} -import gitbucket.core.service.{AccountService, IssuesService, PullRequestService, RepositoryService} +import gitbucket.core.service._ import gitbucket.core.service.IssuesService.IssueSearchCondition import gitbucket.core.service.PullRequestService.PullRequestLimit import gitbucket.core.util.Directory.getRepositoryDir import gitbucket.core.util.Implicits._ import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.util.SyntaxSugars.using -import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName} +import gitbucket.core.util._ import org.eclipse.jgit.api.Git +import org.scalatra.NoContent + import scala.collection.JavaConverters._ trait ApiPullRequestControllerBase extends ControllerBase { - self: AccountService with IssuesService with PullRequestService with RepositoryService with ReferrerAuthenticator => + self: AccountService + with IssuesService + with PullRequestService + with RepositoryService + with MergeService + with ReferrerAuthenticator + with ReadableUsersAuthenticator + with WritableUsersAuthenticator => /* * i. Link Relations @@ -25,9 +34,6 @@ * ii. List pull requests * https://developer.github.com/v3/pulls/#list-pull-requests */ - /** - * 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 @@ -62,38 +68,11 @@ * iii. Get a single pull request * https://developer.github.com/v3/pulls/#get-a-single-pull-request */ - /** - * 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.empty - ) - baseOwner <- users.get(repository.owner) - headOwner <- users.get(pullRequest.requestUserName) - issueUser <- users.get(issue.openedUserName) - assignee = issue.assignedUserName.flatMap { userName => - getAccountByUserName(userName, false) - } - headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) } yield { - JsonFormat( - ApiPullRequest( - issue = issue, - pullRequest = pullRequest, - headRepo = ApiRepository(headRepo, ApiUser(headOwner)), - baseRepo = ApiRepository(repository, ApiUser(baseOwner)), - user = ApiUser(issueUser), - labels = getIssueLabels(repository.owner, repository.name, issue.issueId) - .map(ApiLabel(_, RepositoryName(repository))), - assignee = assignee.map(ApiUser.apply), - mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) - ) - ) + JsonFormat(getApiPullRequest(repository, issueId)) }) getOrElse NotFound() }) @@ -102,6 +81,79 @@ * https://developer.github.com/v3/pulls/#create-a-pull-request * requested #1843 */ + post("/api/v3/repos/:owner/:repository/pulls")(readableUsersOnly { repository => + (for { + data <- extractFromJsonBody[Either[CreateAPullRequest, CreateAPullRequestAlt]] + } yield { + data match { + case Left(createPullReq) => + val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReq.head, repository.owner) + getRepository(reqOwner, repository.name) + .flatMap { + forkedRepository => + getPullRequestCommitFromTo(repository, forkedRepository, createPullReq.base, reqBranch) match { + case (Some(commitIdFrom), Some(commitIdTo)) => + val issueId = insertIssue( + owner = repository.owner, + repository = repository.name, + loginUser = context.loginAccount.get.userName, + title = createPullReq.title, + content = createPullReq.body, + assignedUserName = None, + milestoneId = None, + priorityId = None, + isPullRequest = true + ) + + createPullRequest( + originUserName = repository.owner, + originRepositoryName = repository.name, + issueId = issueId, + originBranch = createPullReq.base, + requestUserName = reqOwner, + requestRepositoryName = repository.name, + requestBranch = reqBranch, + commitIdFrom = commitIdFrom.getName, + commitIdTo = commitIdTo.getName + ) + getApiPullRequest(repository, issueId).map(JsonFormat(_)) + case _ => + None + } + } + .getOrElse { + NotFound() + } + case Right(createPullReqAlt) => + val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReqAlt.head, repository.owner) + getRepository(reqOwner, repository.name) + .flatMap { + forkedRepository => + getPullRequestCommitFromTo(repository, forkedRepository, createPullReqAlt.base, reqBranch) match { + case (Some(commitIdFrom), Some(commitIdTo)) => + changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue) + createPullRequest( + originUserName = repository.owner, + originRepositoryName = repository.name, + issueId = createPullReqAlt.issue, + originBranch = createPullReqAlt.base, + requestUserName = reqOwner, + requestRepositoryName = repository.name, + requestBranch = reqBranch, + commitIdFrom = commitIdFrom.getName, + commitIdTo = commitIdTo.getName + ) + getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_)) + case _ => + None + } + } + .getOrElse { + NotFound() + } + } + }) + }) /* * v. Update a pull request @@ -112,9 +164,6 @@ * vi. List commits on a pull request * https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request */ - /** - * 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 @@ -149,6 +198,18 @@ * viii. Get if a pull request has been merged * https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged */ + get("/api/v3/repos/:owner/:repository/pulls/:id/merge")(referrersOnly {repository => + (for{ + issueId <- params("id").toIntOpt + (issue, pullReq) <- getPullRequest(repository.owner, repository.name, issueId) + } yield { + if(checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) { + NoContent + }else{ + NotFound + } + }).getOrElse(NotFound) + }) /* * ix. Merge a pull request (Merge Button) @@ -156,7 +217,36 @@ */ /* - * x. Labels, assignees, and milestones - * https://developer.github.com/v3/pulls/#labels-assignees-and-milestones - */ + * x. Labels, assignees, and milestones + * https://developer.github.com/v3/pulls/#labels-assignees-and-milestones + */ + + private def getApiPullRequest(repository: RepositoryService.RepositoryInfo, issueId: Int): Option[ApiPullRequest] = { + for { + (issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) + users = getAccountsByUserNames( + Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), + Set.empty + ) + baseOwner <- users.get(repository.owner) + headOwner <- users.get(pullRequest.requestUserName) + issueUser <- users.get(issue.openedUserName) + assignee = issue.assignedUserName.flatMap { userName => + getAccountByUserName(userName, false) + } + headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) + } yield { + ApiPullRequest( + issue = issue, + pullRequest = pullRequest, + headRepo = ApiRepository(headRepo, ApiUser(headOwner)), + baseRepo = ApiRepository(repository, ApiUser(baseOwner)), + user = ApiUser(issueUser), + labels = getIssueLabels(repository.owner, repository.name, issue.issueId) + .map(ApiLabel(_, RepositoryName(repository))), + assignee = assignee.map(ApiUser.apply), + mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) + ) + } + } } diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index 79223bf..29665bd 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -526,6 +526,15 @@ .update(title, content, currentDate) } + def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session) = { + Issues + .filter(_.byPrimaryKey(owner, repository, issueId)) + .map { t => + t.pullRequest + } + .update(true) + } + def updateAssignedUserName( owner: String, repository: String, diff --git a/src/main/scala/gitbucket/core/service/PullRequestService.scala b/src/main/scala/gitbucket/core/service/PullRequestService.scala index 8612d74..8297048 100644 --- a/src/main/scala/gitbucket/core/service/PullRequestService.scala +++ b/src/main/scala/gitbucket/core/service/PullRequestService.scala @@ -4,6 +4,7 @@ import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile.blockingApi._ import difflib.{Delta, DiffUtils} +import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Directory._ import gitbucket.core.util.Implicits._ @@ -12,6 +13,7 @@ import gitbucket.core.view import gitbucket.core.view.helpers import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.ObjectId import scala.collection.JavaConverters._ @@ -384,6 +386,58 @@ updateClosed(owner, repository, pull.issueId, true) } + /** + * Parses branch identifier and extracts owner and branch name as tuple. + * + * - "owner:branch" to ("owner", "branch") + * - "branch" to ("defaultOwner", "branch") + */ + def parseCompareIdentifier(value: String, defaultOwner: String): (String, String) = + if (value.contains(':')) { + val array = value.split(":") + (array(0), array(1)) + } else { + (defaultOwner, value) + } + + def getPullRequestCommitFromTo( + originRepository: RepositoryInfo, + forkedRepository: RepositoryInfo, + originId: String, + forkedId: String + ): (Option[ObjectId], Option[ObjectId]) = { + using( + Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), + Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) + ) { + case (oldGit, newGit) => + if (originRepository.branchList.contains(originId)) { + val forkedId2 = + forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) + + val originId2 = JGitUtil.getForkedCommitId( + oldGit, + newGit, + originRepository.owner, + originRepository.name, + originId, + forkedRepository.owner, + forkedRepository.name, + forkedId2 + ) + + (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) + + } else { + val originId2 = + originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId) + val forkedId2 = + forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) + + (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) + } + } + } } object PullRequestService {