mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-06-18 15:15:18 +00:00
API: new GET /repos/{owner}/{repo}/git/blobs
endpoint to retrieve multiple blobs at once (#8179)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8179 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
commit
07cc5802bf
8 changed files with 180 additions and 20 deletions
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
// GitBlobResponse represents a git blob
|
// GitBlob represents a git blob
|
||||||
type GitBlobResponse struct {
|
type GitBlob struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Encoding string `json:"encoding"`
|
Encoding string `json:"encoding"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
|
|
@ -1310,6 +1310,7 @@ func Routes() *web.Route {
|
||||||
m.Get("/refs", repo.GetGitAllRefs)
|
m.Get("/refs", repo.GetGitAllRefs)
|
||||||
m.Get("/refs/*", repo.GetGitRefs)
|
m.Get("/refs/*", repo.GetGitRefs)
|
||||||
m.Get("/trees/{sha}", repo.GetTree)
|
m.Get("/trees/{sha}", repo.GetTree)
|
||||||
|
m.Get("/blobs", repo.GetBlobs)
|
||||||
m.Get("/blobs/{sha}", repo.GetBlob)
|
m.Get("/blobs/{sha}", repo.GetBlob)
|
||||||
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
||||||
m.Group("/notes/{sha}", func() {
|
m.Group("/notes/{sha}", func() {
|
||||||
|
|
|
@ -5,11 +5,54 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"forgejo.org/services/context"
|
"forgejo.org/services/context"
|
||||||
files_service "forgejo.org/services/repository/files"
|
files_service "forgejo.org/services/repository/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetBlobs gets multiple blobs of a repository.
|
||||||
|
func GetBlobs(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/git/blobs repository GetBlobs
|
||||||
|
// ---
|
||||||
|
// summary: Gets multiplbe blobs of a repository.
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: shas
|
||||||
|
// in: query
|
||||||
|
// description: a comma separated list of blob-sha (mind the overall URL-length limit of ~2,083 chars)
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/GitBlobList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
|
||||||
|
shas := ctx.FormString("shas")
|
||||||
|
if len(shas) == 0 {
|
||||||
|
ctx.Error(http.StatusBadRequest, "", "shas not provided")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if blobs, err := files_service.GetBlobsBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, strings.Split(shas, ",")); err != nil {
|
||||||
|
ctx.Error(http.StatusBadRequest, "", err)
|
||||||
|
} else {
|
||||||
|
ctx.JSON(http.StatusOK, blobs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlob get the blob of a repository file.
|
// GetBlob get the blob of a repository file.
|
||||||
func GetBlob(ctx *context.APIContext) {
|
func GetBlob(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/git/blobs/{sha} repository GetBlob
|
// swagger:operation GET /repos/{owner}/{repo}/git/blobs/{sha} repository GetBlob
|
||||||
|
@ -30,12 +73,12 @@ func GetBlob(ctx *context.APIContext) {
|
||||||
// required: true
|
// required: true
|
||||||
// - name: sha
|
// - name: sha
|
||||||
// in: path
|
// in: path
|
||||||
// description: sha of the commit
|
// description: sha of the blob to retrieve
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/GitBlobResponse"
|
// "$ref": "#/responses/GitBlob"
|
||||||
// "400":
|
// "400":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
|
|
|
@ -231,11 +231,18 @@ type swaggerGitTreeResponse struct {
|
||||||
Body api.GitTreeResponse `json:"body"`
|
Body api.GitTreeResponse `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitBlobResponse
|
// GitBlob
|
||||||
// swagger:response GitBlobResponse
|
// swagger:response GitBlob
|
||||||
type swaggerGitBlobResponse struct {
|
type swaggerGitBlob struct {
|
||||||
// in: body
|
// in: body
|
||||||
Body api.GitBlobResponse `json:"body"`
|
Body api.GitBlob `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitBlobList
|
||||||
|
// swagger:response GitBlobList
|
||||||
|
type swaggerGitBlobList struct {
|
||||||
|
// in: body
|
||||||
|
Body []api.GitBlob `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit
|
// Commit
|
||||||
|
|
|
@ -250,8 +250,25 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref
|
||||||
return contentsResponse, nil
|
return contentsResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlobBySHA get the GitBlobResponse of a repository using a sha hash.
|
// GetBlobsBySHA gets multiple GitBlobs of a repository by sha hash.
|
||||||
func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string) (*api.GitBlobResponse, error) {
|
func GetBlobsBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, shas []string) ([]*api.GitBlob, error) {
|
||||||
|
if len(shas) > setting.API.MaxResponseItems {
|
||||||
|
shas = shas[:setting.API.MaxResponseItems]
|
||||||
|
}
|
||||||
|
|
||||||
|
blobs := make([]*api.GitBlob, 0, len(shas))
|
||||||
|
for _, sha := range shas {
|
||||||
|
blob, err := GetBlobBySHA(ctx, repo, gitRepo, sha)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blobs = append(blobs, blob)
|
||||||
|
}
|
||||||
|
return blobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlobBySHA get the GitBlob of a repository using a sha hash.
|
||||||
|
func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string) (*api.GitBlob, error) {
|
||||||
gitBlob, err := gitRepo.GetBlob(sha)
|
gitBlob, err := gitRepo.GetBlob(sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -263,7 +280,7 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &api.GitBlobResponse{
|
return &api.GitBlob{
|
||||||
SHA: gitBlob.ID.String(),
|
SHA: gitBlob.ID.String(),
|
||||||
URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()),
|
URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()),
|
||||||
Size: gitBlob.Size(),
|
Size: gitBlob.Size(),
|
||||||
|
|
|
@ -192,7 +192,7 @@ func TestGetBlobBySHA(t *testing.T) {
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
gbr, err := GetBlobBySHA(db.DefaultContext, repo, gitRepo, "65f1bf27bc3bf70f64657658635e66094edbcb4d")
|
gbr, err := GetBlobBySHA(db.DefaultContext, repo, gitRepo, "65f1bf27bc3bf70f64657658635e66094edbcb4d")
|
||||||
expectedGBR := &api.GitBlobResponse{
|
expectedGBR := &api.GitBlob{
|
||||||
Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
|
Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
|
||||||
Encoding: "base64",
|
Encoding: "base64",
|
||||||
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
|
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
|
||||||
|
@ -202,3 +202,43 @@ func TestGetBlobBySHA(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedGBR, gbr)
|
assert.Equal(t, expectedGBR, gbr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetBlobsBySHA(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
gbr, err := GetBlobsBySHA(db.DefaultContext, repo, gitRepo, []string{
|
||||||
|
"ea82fc8777a24b07c26b3a4bf4e2742c03733eab", // Home.md
|
||||||
|
"6395b68e1feebb1e4c657b4f9f6ba2676a283c0b", // line.svg
|
||||||
|
"26f842bcad37fa40a1bb34cbb5ee219ee35d863d", // test.xml
|
||||||
|
})
|
||||||
|
expectedGBR := []*api.GitBlob{
|
||||||
|
{
|
||||||
|
Content: "IyBIb21lIHBhZ2UKClRoaXMgaXMgdGhlIGhvbWUgcGFnZSEK",
|
||||||
|
Encoding: "base64",
|
||||||
|
URL: "https://try.gitea.io/api/v1/repos/user2/repo2/git/blobs/ea82fc8777a24b07c26b3a4bf4e2742c03733eab",
|
||||||
|
SHA: "ea82fc8777a24b07c26b3a4bf4e2742c03733eab",
|
||||||
|
Size: 36,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Content: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZwogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHdpZHRoPSIxMjgiCiAgIGhlaWdodD0iMTI4IgogICB2aWV3Qm94PSIwIDAgMTI4IDEyOCI+CgogIDxsaW5lIHgxPSIwIiB5MT0iNyIgeDI9IjEwIiB5Mj0iNyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPC9zdmc+",
|
||||||
|
Encoding: "base64",
|
||||||
|
URL: "https://try.gitea.io/api/v1/repos/user2/repo2/git/blobs/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b",
|
||||||
|
SHA: "6395b68e1feebb1e4c657b4f9f6ba2676a283c0b",
|
||||||
|
Size: 246,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Content: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHRlc3Q+VGhpcyBpcyBYTUw8L3Rlc3Q+Cg==",
|
||||||
|
Encoding: "base64",
|
||||||
|
URL: "https://try.gitea.io/api/v1/repos/user2/repo2/git/blobs/26f842bcad37fa40a1bb34cbb5ee219ee35d863d",
|
||||||
|
SHA: "26f842bcad37fa40a1bb34cbb5ee219ee35d863d",
|
||||||
|
Size: 64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedGBR, gbr)
|
||||||
|
}
|
||||||
|
|
66
templates/swagger/v1_json.tmpl
generated
66
templates/swagger/v1_json.tmpl
generated
|
@ -7616,6 +7616,49 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/git/blobs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Gets multiplbe blobs of a repository.",
|
||||||
|
"operationId": "GetBlobs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "a comma separated list of blob-sha (mind the overall URL-length limit of ~2,083 chars)",
|
||||||
|
"name": "shas",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/GitBlobList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/git/blobs/{sha}": {
|
"/repos/{owner}/{repo}/git/blobs/{sha}": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -7643,7 +7686,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "sha of the commit",
|
"description": "sha of the blob to retrieve",
|
||||||
"name": "sha",
|
"name": "sha",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
@ -7651,7 +7694,7 @@
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"$ref": "#/responses/GitBlobResponse"
|
"$ref": "#/responses/GitBlob"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
|
@ -24864,8 +24907,8 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "forgejo.org/modules/structs"
|
"x-go-package": "forgejo.org/modules/structs"
|
||||||
},
|
},
|
||||||
"GitBlobResponse": {
|
"GitBlob": {
|
||||||
"description": "GitBlobResponse represents a git blob",
|
"description": "GitBlob represents a git blob",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"content": {
|
"content": {
|
||||||
|
@ -29119,10 +29162,19 @@
|
||||||
"$ref": "#/definitions/GeneralUISettings"
|
"$ref": "#/definitions/GeneralUISettings"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GitBlobResponse": {
|
"GitBlob": {
|
||||||
"description": "GitBlobResponse",
|
"description": "GitBlob",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/GitBlobResponse"
|
"$ref": "#/definitions/GitBlob"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GitBlobList": {
|
||||||
|
"description": "GitBlobList",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/GitBlob"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GitHook": {
|
"GitHook": {
|
||||||
|
|
|
@ -37,7 +37,7 @@ func TestAPIReposGitBlobs(t *testing.T) {
|
||||||
// Test a public repo that anyone can GET the blob of
|
// Test a public repo that anyone can GET the blob of
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo1.Name, repo1ReadmeSHA)
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo1.Name, repo1ReadmeSHA)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
var gitBlobResponse api.GitBlobResponse
|
var gitBlobResponse api.GitBlob
|
||||||
DecodeJSON(t, resp, &gitBlobResponse)
|
DecodeJSON(t, resp, &gitBlobResponse)
|
||||||
assert.NotNil(t, gitBlobResponse)
|
assert.NotNil(t, gitBlobResponse)
|
||||||
expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"
|
expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue