Skip to content

Commit 6fd2c3a

Browse files
committed
fix
1 parent 29b2800 commit 6fd2c3a

File tree

11 files changed

+300
-202
lines changed

11 files changed

+300
-202
lines changed

modules/git/tree_entry_nogogit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type TreeEntry struct {
1818
sized bool
1919
}
2020

21-
// Name returns the name of the entry
21+
// Name returns the name of the entry (full path name)
2222
func (te *TreeEntry) Name() string {
2323
return te.name
2424
}

modules/structs/repo_file.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ type FileLinksResponse struct {
119119
HTMLURL *string `json:"html"`
120120
}
121121

122+
type ContentsExtResponse struct {
123+
FileContents *ContentsResponse `json:"file_contents,omitempty"`
124+
DirContents []*ContentsResponse `json:"dir_contents,omitempty"`
125+
}
126+
122127
// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content
123128
type ContentsResponse struct {
124129
Name string `json:"name"`

routers/api/v1/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,10 @@ func Routes() *web.Router {
14351435
m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
14361436
}, reqToken())
14371437
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
1438+
m.Group("/contents-ext", func() {
1439+
m.Get("", repo.GetContentsExt)
1440+
m.Get("/*", repo.GetContentsExt)
1441+
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
14381442
m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()).
14391443
Get(repo.GetFileContentsGet).
14401444
Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // POST method requires "write" permission, so we also support "GET" method above

routers/api/v1/repo/file.go

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,62 @@ func resolveRefCommit(ctx *context.APIContext, ref string, minCommitIDLen ...int
905905
return refCommit
906906
}
907907

908+
func GetContentsExt(ctx *context.APIContext) {
909+
// swagger:operation GET /repos/{owner}/{repo}/contents-ext/{filepath} repository repoGetContentsExt
910+
// ---
911+
// summary: The extended "contents" API, get file metadata and/or content, or list a directory.
912+
// description: It guarantees that only one of the response fields is set if the request succeeds.
913+
// produces:
914+
// - application/json
915+
// parameters:
916+
// - name: owner
917+
// in: path
918+
// description: owner of the repo
919+
// type: string
920+
// required: true
921+
// - name: repo
922+
// in: path
923+
// description: name of the repo
924+
// type: string
925+
// required: true
926+
// - name: filepath
927+
// in: path
928+
// description: path of the dir, file, symlink or submodule in the repo
929+
// type: string
930+
// required: true
931+
// - name: ref
932+
// in: query
933+
// description: "The name of the commit/branch/tag. Default to the repository’s default branch."
934+
// type: string
935+
// required: false
936+
// - name: includes
937+
// in: query
938+
// description: Set it to "file_content" to retrieve the file content when requesting a file, otherwise the response only contains the file's metadata.
939+
// type: string
940+
// required: false
941+
// responses:
942+
// "200":
943+
// "$ref": "#/responses/ContentsExtResponse"
944+
// "404":
945+
// "$ref": "#/responses/notFound"
946+
947+
// TODO: add more includes options, like "lfs_content"
948+
opts := files_service.GetContentsOrListOptions{}
949+
for includeOpt := range strings.SplitSeq(ctx.FormString("includes"), ",") {
950+
if includeOpt == "" {
951+
continue
952+
}
953+
switch includeOpt {
954+
case "file_content":
955+
opts.IncludeSingleFileContent = true
956+
default:
957+
ctx.APIError(http.StatusBadRequest, fmt.Sprintf("unknown include option %q", includeOpt))
958+
return
959+
}
960+
}
961+
ctx.JSON(http.StatusOK, getRepoContents(ctx, opts))
962+
}
963+
908964
// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
909965
func GetContents(ctx *context.APIContext) {
910966
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
@@ -938,22 +994,28 @@ func GetContents(ctx *context.APIContext) {
938994
// "$ref": "#/responses/ContentsResponse"
939995
// "404":
940996
// "$ref": "#/responses/notFound"
997+
ret := getRepoContents(ctx, files_service.GetContentsOrListOptions{IncludeSingleFileContent: true})
998+
if ctx.Written() {
999+
return
1000+
}
1001+
ctx.JSON(http.StatusOK, util.Iif[any](ret.FileContents != nil, ret.FileContents, ret.DirContents))
1002+
}
9411003

1004+
func getRepoContents(ctx *context.APIContext, opts files_service.GetContentsOrListOptions) *api.ContentsExtResponse {
9421005
treePath := ctx.PathParam("*")
9431006
refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
9441007
if ctx.Written() {
945-
return
1008+
return nil
9461009
}
947-
948-
if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, refCommit, treePath); err != nil {
1010+
ret, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, treePath, opts)
1011+
if err != nil {
9491012
if git.IsErrNotExist(err) {
9501013
ctx.APIErrorNotFound("GetContentsOrList", err)
951-
return
1014+
return nil
9521015
}
9531016
ctx.APIErrorInternal(err)
954-
} else {
955-
ctx.JSON(http.StatusOK, fileList)
9561017
}
1018+
return &ret
9571019
}
9581020

9591021
// GetContentsList Get the metadata of all the entries of the root dir
@@ -1084,6 +1146,6 @@ func handleGetFileContents(ctx *context.APIContext) {
10841146
if ctx.Written() {
10851147
return
10861148
}
1087-
filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, refCommit, opts.Files)
1149+
filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, opts.Files)
10881150
ctx.JSON(http.StatusOK, util.SliceNilAsEmpty(filesResponse))
10891151
}

