diff --git a/github4s/src/main/scala/github4s/Decoders.scala b/github4s/src/main/scala/github4s/Decoders.scala index 62d70541..8140db69 100644 --- a/github4s/src/main/scala/github4s/Decoders.scala +++ b/github4s/src/main/scala/github4s/Decoders.scala @@ -368,6 +368,15 @@ object Decoders { val issue = deriveDecoder[CreatePullRequestIssue] data.widen[CreatePullRequest] or issue.widen[CreatePullRequest] } + implicit val decoderPullRequestMergeMethod: Decoder[PullRequestMergeMethod] = + Decoder[String].emap { + case PRMergeMethodMerge.value => PRMergeMethodMerge.asRight + case PRMergeMethodRebase.value => PRMergeMethodRebase.asRight + case PRMergeMethodSquash.value => PRMergeMethodSquash.asRight + case other => s"Unknown pull request merge method: $other".asLeft + } + implicit val decoderPullRequestMergeResponse: Decoder[PullRequestMergeResponse] = + deriveDecoder[PullRequestMergeResponse] implicit val decoderCreateReferenceRequest: Decoder[CreateReferenceRequest] = deriveDecoder[CreateReferenceRequest] implicit val decoderDeleteFileRequest: Decoder[DeleteFileRequest] = diff --git a/github4s/src/main/scala/github4s/Encoders.scala b/github4s/src/main/scala/github4s/Encoders.scala index d1e33134..c44f9c62 100644 --- a/github4s/src/main/scala/github4s/Encoders.scala +++ b/github4s/src/main/scala/github4s/Encoders.scala @@ -42,12 +42,18 @@ object Encoders { } } + implicit val encoderPullRequestMergeRequest: Encoder[PullRequestMergeRequest] = + deriveEncoder[PullRequestMergeRequest] + implicit val encodePrrStatus: Encoder[PullRequestReviewState] = Encoder.encodeString.contramap(_.value) implicit val encodePrrEvent: Encoder[PullRequestReviewEvent] = Encoder.encodeString.contramap(_.value) + implicit val encodePrMergeMethod: Encoder[PullRequestMergeMethod] = + Encoder.encodeString.contramap(_.value) + implicit val encodeEditGistFile: Encoder[EditGistFile] = { deriveEncoder[EditGistFile].mapJsonObject( _.filter(e => !(e._1.equals("filename") && e._2.isNull)) diff --git a/github4s/src/main/scala/github4s/GHError.scala b/github4s/src/main/scala/github4s/GHError.scala index be603de6..d71237d9 100644 --- a/github4s/src/main/scala/github4s/GHError.scala +++ b/github4s/src/main/scala/github4s/GHError.scala @@ -141,6 +141,30 @@ object GHError { deriveDecoder[NotFoundError] } + /** + * Corresponds to a 405 status code + * @param message that was given in the response body + */ + final case class MethodNotAllowed(message: String) extends GHError(message) { + override def toString: String = s"MethodNotAllowed($message)" + } + object MethodNotAllowed { + private[github4s] implicit val methodNotAllowedDecoder: Decoder[MethodNotAllowed] = + deriveDecoder[MethodNotAllowed] + } + + /** + * Corresponds to a 409 status code + * @param message that was given in the response body + */ + final case class Conflict(message: String) extends GHError(message) { + override def toString: String = s"Conflict($message)" + } + object Conflict { + private[github4s] implicit val conflictDecoder: Decoder[Conflict] = + deriveDecoder[Conflict] + } + sealed trait ErrorCode object ErrorCode { case object MissingResource extends ErrorCode diff --git a/github4s/src/main/scala/github4s/algebras/PullRequests.scala b/github4s/src/main/scala/github4s/algebras/PullRequests.scala index f75d9b20..e0e1d822 100644 --- a/github4s/src/main/scala/github4s/algebras/PullRequests.scala +++ b/github4s/src/main/scala/github4s/algebras/PullRequests.scala @@ -104,6 +104,29 @@ trait PullRequests[F[_]] { headers: Map[String, String] = Map() ): F[GHResponse[PullRequest]] + /** + * Merge a pull request + * + * @param owner Owner of the repo + * @param repo Name of the repo + * @param number of the pull request for which we want to merge + * @param commitTitle Title for the automatic commit message + * @param commitMessage Extra detail to append to automatic commit message + * @param sha SHA that pull request head must match to allow merge + * @param mergeMethod Merge method to use. Possible values are merge, squash or rebase. Default is merge. + * @param headers Optional user headers to include in the request + */ + def mergePullRequest( + owner: String, + repo: String, + number: Long, + commitTitle: Option[String] = None, + commitMessage: Option[String] = None, + sha: Option[String] = None, + mergeMethod: Option[PullRequestMergeMethod] = None, + headers: Map[String, String] = Map() + ): F[GHResponse[PullRequestMergeResponse]] + /** * List pull request reviews. * diff --git a/github4s/src/main/scala/github4s/domain/PullRequest.scala b/github4s/src/main/scala/github4s/domain/PullRequest.scala index aa87d49c..33bf44f3 100644 --- a/github4s/src/main/scala/github4s/domain/PullRequest.scala +++ b/github4s/src/main/scala/github4s/domain/PullRequest.scala @@ -78,6 +78,15 @@ final case class CreatePullRequestIssue( maintainer_can_modify: Option[Boolean] = Some(true) ) extends CreatePullRequest +final case class PullRequestMergeRequest( + commit_title: Option[String], + commit_message: Option[String], + sha: Option[String], + merge_method: Option[PullRequestMergeMethod] +) + +final case class PullRequestMergeResponse(sha: String, merged: Boolean, message: String) + sealed abstract class PRFilter(val name: String, val value: String) extends Product with Serializable { @@ -154,3 +163,9 @@ final case class ReviewersResponse( final case class BranchUpdateRequest(expected_head_sha: Option[String]) final case class BranchUpdateResponse(message: String, url: String) + +sealed abstract class PullRequestMergeMethod(val value: String) extends Product with Serializable + +case object PRMergeMethodMerge extends PullRequestMergeMethod("merge") +case object PRMergeMethodSquash extends PullRequestMergeMethod("squash") +case object PRMergeMethodRebase extends PullRequestMergeMethod("rebase") diff --git a/github4s/src/main/scala/github4s/http/HttpClient.scala b/github4s/src/main/scala/github4s/http/HttpClient.scala index 8ed94ed7..44b362c9 100644 --- a/github4s/src/main/scala/github4s/http/HttpClient.scala +++ b/github4s/src/main/scala/github4s/http/HttpClient.scala @@ -208,6 +208,8 @@ object HttpClient { case 401 => response.attemptAs[UnauthorizedError].map(_.asLeft) case 403 => response.attemptAs[ForbiddenError].map(_.asLeft) case 404 => response.attemptAs[GHError](notFoundEntityDecoder).map(_.asLeft) + case 405 => response.attemptAs[MethodNotAllowed].map(_.asLeft) + case 409 => response.attemptAs[Conflict].map(_.asLeft) case 422 => response.attemptAs[UnprocessableEntityError].map(_.asLeft) case 423 => response.attemptAs[RateLimitExceededError].map(_.asLeft) case _ => diff --git a/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala b/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala index 6535fafc..3aef541e 100644 --- a/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala +++ b/github4s/src/main/scala/github4s/interpreters/PullRequestsInterpreter.scala @@ -81,6 +81,30 @@ class PullRequestsInterpreter[F[_]](implicit client: HttpClient[F]) extends Pull .post[CreatePullRequest, PullRequest](s"repos/$owner/$repo/pulls", headers, data) } + override def mergePullRequest( + owner: String, + repo: String, + number: Long, + commitTitle: Option[String], + commitMessage: Option[String], + sha: Option[String], + mergeMethod: Option[PullRequestMergeMethod], + headers: Map[String, String] + ) = { + val request = PullRequestMergeRequest( + commitTitle, + commitMessage, + sha, + mergeMethod + ) + client + .put[PullRequestMergeRequest, PullRequestMergeResponse]( + s"/repos/$owner/$repo/pulls/$number/merge", + headers, + request + ) + } + override def listReviews( owner: String, repo: String,