From f42ff607728fe051c3988e50e641b27301544c64 Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Mon, 8 Oct 2018 23:23:18 +0900 Subject: [PATCH 1/9] Add some GitHub compatible API, close #2079 --- .../gitbucket/core/api/AddACollaborator.scala | 9 +++++ .../scala/gitbucket/core/api/ApiGroup.scala | 20 ++++++++++ .../gitbucket/core/api/CreateAUser.scala | 11 +++++ .../scala/gitbucket/core/api/JsonFormat.scala | 1 + .../api/ApiIssueControllerBase.scala | 3 +- .../api/ApiOrganizationControllerBase.scala | 35 ++++++++++++++-- .../api/ApiPullRequestControllerBase.scala | 1 + ...RepositoryCollaboratorControllerBase.scala | 27 ++++++++++--- .../ApiRepositoryContentsControllerBase.scala | 3 ++ .../api/ApiUserControllerBase.scala | 40 ++++++++++++++++--- .../core/service/AccountService.scala | 7 +++- .../core/service/RepositoryService.scala | 8 ++++ 12 files changed, 148 insertions(+), 17 deletions(-) create mode 100644 src/main/scala/gitbucket/core/api/AddACollaborator.scala create mode 100644 src/main/scala/gitbucket/core/api/ApiGroup.scala create mode 100644 src/main/scala/gitbucket/core/api/CreateAUser.scala diff --git a/src/main/scala/gitbucket/core/api/AddACollaborator.scala b/src/main/scala/gitbucket/core/api/AddACollaborator.scala new file mode 100644 index 000000000..05b176870 --- /dev/null +++ b/src/main/scala/gitbucket/core/api/AddACollaborator.scala @@ -0,0 +1,9 @@ +package gitbucket.core.api + +case class AddACollaborator(permission: String) { + val role: String = permission match { + case "admin" => "ADMIN" + case "push" => "DEVELOPER" + case "pull" => "GUEST" + } +} diff --git a/src/main/scala/gitbucket/core/api/ApiGroup.scala b/src/main/scala/gitbucket/core/api/ApiGroup.scala new file mode 100644 index 000000000..5af6d4f2e --- /dev/null +++ b/src/main/scala/gitbucket/core/api/ApiGroup.scala @@ -0,0 +1,20 @@ +package gitbucket.core.api + +import java.util.Date + +import gitbucket.core.model.Account + +case class ApiGroup(login: String, description: Option[String], created_at: Date) { + val id = 0 // dummy id + val url = ApiPath(s"/api/v3/orgs/${login}") + val html_url = ApiPath(s"/${login}") + val avatar_url = ApiPath(s"/${login}/_avatar") +} + +object ApiGroup { + def apply(group: Account): ApiGroup = ApiGroup( + login = group.userName, + description = group.description, + created_at = group.registeredDate + ) +} diff --git a/src/main/scala/gitbucket/core/api/CreateAUser.scala b/src/main/scala/gitbucket/core/api/CreateAUser.scala new file mode 100644 index 000000000..f2cd9f053 --- /dev/null +++ b/src/main/scala/gitbucket/core/api/CreateAUser.scala @@ -0,0 +1,11 @@ +package gitbucket.core.api + +case class CreateAUser( + login: String, + password: String, + email: String, + fullName: Option[String], + isAdmin: Option[Boolean], + description: Option[String], + url: Option[String] +) diff --git a/src/main/scala/gitbucket/core/api/JsonFormat.scala b/src/main/scala/gitbucket/core/api/JsonFormat.scala index 857992643..96fbd690a 100644 --- a/src/main/scala/gitbucket/core/api/JsonFormat.scala +++ b/src/main/scala/gitbucket/core/api/JsonFormat.scala @@ -24,6 +24,7 @@ object JsonFormat { }, { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) } ) ) + FieldSerializer[ApiUser]() + + FieldSerializer[ApiGroup]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() + FieldSerializer[ApiCommitListItem.Parent]() + diff --git a/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala index 6fe0632e2..12f44bef9 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala @@ -5,7 +5,7 @@ import gitbucket.core.model.{Account, Issue} import gitbucket.core.service.{AccountService, IssueCreationService, IssuesService, MilestonesService} import gitbucket.core.service.IssuesService.IssueSearchCondition import gitbucket.core.service.PullRequestService.PullRequestLimit -import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName} +import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName, UsersAuthenticator} import gitbucket.core.util.Implicits._ trait ApiIssueControllerBase extends ControllerBase { @@ -18,6 +18,7 @@ trait ApiIssueControllerBase extends ControllerBase { /* * i. List issues * https://developer.github.com/v3/issues/#list-issues + * requested: 1743 */ /* diff --git a/src/main/scala/gitbucket/core/controller/api/ApiOrganizationControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiOrganizationControllerBase.scala index 0b64c9fbd..85ca93cde 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiOrganizationControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiOrganizationControllerBase.scala @@ -1,26 +1,36 @@ package gitbucket.core.controller.api -import gitbucket.core.api.{ApiRepository, ApiUser, JsonFormat} +import gitbucket.core.api.{ApiGroup, ApiRepository, ApiUser, JsonFormat} import gitbucket.core.controller.ControllerBase import gitbucket.core.service.{AccountService, RepositoryService} import gitbucket.core.util.Implicits._ +import gitbucket.core.util.UsersAuthenticator trait ApiOrganizationControllerBase extends ControllerBase { - self: RepositoryService with AccountService => + self: RepositoryService with AccountService with UsersAuthenticator => /* * i. List your organizations * https://developer.github.com/v3/orgs/#list-your-organizations */ + get("/api/v3/user/orgs")(usersOnly { + JsonFormat(getGroupsByUserName(context.loginAccount.get.userName).flatMap(getAccountByUserName(_)).map(ApiGroup(_))) + }) /* * ii. List all organizations * https://developer.github.com/v3/orgs/#list-all-organizations */ + get("/api/v3/organizations") { + JsonFormat(getAllUsers(false, true).filter(a => a.isGroupAccount).map(ApiGroup(_))) + } /* * iii. List user organizations * https://developer.github.com/v3/orgs/#list-user-organizations */ + get("/api/v3/users/:userName/orgs") { + JsonFormat(getGroupsByUserName(params("userName")).flatMap(getAccountByUserName(_)).map(ApiGroup(_))) + } /** * iv. Get an organization @@ -28,7 +38,26 @@ trait ApiOrganizationControllerBase extends ControllerBase { */ get("/api/v3/orgs/:groupName") { getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account => - JsonFormat(ApiUser(account)) + JsonFormat(ApiGroup(account)) } getOrElse NotFound() } + + /* + * v. Edit an organization + * https://developer.github.com/v3/orgs/#edit-an-organization + */ + + /* + * ghe: i. Create an organization + * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/orgs/#create-an-organization + */ + + /* + * ghe: ii. Rename an organization + * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/orgs/#rename-an-organization + */ + + /* + * should implement delete an organization API? + */ } diff --git a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala index fe66183dd..d2d835799 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala @@ -100,6 +100,7 @@ trait ApiPullRequestControllerBase extends ControllerBase { /* * iv. Create a pull request * https://developer.github.com/v3/pulls/#create-a-pull-request + * requested #1843 */ /* diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCollaboratorControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCollaboratorControllerBase.scala index 402b7a3f6..f8fe1e553 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCollaboratorControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCollaboratorControllerBase.scala @@ -1,12 +1,13 @@ package gitbucket.core.controller.api -import gitbucket.core.api.{ApiUser, JsonFormat} +import gitbucket.core.api.{AddACollaborator, ApiUser, JsonFormat} import gitbucket.core.controller.ControllerBase import gitbucket.core.service.{AccountService, RepositoryService} import gitbucket.core.util.Implicits._ -import gitbucket.core.util.ReferrerAuthenticator +import gitbucket.core.util.{OwnerAuthenticator, ReferrerAuthenticator} +import org.scalatra.NoContent trait ApiRepositoryCollaboratorControllerBase extends ControllerBase { - self: RepositoryService with AccountService with ReferrerAuthenticator => + self: RepositoryService with AccountService with ReferrerAuthenticator with OwnerAuthenticator => /* * i. List collaborators @@ -31,10 +32,24 @@ trait ApiRepositoryCollaboratorControllerBase extends ControllerBase { /* * iv. Add user as a collaborator * https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator + * requested #1586 */ + put("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository => + for { + data <- extractFromJsonBody[AddACollaborator] + } yield { + addCollaborator(repository.owner, repository.name, params("userName"), data.role) + NoContent() + } + }) /* - * v. Remove user as a collaborator - * https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator - */ + * v. Remove user as a collaborator + * https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator + * requested #1586 + */ + delete("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository => + removeCollaborator(repository.owner, repository.name, params("userName")) + NoContent() + }) } diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala index c1d128b63..d0dae95cb 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala @@ -103,16 +103,19 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase { /* * iii. Create a file * https://developer.github.com/v3/repos/contents/#create-a-file + * requested #2112 */ /* * iv. Update a file * https://developer.github.com/v3/repos/contents/#update-a-file + * requested #2112 */ /* * v. Delete a file * https://developer.github.com/v3/repos/contents/#delete-a-file + * should be implemented */ /* diff --git a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala index 7c596fde7..d5bbbd875 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala @@ -1,11 +1,12 @@ package gitbucket.core.controller.api -import gitbucket.core.api.{ApiUser, JsonFormat} +import gitbucket.core.api.{ApiUser, CreateAUser, JsonFormat} import gitbucket.core.controller.ControllerBase import gitbucket.core.service.{AccountService, RepositoryService} +import gitbucket.core.util.AdminAuthenticator import gitbucket.core.util.Implicits._ trait ApiUserControllerBase extends ControllerBase { - self: RepositoryService with AccountService => + self: RepositoryService with AccountService with AdminAuthenticator => /** * i. Get a single user @@ -39,8 +40,37 @@ trait ApiUserControllerBase extends ControllerBase { */ /* - * v. Get all users - * https://developer.github.com/v3/users/#get-all-users - */ + * v. Get all users + * https://developer.github.com/v3/users/#get-all-users + */ + /* + * ghe: i. Create a new use + * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#create-a-new-user + */ + post("/api/v3/admin/users")(adminOnly { + for { + data <- extractFromJsonBody[CreateAUser] + } yield { + val user = createAccount( + data.login, + data.password, + data.fullName.getOrElse(data.login), + data.email, + data.isAdmin.getOrElse(false), + data.description, + data.url + ) + JsonFormat(ApiUser(user)) + } + }) + + /* + * ghe: vii. Suspend a user + * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#suspend-a-user + */ + /* + * ghe: vii. Unsuspend a user + * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#unsuspend-a-user + */ } diff --git a/src/main/scala/gitbucket/core/service/AccountService.scala b/src/main/scala/gitbucket/core/service/AccountService.scala index ea851b105..f5f9cdbd0 100644 --- a/src/main/scala/gitbucket/core/service/AccountService.scala +++ b/src/main/scala/gitbucket/core/service/AccountService.scala @@ -163,8 +163,8 @@ trait AccountService { isAdmin: Boolean, description: Option[String], url: Option[String] - )(implicit s: Session): Unit = - Accounts insert Account( + )(implicit s: Session): Account = { + val account = Account( userName = userName, password = password, fullName = fullName, @@ -179,6 +179,9 @@ trait AccountService { isRemoved = false, description = description ) + Accounts insert account + account + } def updateAccount(account: Account)(implicit s: Session): Unit = Accounts diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 08f077d6b..e5295ed30 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -560,6 +560,14 @@ trait RepositoryService { self: AccountService => ): Unit = Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role) + /** + * Remove specified collaborator from the repository. + */ + def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)( + implicit s: Session + ): Unit = + Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete + /** * Remove all collaborators from the repository. */ From 2eee95bd2da8a5cc09fec2100a82be811d5bbd53 Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 13 Oct 2018 09:40:18 +0900 Subject: [PATCH 2/9] Add Issue label related APIs --- .../core/controller/ApiController.scala | 9 ++- .../api/ApiIssueLabelControllerBase.scala | 58 +++++++++++++++++++ .../api/ApiUserControllerBase.scala | 6 +- .../core/service/IssuesService.scala | 19 ++++++ .../core/service/LabelsService.scala | 6 ++ 5 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 049f59bea..9f4dd8915 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -1,7 +1,12 @@ package gitbucket.core.controller import gitbucket.core.api._ -import gitbucket.core.controller.api.{ApiOrganizationControllerBase, ApiRepositoryControllerBase, ApiUserControllerBase} +import gitbucket.core.controller.api.{ + ApiIssueLabelControllerBase, + ApiOrganizationControllerBase, + ApiRepositoryControllerBase, + ApiUserControllerBase +} import gitbucket.core.service._ import gitbucket.core.util.Implicits._ import gitbucket.core.util._ @@ -12,6 +17,7 @@ class ApiController with ApiOrganizationControllerBase with ApiRepositoryControllerBase with ApiUserControllerBase + with ApiIssueLabelControllerBase with RepositoryService with AccountService with ProtectedBranchService @@ -30,6 +36,7 @@ class ApiController with WikiService with ActivityService with PrioritiesService + with AdminAuthenticator with OwnerAuthenticator with UsersAuthenticator with GroupManagerAuthenticator diff --git a/src/main/scala/gitbucket/core/controller/api/ApiIssueLabelControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiIssueLabelControllerBase.scala index 0f9e4cc97..307a4dbe3 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiIssueLabelControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiIssueLabelControllerBase.scala @@ -109,26 +109,84 @@ trait ApiIssueLabelControllerBase extends ControllerBase { * vi. List labels on an issue * https://developer.github.com/v3/issues/labels/#list-labels-on-an-issue */ + get("/api/v3/repos/:owner/:repository/issues/:id/labels")(referrersOnly { repository => + JsonFormat(getIssueLabels(repository.owner, repository.name, params("id").toInt).map { l => + ApiLabel(l, RepositoryName(repository.owner, repository.name)) + }) + }) /* * vii. Add labels to an issue * https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue */ + post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => + JsonFormat(for { + data <- extractFromJsonBody[Seq[String]]; + issueId <- params("id").toIntOpt + } yield { + data.map { labelName => + val label = getLabel(repository.owner, repository.name, labelName).getOrElse( + getLabel( + repository.owner, + repository.name, + createLabel(repository.owner, repository.name, labelName) + ).get + ) + registerIssueLabel(repository.owner, repository.name, issueId, label.labelId, true) + ApiLabel(label, RepositoryName(repository.owner, repository.name)) + } + }) + }) /* * viii. Remove a label from an issue * https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue */ + delete("/api/v3/repos/:owner/:repository/issues/:id/labels/:name")(writableUsersOnly { repository => + val issueId = params("id").toInt + val labelName = params("name") + getLabel(repository.owner, repository.name, labelName) match { + case Some(label) => + deleteIssueLabel(repository.owner, repository.name, issueId, label.labelId, true) + JsonFormat(Seq(label)) + case None => + NotFound() + } + }) /* * ix. Replace all labels for an issue * https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue */ + put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => + JsonFormat(for { + data <- extractFromJsonBody[Seq[String]]; + issueId <- params("id").toIntOpt + } yield { + deleteAllIssueLabels(repository.owner, repository.name, issueId, true) + data.map { labelName => + val label = getLabel(repository.owner, repository.name, labelName).getOrElse( + getLabel( + repository.owner, + repository.name, + createLabel(repository.owner, repository.name, labelName) + ).get + ) + registerIssueLabel(repository.owner, repository.name, issueId, label.labelId, true) + ApiLabel(label, RepositoryName(repository.owner, repository.name)) + } + }) + }) /* * x. Remove all labels from an issue * https://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue */ + delete("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository => + val issueId = params("id").toInt + deleteAllIssueLabels(repository.owner, repository.name, issueId, true) + NoContent() + }) /* * xi Get labels for every issue in a milestone diff --git a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala index d5bbbd875..dc708d9c4 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala @@ -70,7 +70,7 @@ trait ApiUserControllerBase extends ControllerBase { * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#suspend-a-user */ /* - * ghe: vii. Unsuspend a user - * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#unsuspend-a-user - */ + * ghe: vii. Unsuspend a user + * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#unsuspend-a-user + */ } diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index 86103d2dd..79223bf09 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -475,6 +475,25 @@ trait IssuesService { IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) delete } + def deleteAllIssueLabels(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)( + implicit context: Context, + s: Session + ): Int = { + if (insertComment) { + IssueComments insert IssueComment( + userName = owner, + repositoryName = repository, + issueId = issueId, + action = "delete_label", + commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"), + content = "All labels", + registeredDate = currentDate, + updatedDate = currentDate + ) + } + IssueLabels filter (_.byIssue(owner, repository, issueId)) delete + } + def createComment( owner: String, repository: String, diff --git a/src/main/scala/gitbucket/core/service/LabelsService.scala b/src/main/scala/gitbucket/core/service/LabelsService.scala index 4bbd5d837..a0f7dc8f5 100644 --- a/src/main/scala/gitbucket/core/service/LabelsService.scala +++ b/src/main/scala/gitbucket/core/service/LabelsService.scala @@ -3,6 +3,7 @@ package gitbucket.core.service import gitbucket.core.model.Label import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile.blockingApi._ +import gitbucket.core.util.StringUtil trait LabelsService { @@ -24,6 +25,11 @@ trait LabelsService { ) } + def createLabel(owner: String, repository: String, labelName: String)(implicit s: Session): Int = { + val color = StringUtil.md5(labelName).substring(0, 6) + createLabel(owner, repository, labelName, color) + } + def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String)( implicit s: Session ): Unit = From 023d150d95917547d6899c5d9fab6a332a718d0e Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 13 Oct 2018 10:24:34 +0900 Subject: [PATCH 3/9] Add user management related APIs --- .../gitbucket/core/api/UpdateAUser.scala | 11 ++++ .../api/ApiUserControllerBase.scala | 52 ++++++++++++++++--- 2 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 src/main/scala/gitbucket/core/api/UpdateAUser.scala diff --git a/src/main/scala/gitbucket/core/api/UpdateAUser.scala b/src/main/scala/gitbucket/core/api/UpdateAUser.scala new file mode 100644 index 000000000..e584a7dfd --- /dev/null +++ b/src/main/scala/gitbucket/core/api/UpdateAUser.scala @@ -0,0 +1,11 @@ +package gitbucket.core.api + +case class UpdateAUser( + name: String, + email: String, + blog: String, + company: String, + location: String, + hireable: Boolean, + bio: String +) diff --git a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala index dc708d9c4..eef2c68ef 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala @@ -1,12 +1,13 @@ package gitbucket.core.controller.api -import gitbucket.core.api.{ApiUser, CreateAUser, JsonFormat} +import gitbucket.core.api.{ApiUser, CreateAUser, JsonFormat, UpdateAUser} import gitbucket.core.controller.ControllerBase import gitbucket.core.service.{AccountService, RepositoryService} -import gitbucket.core.util.AdminAuthenticator +import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator} import gitbucket.core.util.Implicits._ +import org.scalatra.NoContent trait ApiUserControllerBase extends ControllerBase { - self: RepositoryService with AccountService with AdminAuthenticator => + self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator => /** * i. Get a single user @@ -33,6 +34,18 @@ trait ApiUserControllerBase extends ControllerBase { * iii. Update the authenticated user * https://developer.github.com/v3/users/#update-the-authenticated-user */ + patch("/api/v3/user")(usersOnly{ + (for{ + data <- extractFromJsonBody[UpdateAUser] + } yield { + val updatedAccount = context.loginAccount.get.copy( + mailAddress = data.email, + description = Some(data.bio) + ) + updateAccount(updatedAccount) + JsonFormat(ApiUser(updatedAccount)) + }) + }) /* * iv. Get contextual information about a user @@ -43,9 +56,12 @@ trait ApiUserControllerBase extends ControllerBase { * v. Get all users * https://developer.github.com/v3/users/#get-all-users */ + get("/api/v3/users") { + JsonFormat(getAllUsers(false, false).map(a => ApiUser(a))) + } /* - * ghe: i. Create a new use + * ghe: i. Create a new user * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#create-a-new-user */ post("/api/v3/admin/users")(adminOnly { @@ -69,8 +85,30 @@ trait ApiUserControllerBase extends ControllerBase { * ghe: vii. Suspend a user * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#suspend-a-user */ + put("/api/v3/users/:userName/suspended")(adminOnly{ + val userName = params("userName") + getAccountByUserName(userName) match { + case Some(targetAccount) => + removeUserRelatedData(userName) + updateAccount(targetAccount.copy(isRemoved = true)) + NoContent() + case None => + NotFound() + } + }) + /* - * ghe: vii. Unsuspend a user - * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#unsuspend-a-user - */ + * ghe: vii. Unsuspend a user + * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#unsuspend-a-user + */ + delete("/api/v3/users/:userName/suspended")(adminOnly{ + val userName = params("userName") + getAccountByUserName(userName) match { + case Some(targetAccount) => + updateAccount(targetAccount.copy(isRemoved = false)) + NoContent() + case None => + NotFound() + } + }) } From a66041de88ae6f9675b1baceea3971db0446f71b Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 13 Oct 2018 10:24:49 +0900 Subject: [PATCH 4/9] CRLF->LF --- .../gitbucket/core/api/UpdateAUser.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/scala/gitbucket/core/api/UpdateAUser.scala b/src/main/scala/gitbucket/core/api/UpdateAUser.scala index e584a7dfd..25c917b67 100644 --- a/src/main/scala/gitbucket/core/api/UpdateAUser.scala +++ b/src/main/scala/gitbucket/core/api/UpdateAUser.scala @@ -1,11 +1,11 @@ -package gitbucket.core.api - -case class UpdateAUser( - name: String, - email: String, - blog: String, - company: String, - location: String, - hireable: Boolean, - bio: String -) +package gitbucket.core.api + +case class UpdateAUser( + name: String, + email: String, + blog: String, + company: String, + location: String, + hireable: Boolean, + bio: String +) From c5760bd378521ab1134380d801276f70d9d5be59 Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 13 Oct 2018 10:26:01 +0900 Subject: [PATCH 5/9] scalafmt --- .../core/controller/api/ApiUserControllerBase.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala index eef2c68ef..009f298bb 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala @@ -34,8 +34,8 @@ trait ApiUserControllerBase extends ControllerBase { * iii. Update the authenticated user * https://developer.github.com/v3/users/#update-the-authenticated-user */ - patch("/api/v3/user")(usersOnly{ - (for{ + patch("/api/v3/user")(usersOnly { + (for { data <- extractFromJsonBody[UpdateAUser] } yield { val updatedAccount = context.loginAccount.get.copy( @@ -85,7 +85,7 @@ trait ApiUserControllerBase extends ControllerBase { * ghe: vii. Suspend a user * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#suspend-a-user */ - put("/api/v3/users/:userName/suspended")(adminOnly{ + put("/api/v3/users/:userName/suspended")(adminOnly { val userName = params("userName") getAccountByUserName(userName) match { case Some(targetAccount) => @@ -101,7 +101,7 @@ trait ApiUserControllerBase extends ControllerBase { * ghe: vii. Unsuspend a user * https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#unsuspend-a-user */ - delete("/api/v3/users/:userName/suspended")(adminOnly{ + delete("/api/v3/users/:userName/suspended")(adminOnly { val userName = params("userName") getAccountByUserName(userName) match { case Some(targetAccount) => From ac00c03a9636727b7c46d676dd203a4d7e901b6a Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 13 Oct 2018 10:48:10 +0900 Subject: [PATCH 6/9] modify fields to option --- .../scala/gitbucket/core/api/UpdateAUser.scala | 14 +++++++------- .../controller/api/ApiUserControllerBase.scala | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/scala/gitbucket/core/api/UpdateAUser.scala b/src/main/scala/gitbucket/core/api/UpdateAUser.scala index 25c917b67..1981c78b5 100644 --- a/src/main/scala/gitbucket/core/api/UpdateAUser.scala +++ b/src/main/scala/gitbucket/core/api/UpdateAUser.scala @@ -1,11 +1,11 @@ package gitbucket.core.api case class UpdateAUser( - name: String, - email: String, - blog: String, - company: String, - location: String, - hireable: Boolean, - bio: String + name: Option[String], + email: Option[String], + blog: Option[String], + company: Option[String], + location: Option[String], + hireable: Option[Boolean], + bio: Option[String] ) diff --git a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala index 009f298bb..4b696212f 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala @@ -38,9 +38,9 @@ trait ApiUserControllerBase extends ControllerBase { (for { data <- extractFromJsonBody[UpdateAUser] } yield { - val updatedAccount = context.loginAccount.get.copy( - mailAddress = data.email, - description = Some(data.bio) + val loginAccount = context.loginAccount.get + val updatedAccount = loginAccount.copy( + mailAddress = data.email.getOrElse(loginAccount.mailAddress) ) updateAccount(updatedAccount) JsonFormat(ApiUser(updatedAccount)) From 90f0006bc0dc109935e1cf45fa35daff56acb3f3 Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 13 Oct 2018 17:16:35 +0900 Subject: [PATCH 7/9] implement PullRequest related APIs --- .../core/api/CreateAPullRequest.scala | 16 ++ .../controller/PullRequestsController.scala | 163 +++++++---------- .../api/ApiPullRequestControllerBase.scala | 170 +++++++++++++----- .../core/service/IssuesService.scala | 9 + .../core/service/PullRequestService.scala | 54 ++++++ 5 files changed, 269 insertions(+), 143 deletions(-) create mode 100644 src/main/scala/gitbucket/core/api/CreateAPullRequest.scala 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 000000000..aa253b80e --- /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 c505fba9d..6d7262f10 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.Implicits._ 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 @@ trait PullRequestsControllerBase extends ControllerBase { .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 @@ trait PullRequestsControllerBase extends ControllerBase { 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 d2d835799..b4c45bcba 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 @@ package gitbucket.core.controller.api 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 @@ trait ApiPullRequestControllerBase extends ControllerBase { * 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 @@ trait ApiPullRequestControllerBase extends ControllerBase { * 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 @@ trait ApiPullRequestControllerBase extends ControllerBase { * 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 @@ trait ApiPullRequestControllerBase extends ControllerBase { * 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 @@ trait ApiPullRequestControllerBase extends ControllerBase { * 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 @@ trait ApiPullRequestControllerBase extends ControllerBase { */ /* - * 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 79223bf09..29665bd8e 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -526,6 +526,15 @@ trait IssuesService { .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 8612d7452..829704842 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.{CommitComments => _, Session => _, _} 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.util.JGitUtil.{CommitInfo, DiffInfo} 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 @@ trait PullRequestService { self: IssuesService with CommitsService => 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 { From edf0903620745d7a64268357054e52299c408fcc Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 13 Oct 2018 23:47:45 +0900 Subject: [PATCH 8/9] scalafmt --- .../controller/api/ApiPullRequestControllerBase.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala index b4c45bcba..13cc75d26 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala @@ -198,14 +198,14 @@ trait ApiPullRequestControllerBase extends ControllerBase { * 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{ + 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) { + if (checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) { NoContent - }else{ + } else { NotFound } }).getOrElse(NotFound) From de0bdac66b8358bb636e0e0360aaa82d3482179a Mon Sep 17 00:00:00 2001 From: KOUNOIKE Date: Sat, 8 Dec 2018 19:53:32 +0900 Subject: [PATCH 9/9] fix inherit error --- src/main/scala/gitbucket/core/controller/ApiController.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 7e2ef3932..1cf08af2d 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -33,6 +33,7 @@ class ApiController with RepositoryCreationService with IssueCreationService with HandleCommentService + with MergeService with WebHookService with WebHookPullRequestService with WebHookIssueCommentService