routers/api/v1/swagger/repo.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,12 @@ type swaggerContentsListResponse struct {
331331
Body []api.ContentsResponse `json:"body"`
332332
}
333333

334+
// swagger:response ContentsExtResponse
335+
type swaggerContentsExtResponse struct {
336+
// in:body
337+
Body api.ContentsExtResponse `json:"body"`
338+
}
339+
334340
// FileDeleteResponse
335341
// swagger:response FileDeleteResponse
336342
type swaggerFileDeleteResponse struct {

services/repository/files/content.go

Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ package files
55

66
import (
77
"context"
8-
"fmt"
98
"net/url"
109
"path"
1110

1211
repo_model "code.gitea.io/gitea/models/repo"
1312
"code.gitea.io/gitea/modules/git"
14-
"code.gitea.io/gitea/modules/gitrepo"
1513
"code.gitea.io/gitea/modules/setting"
1614
api "code.gitea.io/gitea/modules/structs"
1715
"code.gitea.io/gitea/modules/util"
@@ -34,54 +32,47 @@ func (ct *ContentType) String() string {
3432
return string(*ct)
3533
}
3634

35+
type GetContentsOrListOptions struct {
36+
IncludeSingleFileContent bool // include the file's content when the tree path is a file
37+
}
38+
3739
// GetContentsOrList gets the metadata of a file's contents (*ContentsResponse) if treePath not a tree
3840
// directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag
39-
func GetContentsOrList(ctx context.Context, repo *repo_model.Repository, refCommit *utils.RefCommit, treePath string) (any, error) {
40-
if repo.IsEmpty {
41-
return make([]any, 0), nil
41+
func GetContentsOrList(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treePath string, opts GetContentsOrListOptions) (ret api.ContentsExtResponse, _ error) {
42+
entry, err := prepareGetContentsEntry(refCommit, &treePath)
43+
if repo.IsEmpty && treePath == "" {
44+
return api.ContentsExtResponse{DirContents: make([]*api.ContentsResponse, 0)}, nil
4245
}
43-
44-
// Check that the path given in opts.treePath is valid (not a git path)
45-
cleanTreePath := CleanGitTreePath(treePath)
46-
if cleanTreePath == "" && treePath != "" {
47-
return nil, ErrFilenameInvalid{
48-
Path: treePath,
49-
}
50-
}
51-
treePath = cleanTreePath
52-
53-
// Get the commit object for the ref
54-
commit := refCommit.Commit
55-
56-
entry, err := commit.GetTreeEntryByPath(treePath)
5746
if err != nil {
58-
return nil, err
47+
return ret, err
5948
}
6049

50+
// get file contents
6151
if entry.Type() != "tree" {
62-
return GetContents(ctx, repo, refCommit, treePath, false)
52+
ret.FileContents, err = getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
53+
return ret, err
6354
}
6455

65-
// We are in a directory, so we return a list of FileContentResponse objects
66-
var fileList []*api.ContentsResponse
67-
68-
gitTree, err := commit.SubTree(treePath)
56+
// list directory contents
57+
gitTree, err := refCommit.Commit.SubTree(treePath)
6958
if err != nil {
70-
return nil, err
59+
return ret, err
7160
}
7261
entries, err := gitTree.ListEntries()
7362
if err != nil {
74-
return nil, err
63+
return ret, err
7564
}
65+
opts = GetContentsOrListOptions{IncludeSingleFileContent: false} // never include file content when listing a directory
66+
ret.DirContents = make([]*api.ContentsResponse, 0, len(entries))
7667
for _, e := range entries {
7768
subTreePath := path.Join(treePath, e.Name())
78-
fileContentResponse, err := GetContents(ctx, repo, refCommit, subTreePath, true)
69+
fileContentResponse, err := GetFileContents(ctx, repo, gitRepo, refCommit, subTreePath, opts)
7970
if err != nil {
80-
return nil, err
71+
return ret, err
8172
}
82-
fileList = append(fileList, fileContentResponse)
73+
ret.DirContents = append(ret.DirContents, fileContentResponse)
8374
}
84-
return fileList, nil
75+
return ret, nil
8576
}
8677

8778
// GetObjectTypeFromTreeEntry check what content is behind it
@@ -100,34 +91,36 @@ func GetObjectTypeFromTreeEntry(entry *git.TreeEntry) ContentType {
10091
}
10192
}
10293

103-
// GetContents gets the metadata on a file's contents. Ref can be a branch, commit or tag
104-
func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *utils.RefCommit, treePath string, forList bool) (*api.ContentsResponse, error) {
94+
func prepareGetContentsEntry(refCommit *utils.RefCommit, treePath *string) (*git.TreeEntry, error) {
10595
// Check that the path given in opts.treePath is valid (not a git path)
106-
cleanTreePath := CleanGitTreePath(treePath)
107-
if cleanTreePath == "" && treePath != "" {
108-
return nil, ErrFilenameInvalid{
109-
Path: treePath,
110-
}
96+
cleanTreePath := CleanGitTreePath(*treePath)
97+
if cleanTreePath == "" && *treePath != "" {
98+
return nil, ErrFilenameInvalid{Path: *treePath}
11199
}
112-
treePath = cleanTreePath
100+
*treePath = cleanTreePath
113101

114-
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
115-
if err != nil {
116-
return nil, err
102+
// Only allow safe ref types
103+
refType := refCommit.RefName.RefType()
104+
if refType != git.RefTypeBranch && refType != git.RefTypeTag && refType != git.RefTypeCommit {
105+
return nil, util.NewNotExistErrorf("no commit found for the ref [ref: %s]", refCommit.RefName)
117106
}
118-
defer closer.Close()
119107

120-
commit := refCommit.Commit
121-
entry, err := commit.GetTreeEntryByPath(treePath)
108+
return refCommit.Commit.GetTreeEntryByPath(*treePath)
109+
}
110+
111+
// GetFileContents gets the metadata on a file's contents. Ref can be a branch, commit or tag
112+
func GetFileContents(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treePath string, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
113+
entry, err := prepareGetContentsEntry(refCommit, &treePath)
122114
if err != nil {
123115
return nil, err
124116
}
117+
return getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
118+
}
125119

120+
func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
126121
refType := refCommit.RefName.RefType()
127-
if refType != git.RefTypeBranch && refType != git.RefTypeTag && refType != git.RefTypeCommit {
128-
return nil, fmt.Errorf("no commit found for the ref [ref: %s]", refCommit.RefName)
129-
}
130-
122+
commit := refCommit.Commit
123+
treePath := entry.Name()
131124
selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(treePath) + "?ref=" + url.QueryEscape(refCommit.InputRef))
132125
if err != nil {
133126
return nil, err
@@ -139,7 +132,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *ut
139132
return nil, err
140133
}
141134

142-
lastCommit, err := commit.GetCommitByPath(treePath)
135+
lastCommit, err := refCommit.Commit.GetCommitByPath(treePath)
143136
if err != nil {
144137
return nil, err
145138
}
@@ -170,7 +163,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *ut
170163
if entry.IsRegular() || entry.IsExecutable() {
171164
contentsResponse.Type = string(ContentTypeRegular)
172165
// if it is listing the repo root dir, don't waste system resources on reading content
173-
if !forList {
166+
if opts.IncludeSingleFileContent {
174167
blobResponse, err := GetBlobBySHA(ctx, repo, gitRepo, entry.ID.String())
175168
if err != nil {
176169
return nil, err

0 commit comments

Comments
 (0)