Fix refs API as far as Jenkins github-branch-source plugin can detect tags (#2936)

Co-authored-by: Thomas Geier <thomas.geier@solidat.de>
This commit is contained in:
Naoki Takezoe
2021-12-06 01:06:59 +09:00
committed by GitHub
parent 6ab37fd596
commit aba428bba1
8 changed files with 195 additions and 69 deletions

View File

@@ -1,5 +1,49 @@
package gitbucket.core.api
case class ApiObject(sha: String)
import gitbucket.core.util.JGitUtil.TagInfo
import gitbucket.core.util.RepositoryName
import org.eclipse.jgit.lib.Ref
case class ApiRef(ref: String, `object`: ApiObject)
case class ApiRefCommit(
sha: String,
`type`: String,
url: ApiPath
)
case class ApiRef(
ref: String,
node_id: String = "",
url: ApiPath,
`object`: ApiRefCommit,
)
object ApiRef {
def fromRef(
repositoryName: RepositoryName,
ref: Ref
): ApiRef =
ApiRef(
ref = ref.getName,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/${ref.getName}"),
`object` = ApiRefCommit(
sha = ref.getObjectId.getName,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${ref.getObjectId.getName}"),
`type` = "commit"
)
)
def fromTag(
repositoryName: RepositoryName,
tagInfo: TagInfo
): ApiRef =
ApiRef(
ref = s"refs/tags/${tagInfo.name}",
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/refs/tags/${tagInfo.name}"),
`object` = ApiRefCommit(
sha = tagInfo.id,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/tags/${tagInfo.id}"), // TODO This URL is not yet available?
`type` = "commit"
)
)
}

View File

@@ -1,29 +0,0 @@
package gitbucket.core.api
import gitbucket.core.util.RepositoryName
case class ApiTagCommit(
sha: String,
url: ApiPath
)
case class ApiTag(
name: String,
commit: ApiTagCommit,
zipball_url: ApiPath,
tarball_url: ApiPath
)
object ApiTag {
def apply(
tagName: String,
repositoryName: RepositoryName,
commitId: String
): ApiTag =
ApiTag(
name = tagName,
commit = ApiTagCommit(sha = commitId, url = ApiPath(s"/${repositoryName.fullName}/commits/${commitId}")),
zipball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.zip"),
tarball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.tar.gz")
)
}

View File

@@ -1,9 +1,10 @@
package gitbucket.core.controller.api
import gitbucket.core.api.{ApiObject, ApiRef, CreateARef, JsonFormat, UpdateARef}
import gitbucket.core.api.{ApiRef, CreateARef, JsonFormat, UpdateARef}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.ReferrerAuthenticator
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.RefUpdate.Result
@@ -23,37 +24,43 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#get-a-reference
*/
get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository =>
getRef()
val revstr = multiParams("splat").head
getRef(revstr, repository)
})
// Some versions of GHE support this path
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
logger.warn("git/refs/ endpoint may not be compatible with GitHub API v3. Consider using git/ref/ endpoint instead")
getRef()
val revstr = multiParams("splat").head
getRef(revstr, repository)
})
private def getRef() = {
val revstr = multiParams("splat").head
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val ref = git.getRepository().findRef(revstr)
protected def getRef(revstr: String, repository: RepositoryInfo): String = {
logger.debug(s"getRef: path '${revstr}'")
if (ref != null) {
val sha = ref.getObjectId().name()
JsonFormat(ApiRef(revstr, ApiObject(sha)))
val name = RepositoryName(repository)
val result = JsonFormat(revstr match {
case tags if tags == "tags" =>
repository.tags.map(ApiRef.fromTag(name, _))
case other =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git.getRepository().findRef(other) match {
case null =>
val refs = git
.getRepository()
.getRefDatabase()
.getRefsByPrefix("refs/")
.asScala
} else {
val refs = git
.getRepository()
.getRefDatabase()
.getRefsByPrefix("refs/")
.asScala
refs.map(ApiRef.fromRef(name, _))
case hit =>
ApiRef.fromRef(name, hit)
}
}
})
JsonFormat(refs.map { ref =>
val sha = ref.getObjectId().name()
ApiRef(revstr, ApiObject(sha))
})
}
}
logger.debug(s"json result: $result")
result
}
/*
@@ -65,17 +72,17 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* iii. Create a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
*/
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { _ =>
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
extractFromJsonBody[CreateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
val ref = git.getRepository.findRef(data.ref)
if (ref == null) {
val update = git.getRepository.updateRef(data.ref)
update.setNewObjectId(ObjectId.fromString(data.sha))
val result = update.update()
result match {
case Result.NEW => JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
case Result.NEW => JsonFormat(ApiRef.fromRef(RepositoryName(repository.owner, repository.name), ref))
case _ => UnprocessableEntity(result.name())
}
} else {
@@ -89,11 +96,11 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* iv. Update a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
*/
patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
val refName = multiParams("splat").mkString("/")
extractFromJsonBody[UpdateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
val ref = git.getRepository.findRef(refName)
if (ref == null) {
UnprocessableEntity("Ref does not exist.")
@@ -104,7 +111,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
val result = update.update()
result match {
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef))
case _ => UnprocessableEntity(result.name())
}
}
@@ -116,7 +123,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* v. Delete a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#delete-a-reference
*/
delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
val refName = multiParams("splat").mkString("/")
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val ref = git.getRepository.findRef(refName)

View File

@@ -16,6 +16,7 @@ import scala.util.Using
trait ApiRepositoryControllerBase extends ControllerBase {
self: RepositoryService
with ApiGitReferenceControllerBase
with RepositoryCreationService
with AccountService
with OwnerAuthenticator
@@ -184,9 +185,11 @@ trait ApiRepositoryControllerBase extends ControllerBase {
* https://docs.github.com/en/rest/reference/repos#list-repository-tags
*/
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
JsonFormat(
repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id))
)
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JsonFormat(
self.getRef("tags", repository)
)
}
})
/*

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>gitbucket.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
-->
<logger name="gitbucket" level="DEBUG"/>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
<!--
<logger name="service.WebHookService" level="DEBUG" />
<logger name="servlet" level="DEBUG" />
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
-->
</configuration>

View File

@@ -134,6 +134,24 @@ class ApiIntegrationTest extends AnyFunSuite {
assert(statusList.get(1).getState == GHCommitState.FAILURE)
assert(statusList.get(1).getContext == "context")
}
// get master ref
{
val ref = repo.getRef("heads/master")
assert(ref.getRef == "refs/heads/master")
assert(
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/master"
)
assert(ref.getObject.getType == "commit")
}
// // get tag v1.0
// {
// val ref = repo.getRef("heads/tags/v1.0")
// assert(ref.getRef == "heads/tags/v1.0")
// assert(ref.getUrl == "tbd")
// assert(ref.getObject.getType == "tag")
// }
}
}

View File

@@ -432,9 +432,29 @@ object ApiSpecModels {
val apiPusher = ApiPusher(account)
val apiRef = ApiRef(
ref = "refs/heads/featureA",
`object` = ApiObject(sha1)
//have both urls as https, as the expected samples are using https
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
val apiRefHeadsMaster = ApiRef(
ref = "refs/heads/master",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/master"),
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
`object` = ApiRefCommit(
sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d",
`type` = "commit",
url = ApiPath("/repos/gitbucket/gitbucket/git/commits/6b2d124d092402f2c2b7131caada05ead9e7de6d")
)
)
val apiRefTag = ApiRef(
ref = "refs/tags/1.0",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/tags/1.0"),
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
`object` = ApiRefCommit(
sha = "1f164ecf2f59190afc8d7204a221c739e707df4c",
`type` = "tag",
url = ApiPath("/repos/gitbucket/gitbucket/git/tags/1f164ecf2f59190afc8d7204a221c739e707df4c")
)
)
val assetFileName = "010203040a0b0c0d"
@@ -765,8 +785,33 @@ object ApiSpecModels {
val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}"""
//I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag"
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonRefHeadsMaster =
"""{
|"ref": "refs/heads/master",
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/master",
|"object": {
|"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|"type": "commit",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/commits/6b2d124d092402f2c2b7131caada05ead9e7de6d"
|}
|}""".stripMargin
val jsonRefTag =
"""{
|"ref": "refs/tags/1.0",
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/tags/1.0",
|"object": {
|"sha": "1f164ecf2f59190afc8d7204a221c739e707df4c",
|"type": "tag",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/tags/1f164ecf2f59190afc8d7204a221c739e707df4c"
|}
|}""".stripMargin
val jsonReleaseAsset =
s"""{
|"name":"release.zip",

View File

@@ -8,6 +8,12 @@ class JsonFormatSpec extends AnyFunSuite {
implicit val format = JsonFormat.jsonFormats
private def expected(json: String) = json.replaceAll("\n", "")
def normalizeJson(json: String) = {
org.json4s.jackson.parseJson(json)
}
def assertEqualJson(actual: String, expected: String) = {
assert(normalizeJson(actual) == normalizeJson(expected))
}
test("apiUser") {
assert(JsonFormat(apiUser) == expected(jsonUser))
@@ -76,8 +82,11 @@ class JsonFormatSpec extends AnyFunSuite {
test("apiPusher") {
assert(JsonFormat(apiPusher) == expected(jsonPusher))
}
test("apiRef") {
assert(JsonFormat(apiRef) == expected(jsonRef))
test("apiRefHead") {
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster)
}
test("apiRefTag") {
assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag)
}
test("apiReleaseAsset") {
assert(JsonFormat(apiReleaseAsset) == expected(jsonReleaseAsset))