diff --git a/src/main/scala/gitbucket/core/api/ApiRef.scala b/src/main/scala/gitbucket/core/api/ApiRef.scala index 01a9785..97c5d41 100644 --- a/src/main/scala/gitbucket/core/api/ApiRef.scala +++ b/src/main/scala/gitbucket/core/api/ApiRef.scala @@ -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" + ) + ) +} diff --git a/src/main/scala/gitbucket/core/api/ApiTag.scala b/src/main/scala/gitbucket/core/api/ApiTag.scala deleted file mode 100644 index 46fd25a..0000000 --- a/src/main/scala/gitbucket/core/api/ApiTag.scala +++ /dev/null @@ -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") - ) -} diff --git a/src/main/scala/gitbucket/core/controller/api/ApiGitReferenceControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiGitReferenceControllerBase.scala index 516b433..e4a3c50 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiGitReferenceControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiGitReferenceControllerBase.scala @@ -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 @@ * 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 @@ * 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 @@ * 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 @@ 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 @@ * 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) diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryControllerBase.scala index 799474b..44db6d7 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryControllerBase.scala @@ -16,6 +16,7 @@ trait ApiRepositoryControllerBase extends ControllerBase { self: RepositoryService + with ApiGitReferenceControllerBase with RepositoryCreationService with AccountService with OwnerAuthenticator @@ -184,9 +185,11 @@ * 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) + ) + } }) /* diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..3b7a89d --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,29 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/src/test/scala/gitbucket/core/api/ApiIntegrationTest.scala b/src/test/scala/gitbucket/core/api/ApiIntegrationTest.scala index 3bfba43..747ae03 100644 --- a/src/test/scala/gitbucket/core/api/ApiIntegrationTest.scala +++ b/src/test/scala/gitbucket/core/api/ApiIntegrationTest.scala @@ -134,6 +134,24 @@ 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") +// } } } diff --git a/src/test/scala/gitbucket/core/api/ApiSpecModels.scala b/src/test/scala/gitbucket/core/api/ApiSpecModels.scala index 7511382..daa73b8 100644 --- a/src/test/scala/gitbucket/core/api/ApiSpecModels.scala +++ b/src/test/scala/gitbucket/core/api/ApiSpecModels.scala @@ -432,9 +432,29 @@ 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 @@ 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", diff --git a/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala b/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala index 484ce7c..95c4cfb 100644 --- a/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala +++ b/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala @@ -8,6 +8,12 @@ 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 @@ 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))