Skip to content

Commit 0b01fb7

Browse files
Support retrieving a blob (#494)
1 parent 150230a commit 0b01fb7

8 files changed

Lines changed: 110 additions & 1 deletion

File tree

docs/docs/git_data.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ with Github4s, you can:
1414
- [Update a Reference](#update-a-reference)
1515
- [Get a Commit](#get-a-commit)
1616
- [Create a Commit](#create-a-commit)
17+
- [Get a Blob](#get-a-blob)
1718
- [Create a Blob](#create-a-blob)
1819
- [Get a Tree](#get-a-tree)
1920
- [Create a Tree](#create-a-tree)
@@ -191,6 +192,26 @@ See [the API doc](https://developer.github.com/v3/git/commits/#create-a-commit)
191192

192193
## Blobs
193194

195+
### Get a Blob
196+
197+
You can get a blob using `getBlob`; it takes as arguments:
198+
199+
- the repository coordinates (`owner` and `name` of the repository).
200+
- `sha`: the sha of the blob.
201+
202+
```scala mdoc:compile-only
203+
val getBlob = gh.gitData.getBlob("47degrees", "github4s", "d3b048c1f500ee5450e5d7b3d1921ed3e7645891")
204+
val response = getBlob.unsafeRunSync()
205+
response.result match {
206+
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
207+
case Right(r) => println(r)
208+
}
209+
```
210+
211+
The `result` on the right is the corresponding [BlobContent][gitdata-scala].
212+
213+
See [the API doc](https://developer.github.com/v3/git/blobs/#get-a-blob) for full reference.
214+
194215
### Create a Blob
195216

196217
You can create a blob using `createBlob`; it takes as arguments:

github4s/src/main/scala/github4s/Decoders.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ object Decoders {
265265
implicit val decoderCombinedStatus: Decoder[CombinedStatus] = deriveDecoder[CombinedStatus]
266266
implicit val decoderLabel: Decoder[Label] = deriveDecoder[Label]
267267
implicit val decoderContent: Decoder[Content] = deriveDecoder[Content]
268+
implicit val decoderBlobContent: Decoder[BlobContent] = deriveDecoder[BlobContent]
268269
implicit val decoderSubscription: Decoder[Subscription] = deriveDecoder[Subscription]
269270
implicit val decoderAuthorization: Decoder[Authorization] = deriveDecoder[Authorization]
270271
implicit val decoderOAuthToken: Decoder[OAuthToken] = deriveDecoder[OAuthToken]

github4s/src/main/scala/github4s/algebras/GitData.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,22 @@ trait GitData[F[_]] {
130130
headers: Map[String, String] = Map()
131131
): F[GHResponse[RefCommit]]
132132

133+
/**
134+
* Get a Blob by sha
135+
*
136+
* @param owner of the repo
137+
* @param repo name of the repo
138+
* @param fileSha to identify the blob
139+
* @param headers optional user headers to include in the request
140+
* @return a GHResponse with BlobContent
141+
*/
142+
def getBlob(
143+
owner: String,
144+
repo: String,
145+
fileSha: String,
146+
headers: Map[String, String] = Map()
147+
): F[GHResponse[BlobContent]]
148+
133149
/**
134150
* Create a new Blob
135151
*

github4s/src/main/scala/github4s/domain/GitData.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ final case class NewCommitRequest(
8383
author: Option[RefAuthor]
8484
)
8585

86+
final case class BlobContent(
87+
content: Option[String],
88+
encoding: Option[String],
89+
url: String,
90+
sha: String,
91+
size: Int
92+
)
93+
8694
final case class NewBlobRequest(content: String, encoding: Option[String])
8795

8896
final case class NewTreeRequest(base_tree: Option[String], tree: List[TreeData])

github4s/src/main/scala/github4s/interpreters/GitDataInterpreter.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ class GitDataInterpreter[F[_]](implicit client: HttpClient[F], accessToken: Opti
9393
NewCommitRequest(message, tree, parents, author)
9494
)
9595

96+
override def getBlob(
97+
owner: String,
98+
repo: String,
99+
fileSha: String,
100+
headers: Map[String, String]
101+
): F[GHResponse[BlobContent]] =
102+
client.get[BlobContent](accessToken, s"repos/$owner/$repo/git/blobs/$fileSha", headers)
103+
96104
override def createBlob(
97105
owner: String,
98106
repo: String,

github4s/src/test/scala/github4s/integration/ReposSpec.scala

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
package github4s.integration
1818

19-
import cats.effect.IO
2019
import cats.data.NonEmptyList
20+
import cats.effect.{IO, Resource}
2121
import github4s.GHError.NotFoundError
2222
import github4s.Github
2323
import github4s.domain._
@@ -133,6 +133,44 @@ trait ReposSpec extends BaseIntegrationSpec {
133133
response.statusCode shouldBe okStatusCode
134134
}
135135

136+
"Repos >> GetContents" should "have the same contents with getBlob using fileSha" taggedAs Integration in {
137+
138+
val blobResponseFileContent = for {
139+
client <- clientResource
140+
res = Github[IO](client, accessToken)
141+
142+
fileContentsIO = res.repos.getContents(
143+
owner = validRepoOwner,
144+
repo = validRepoName,
145+
path = validFilePath,
146+
headers = headerUserAgent
147+
)
148+
149+
fileContentsResponse <- Resource.liftF(fileContentsIO)
150+
151+
fileContentsEither = fileContentsResponse.result
152+
153+
fileContents <- Resource.liftF(IO.fromEither(fileContentsEither))
154+
155+
blobContentIO = res.gitData.getBlob(
156+
owner = validRepoOwner,
157+
repo = validRepoName,
158+
fileSha = fileContents.head.sha,
159+
headers = headerUserAgent
160+
)
161+
162+
blobContentResponse <- Resource.liftF(blobContentIO)
163+
164+
} yield (blobContentResponse, fileContents.head)
165+
166+
val (blobContentResponse, fileContent) = blobResponseFileContent
167+
.use(a => IO.apply(a))
168+
.unsafeRunSync()
169+
170+
testIsRight[BlobContent](blobContentResponse, _.content.shouldBe(fileContent.content))
171+
blobContentResponse.statusCode shouldBe okStatusCode
172+
}
173+
136174
it should "return error when an invalid path is passed" taggedAs Integration in {
137175
val response = clientResource
138176
.use { client =>

github4s/src/test/scala/github4s/unit/GitDataSpec.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,22 @@ class GitDataSpec extends BaseSpec {
144144

145145
}
146146

147+
"GitData.getBlob" should "call to httpClient.post with the right parameters" in {
148+
149+
val response: IO[GHResponse[BlobContent]] =
150+
IO(
151+
GHResponse(BlobContent(None, None, "", invalidFileSha, 0).asRight, okStatusCode, Map.empty)
152+
)
153+
154+
implicit val httpClientMock = httpClientMockGet[BlobContent](
155+
url = s"repos/$validRepoOwner/$validRepoName/git/blobs/$invalidFileSha",
156+
response = response
157+
)
158+
val gitData = new GitDataInterpreter[IO]
159+
gitData.getBlob(validRepoOwner, validRepoName, invalidFileSha, headerUserAgent)
160+
161+
}
162+
147163
"GitData.getTree" should "call to httpClient.get with the right parameters" in {
148164

149165
val response: IO[GHResponse[TreeResult]] =

github4s/src/test/scala/github4s/utils/TestData.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ trait TestData {
113113

114114
val validTreeSha = "827efc6d56897b048c772eb4087f854f46256132"
115115
val invalidTreeSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
116+
val invalidFileSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
116117

117118
val validTagTitle = "v0.1.1"
118119
val validTagSha = "c3d0be41ecbe669545ee3e94d31ed9a4bc91ee3c"

0 commit comments

Comments
 (0)