From bcc68f78e4fea088ffa3518cbabad5f0850622bc Mon Sep 17 00:00:00 2001 From: Rey Abe Date: Tue, 14 Aug 2018 15:48:31 +0900 Subject: [PATCH] add get tree api implementation --- docs/src/main/tut/git_data.md | 23 ++++++++++++ .../src/main/scala/github4s/GithubAPIs.scala | 8 +++++ .../src/main/scala/github4s/api/GitData.scala | 19 ++++++++++ .../github4s/free/algebra/GitDataOps.scala | 17 +++++++++ .../scala/github4s/free/domain/GitData.scala | 3 +- .../free/interpreters/Interpreters.scala | 2 ++ .../github4s/integration/GHGitDataSpec.scala | 36 ++++++++++++++++++- .../scala/github4s/unit/GHGitDataSpec.scala | 19 ++++++++++ .../scala/github4s/unit/GitDataSpec.scala | 19 ++++++++++ 9 files changed, 144 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/git_data.md b/docs/src/main/tut/git_data.md index 64eeaf3af..7bdf7754d 100644 --- a/docs/src/main/tut/git_data.md +++ b/docs/src/main/tut/git_data.md @@ -14,6 +14,7 @@ with Github4s, you can: - [Get a Commit](#get-a-commit) - [Create a Commit](#create-a-commit) - [Create a Blob](#create-a-blob) +- [Get a Tree](#get-a-tree) - [Create a Tree](#create-a-tree) - [Create a Tag](#create-a-tag) @@ -208,6 +209,28 @@ are just tools to navigate this tree and to manipulate it. In the following sections, we'll see how Github4s provides methods to wrap the Git API. + +### Get a Tree + +You can get a tree using `getTree`; it takes as arguments: + +- the repository coordinates (`owner` and `name` of the repository). +- `sha`: the sha of the commit. +- `recursive`: flag whether to get the tree recursively. + +```tut:silent +val getCommit = Github(accessToken).gitData.getTree("47deg", "github4s", "d3b048c1f500ee5450e5d7b3d1921ed3e7645891",true) + +getCommit.exec[cats.Id, HttpResponse[String]]() match { + case Left(e) => println(s"Something went wrong: ${e.getMessage}") + case Right(r) => println(r.result) +} +``` + +The `result` on the right is the corresponding [TreeResult][gitdata-scala]. + +See [the API doc](https://developer.github.com/v3/git/trees/#get-a-tree) for full reference. + ### Create a Tree The tree creation API will take nested entries as well. If both a tree and a nested entries modifying diff --git a/github4s/shared/src/main/scala/github4s/GithubAPIs.scala b/github4s/shared/src/main/scala/github4s/GithubAPIs.scala index e5ff33228..0be5ccf5c 100644 --- a/github4s/shared/src/main/scala/github4s/GithubAPIs.scala +++ b/github4s/shared/src/main/scala/github4s/GithubAPIs.scala @@ -330,6 +330,14 @@ class GHGitData(accessToken: Option[String] = None)(implicit O: GitDataOps[GitHu ): GHIO[GHResponse[RefInfo]] = O.createBlob(owner, repo, content, encoding, accessToken) + def getTree( + owner: String, + repo: String, + sha: String, + recursive: Boolean + ): GHIO[GHResponse[TreeResult]] = + O.getTree(owner, repo, sha, recursive, accessToken) + def createTree( owner: String, repo: String, diff --git a/github4s/shared/src/main/scala/github4s/api/GitData.scala b/github4s/shared/src/main/scala/github4s/api/GitData.scala index 95ca2d91e..028be46fe 100644 --- a/github4s/shared/src/main/scala/github4s/api/GitData.scala +++ b/github4s/shared/src/main/scala/github4s/api/GitData.scala @@ -191,6 +191,25 @@ class GitData[C, M[_]]( headers, dropNullPrint(NewBlobRequest(content, encoding).asJson)) + /** + * Get a Tree by sha + * + * @param accessToken to identify the authenticated user + * @param headers optional user headers to include in the request + * @param owner of the repo + * @param repo name of the repo + * @param sha the sha of the tree + * @return a GHResponse with the Tree + */ + def tree( + accessToken: Option[String] = None, + headers: Map[String, String] = Map(), + owner: String, + repo: String, + sha: String, + recursive: Boolean=false): M[GHResponse[TreeResult]] = + httpClient.get[TreeResult](accessToken, s"repos/$owner/$repo/git/trees/$sha" + (if (recursive) "?recursive=1" else ""), headers) + /** * Create a new Tree * diff --git a/github4s/shared/src/main/scala/github4s/free/algebra/GitDataOps.scala b/github4s/shared/src/main/scala/github4s/free/algebra/GitDataOps.scala index 30e57869e..de1ccff5c 100644 --- a/github4s/shared/src/main/scala/github4s/free/algebra/GitDataOps.scala +++ b/github4s/shared/src/main/scala/github4s/free/algebra/GitDataOps.scala @@ -76,6 +76,14 @@ final case class CreateBlob( accessToken: Option[String] = None ) extends GitDataOp[GHResponse[RefInfo]] +final case class GetTree( + owner: String, + repo: String, + sha: String, + recursive: Boolean, + accessToken: Option[String] = None +) extends GitDataOp[GHResponse[TreeResult]] + final case class CreateTree( owner: String, repo: String, @@ -156,6 +164,15 @@ class GitDataOps[F[_]](implicit I: InjectK[GitDataOp, F]) { ): Free[F, GHResponse[RefInfo]] = Free.inject[GitDataOp, F](CreateBlob(owner, repo, content, encoding, accessToken)) + def getTree( + owner: String, + repo: String, + sha: String, + recursive: Boolean, + accessToken: Option[String] = None + ): Free[F, GHResponse[TreeResult]] = + Free.inject[GitDataOp, F](GetTree(owner, repo, sha, recursive, accessToken)) + def createTree( owner: String, repo: String, diff --git a/github4s/shared/src/main/scala/github4s/free/domain/GitData.scala b/github4s/shared/src/main/scala/github4s/free/domain/GitData.scala index a20739f86..4ecdcc964 100644 --- a/github4s/shared/src/main/scala/github4s/free/domain/GitData.scala +++ b/github4s/shared/src/main/scala/github4s/free/domain/GitData.scala @@ -57,7 +57,8 @@ case class TreeDataBlob(path: String, mode: String, `type`: String, content: Str case class TreeResult( override val sha: String, override val url: String, - tree: List[TreeDataResult]) + tree: List[TreeDataResult], + truncated: Option[Boolean]=None) extends RefInfo(sha, url) case class TreeDataResult( diff --git a/github4s/shared/src/main/scala/github4s/free/interpreters/Interpreters.scala b/github4s/shared/src/main/scala/github4s/free/interpreters/Interpreters.scala index 5b0c78ef8..357ed5625 100644 --- a/github4s/shared/src/main/scala/github4s/free/interpreters/Interpreters.scala +++ b/github4s/shared/src/main/scala/github4s/free/interpreters/Interpreters.scala @@ -281,6 +281,8 @@ class Interpreters[M[_], C]( gitData.createBlob(accessToken, headers, owner, repo, content, encoding) case CreateTree(owner, repo, baseTree, treeDataList, accessToken) ⇒ gitData.createTree(accessToken, headers, owner, repo, baseTree, treeDataList) + case GetTree(owner, repo, sha, recursive, accessToken) ⇒ + gitData.tree(accessToken, headers, owner, repo, sha, recursive) case CreateTag(owner, repo, tag, message, objectSha, objectType, author, accessToken) ⇒ gitData.createTag( accessToken, diff --git a/github4s/shared/src/test/scala/github4s/integration/GHGitDataSpec.scala b/github4s/shared/src/test/scala/github4s/integration/GHGitDataSpec.scala index 8bfcd8de5..84452aea5 100644 --- a/github4s/shared/src/test/scala/github4s/integration/GHGitDataSpec.scala +++ b/github4s/shared/src/test/scala/github4s/integration/GHGitDataSpec.scala @@ -19,7 +19,7 @@ package github4s.integration import cats.data.NonEmptyList import github4s.Github import github4s.Github._ -import github4s.free.domain.{Ref, RefCommit} +import github4s.free.domain.{Ref, RefCommit, TreeResult} import github4s.implicits._ import github4s.utils.BaseIntegrationSpec @@ -73,4 +73,38 @@ trait GHGitDataSpec[T] extends BaseIntegrationSpec[T] { testFutureIsLeft(response) } + + "GitData >> GetTree" should "return the file tree non-recursively" in { + val response = + Github(accessToken).gitData + .getTree(validRepoOwner, validRepoName, validCommitSha, recursive=false) + .execFuture[T](headerUserAgent) + + testFutureIsRight[TreeResult](response, { r => + r.statusCode shouldBe okStatusCode + r.result.tree.map(_.path) shouldBe List(".gitignore","build.sbt","project") + r.result.truncated shouldBe Some(false) + }) + } + + it should "return the file tree recursively" in { + val response = + Github(accessToken).gitData + .getTree(validRepoOwner, validRepoName, validCommitSha, recursive=true) + .execFuture[T](headerUserAgent) + + testFutureIsRight[TreeResult](response, { r => + r.statusCode shouldBe okStatusCode + r.result.tree.map(_.path) shouldBe List(".gitignore", "build.sbt", "project", "project/build.properties", "project/plugins.sbt") + }) + } + + it should "return an error when an invalid repository name is passed" in { + val response = Github(accessToken).gitData + .getTree(validRepoOwner, invalidRepoName, validCommitSha, recursive=false) + .execFuture[T](headerUserAgent) + + testFutureIsLeft(response) + } + } diff --git a/github4s/shared/src/test/scala/github4s/unit/GHGitDataSpec.scala b/github4s/shared/src/test/scala/github4s/unit/GHGitDataSpec.scala index 475601273..c46d278ea 100644 --- a/github4s/shared/src/test/scala/github4s/unit/GHGitDataSpec.scala +++ b/github4s/shared/src/test/scala/github4s/unit/GHGitDataSpec.scala @@ -135,6 +135,25 @@ class GHGitDataSpec extends BaseSpec { ghGitData.createBlob(validRepoOwner, validRepoName, validNote, encoding) } + "GHGitData.getTree" should "call to GitDataOps with the right parameters" in { + + val response: Free[GitHub4s, GHResponse[TreeResult]] = + Free.pure( + Right( + GHResult( + TreeResult(validCommitSha, githubApiUrl, treeDataResult, truncated=Some(false)), + okStatusCode, + Map.empty))) + + val gitDataOps = mock[GitDataOpsTest] + (gitDataOps.getTree _) + .expects(validRepoOwner, validRepoName, validCommitSha, true, sampleToken) + .returns(response) + + val ghGitData = new GHGitData(sampleToken)(gitDataOps) + ghGitData.getTree(validRepoOwner, validRepoName, validCommitSha, recursive=true) + } + "GHGitData.createTree" should "call to GitDataOps with the right parameters" in { val response: Free[GitHub4s, GHResponse[TreeResult]] = diff --git a/github4s/shared/src/test/scala/github4s/unit/GitDataSpec.scala b/github4s/shared/src/test/scala/github4s/unit/GitDataSpec.scala index 989f1c570..b2c8118e9 100644 --- a/github4s/shared/src/test/scala/github4s/unit/GitDataSpec.scala +++ b/github4s/shared/src/test/scala/github4s/unit/GitDataSpec.scala @@ -182,6 +182,25 @@ class GitDataSpec extends BaseSpec { encoding) } + "GitData.getTree" should "call to httpClient.get with the right parameters" in { + + val response: GHResponse[TreeResult] = + Right( + GHResult( + TreeResult(validCommitSha, githubApiUrl, treeDataResult, truncated=Some(false)), + okStatusCode, + Map.empty)) + val httpClientMock = httpClientMockGet[TreeResult]( + url = s"repos/$validRepoOwner/$validRepoName/git/trees/$validCommitSha?recursive=1", + response = response + ) + val gitData = new GitData[String, Id] { + override val httpClient: HttpClient[String, Id] = httpClientMock + } + + gitData.tree(sampleToken, headerUserAgent, validRepoOwner, validRepoName, validCommitSha) + } + "GitData.createTree" should "call to httpClient.post with the right parameters" in { val response: GHResponse[TreeResult] =