feat: API GET /repos/{owner}/{repo}/git/blobs

This commit is contained in:
oliverpool 2025-06-13 11:36:57 +02:00
parent 07e8684a61
commit a4ea74020f
6 changed files with 167 additions and 7 deletions

View file

@ -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() {

View file

@ -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

View file

@ -238,6 +238,13 @@ type swaggerGitBlob struct {
Body api.GitBlob `json:"body"` Body api.GitBlob `json:"body"`
} }
// GitBlobList
// swagger:response GitBlobList
type swaggerGitBlobList struct {
// in: body
Body []api.GitBlob `json:"body"`
}
// Commit // Commit
// swagger:response Commit // swagger:response Commit
type swaggerCommit struct { type swaggerCommit struct {

View file

@ -250,6 +250,23 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref
return contentsResponse, nil return contentsResponse, nil
} }
// GetBlobsBySHA gets multiple GitBlobs of a repository by sha hash.
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. // 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) { 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)

View file

@ -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)
}

View file

@ -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": {