mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-15 05:37:46 +00:00
Merge a08510567c
into 93a2def96b
This commit is contained in:
commit
3440181522
@ -176,3 +176,8 @@ type FileDeleteResponse struct {
|
||||
Commit *FileCommitResponse `json:"commit"`
|
||||
Verification *PayloadCommitVerification `json:"verification"`
|
||||
}
|
||||
|
||||
// GetFilesOptions options for retrieving metadate and content of multiple files
|
||||
type GetFilesOptions struct {
|
||||
Files []string `json:"files" binding:"Required"`
|
||||
}
|
||||
|
@ -1389,6 +1389,7 @@ func Routes() *web.Router {
|
||||
m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
|
||||
}, reqToken())
|
||||
}, reqRepoReader(unit.TypeCode))
|
||||
m.Post("/files", context.ReferencesGitRepo(), context.RepoRefForAPI, bind(api.GetFilesOptions{}), reqRepoReader(unit.TypeCode), repo.GetFiles)
|
||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||
m.Group("/topics", func() {
|
||||
m.Combo("").Get(repo.ListTopics).
|
||||
|
@ -25,7 +25,9 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
@ -982,3 +984,69 @@ func GetContentsList(ctx *context.APIContext) {
|
||||
// same as GetContents(), this function is here because swagger fails if path is empty in GetContents() interface
|
||||
GetContents(ctx)
|
||||
}
|
||||
|
||||
// GetFiles Get the metadata and contents of requested files
|
||||
func GetFiles(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/files repository repoGetFiles
|
||||
// ---
|
||||
// summary: Get the metadata and contents of requested files
|
||||
// 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: ref
|
||||
// in: query
|
||||
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/GetFilesOptions"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ContentsListResponse"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
apiOpts := web.GetForm(ctx).(*api.GetFilesOptions)
|
||||
|
||||
ref := ctx.FormTrim("ref")
|
||||
if ref == "" {
|
||||
ref = ctx.Repo.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
if !ctx.Repo.GitRepo.IsReferenceExist(ref) {
|
||||
ctx.APIErrorNotFound("GetFiles", "ref does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
files := apiOpts.Files
|
||||
|
||||
filesResponse := files_service.GetContentsListFromTrees(ctx, ctx.Repo.Repository, ref, files)
|
||||
count := len(filesResponse)
|
||||
|
||||
listOpts := utils.GetListOptions(ctx)
|
||||
filesResponse = util.PaginateSlice(filesResponse, listOpts.Page, listOpts.PageSize).([]*api.ContentsResponse)
|
||||
|
||||
ctx.SetTotalCountHeader(int64(count))
|
||||
ctx.JSON(http.StatusOK, filesResponse)
|
||||
}
|
||||
|
@ -118,6 +118,9 @@ type swaggerParameterBodies struct {
|
||||
// in:body
|
||||
EditAttachmentOptions api.EditAttachmentOptions
|
||||
|
||||
// in:body
|
||||
GetFilesOptions api.GetFilesOptions
|
||||
|
||||
// in:body
|
||||
ChangeFilesOptions api.ChangeFilesOptions
|
||||
|
||||
|
@ -17,12 +17,17 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func GetFilesResponseFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, branch string, treeNames []string) (*api.FilesResponse, error) {
|
||||
func GetContentsListFromTrees(ctx context.Context, repo *repo_model.Repository, branch string, treeNames []string) []*api.ContentsResponse {
|
||||
files := []*api.ContentsResponse{}
|
||||
for _, file := range treeNames {
|
||||
fileContents, _ := GetContents(ctx, repo, file, branch, false) // ok if fails, then will be nil
|
||||
files = append(files, fileContents)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func GetFilesResponseFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, branch string, treeNames []string) (*api.FilesResponse, error) {
|
||||
files := GetContentsListFromTrees(ctx, repo, branch, treeNames)
|
||||
fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
|
||||
verification := GetPayloadCommitVerification(ctx, commit)
|
||||
filesResponse := &api.FilesResponse{
|
||||
|
76
templates/swagger/v1_json.tmpl
generated
76
templates/swagger/v1_json.tmpl
generated
@ -6771,6 +6771,68 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/files": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get the metadata and contents of requested files",
|
||||
"operationId": "repoGetFiles",
|
||||
"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": "The name of the commit/branch/tag. Default the repository’s default branch (usually master)",
|
||||
"name": "ref",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/GetFilesOptions"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/ContentsListResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/forks": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@ -23018,6 +23080,20 @@
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"GetFilesOptions": {
|
||||
"description": "GetFilesOptions options for retrieving metadate and content of multiple files",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-name": "Files"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"GitBlobResponse": {
|
||||
"description": "GitBlobResponse represents a git blob",
|
||||
"type": "object",
|
||||
|
145
tests/integration/api_repo_files_get_test.go
Normal file
145
tests/integration/api_repo_files_get_test.go
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func getExpectedcontentsListResponseForFiles(ref, refType, lastCommitSHA string) []*api.ContentsResponse {
|
||||
return []*api.ContentsResponse{getExpectedContentsResponseForContents(ref, refType, lastCommitSHA)}
|
||||
}
|
||||
|
||||
func TestAPIGetRequestedFiles(t *testing.T) {
|
||||
onGiteaRun(t, testAPIGetRequestedFiles)
|
||||
}
|
||||
|
||||
func testAPIGetRequestedFiles(t *testing.T, u *url.URL) {
|
||||
/*** SETUP ***/
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
|
||||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
|
||||
repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
|
||||
filesOptions := &api.GetFilesOptions{
|
||||
Files: []string{
|
||||
"README.md",
|
||||
},
|
||||
}
|
||||
|
||||
// Get user2's token req.Body =
|
||||
session := loginUser(t, user2.Name)
|
||||
token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // TODO: allow for a POST-request to be scope read
|
||||
// Get user4's token
|
||||
session = loginUser(t, user4.Name)
|
||||
token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // TODO: allow for a POST-request to be scope read
|
||||
|
||||
// Get the commit ID of the default branch
|
||||
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo1)
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
// Make a new branch in repo1
|
||||
newBranch := "test_branch"
|
||||
err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
|
||||
assert.NoError(t, err)
|
||||
// Make a new tag in repo1
|
||||
newTag := "test_tag"
|
||||
err = gitRepo.CreateTag(newTag, commitID)
|
||||
assert.NoError(t, err)
|
||||
/*** END SETUP ***/
|
||||
|
||||
// ref is default ref
|
||||
ref := repo1.DefaultBranch
|
||||
refType := "branch"
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files?ref=%s", user2.Name, repo1.Name, ref), &filesOptions)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var contentsListResponse []*api.ContentsResponse
|
||||
DecodeJSON(t, resp, &contentsListResponse)
|
||||
assert.NotNil(t, contentsListResponse)
|
||||
lastCommit, _ := gitRepo.GetCommitByPath("README.md")
|
||||
expectedcontentsListResponse := getExpectedcontentsListResponseForFiles(ref, refType, lastCommit.ID.String())
|
||||
assert.Equal(t, expectedcontentsListResponse, contentsListResponse)
|
||||
|
||||
// No ref
|
||||
refType = "branch"
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files", user2.Name, repo1.Name), &filesOptions)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &contentsListResponse)
|
||||
assert.NotNil(t, contentsListResponse)
|
||||
expectedcontentsListResponse = getExpectedcontentsListResponseForFiles(repo1.DefaultBranch, refType, lastCommit.ID.String())
|
||||
assert.Equal(t, expectedcontentsListResponse, contentsListResponse)
|
||||
|
||||
// ref is the branch we created above in setup
|
||||
ref = newBranch
|
||||
refType = "branch"
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files?ref=%s", user2.Name, repo1.Name, ref), &filesOptions)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &contentsListResponse)
|
||||
assert.NotNil(t, contentsListResponse)
|
||||
branchCommit, _ := gitRepo.GetBranchCommit(ref)
|
||||
lastCommit, _ = branchCommit.GetCommitByPath("README.md")
|
||||
expectedcontentsListResponse = getExpectedcontentsListResponseForFiles(ref, refType, lastCommit.ID.String())
|
||||
assert.Equal(t, expectedcontentsListResponse, contentsListResponse)
|
||||
|
||||
// ref is the new tag we created above in setup
|
||||
ref = newTag
|
||||
refType = "tag"
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files?ref=%s", user2.Name, repo1.Name, ref), &filesOptions)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &contentsListResponse)
|
||||
assert.NotNil(t, contentsListResponse)
|
||||
tagCommit, _ := gitRepo.GetTagCommit(ref)
|
||||
lastCommit, _ = tagCommit.GetCommitByPath("README.md")
|
||||
expectedcontentsListResponse = getExpectedcontentsListResponseForFiles(ref, refType, lastCommit.ID.String())
|
||||
assert.Equal(t, expectedcontentsListResponse, contentsListResponse)
|
||||
|
||||
// ref is a commit
|
||||
ref = commitID
|
||||
refType = "commit"
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files?ref=%s", user2.Name, repo1.Name, ref), &filesOptions)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &contentsListResponse)
|
||||
assert.NotNil(t, contentsListResponse)
|
||||
expectedcontentsListResponse = getExpectedcontentsListResponseForFiles(ref, refType, commitID)
|
||||
assert.Equal(t, expectedcontentsListResponse, contentsListResponse)
|
||||
|
||||
// Test file contents a file with a bad ref
|
||||
ref = "badref"
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files?ref=%s", user2.Name, repo1.Name, ref), &filesOptions)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// Test accessing private ref with user token that does not have access - should fail
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files", user2.Name, repo16.Name), &filesOptions).
|
||||
AddTokenAuth(token4)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// Test access private ref of owner of token
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files", user2.Name, repo16.Name), &filesOptions).
|
||||
AddTokenAuth(token2)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Test access of org org3 private repo file by owner user2
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files", org3.Name, repo3.Name), &filesOptions).
|
||||
AddTokenAuth(token2)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
Loading…
Reference in New Issue
Block a user