Skip to content

Commit d867f33

Browse files
committed
add GET method support
1 parent ff4a105 commit d867f33

File tree

4 files changed

+134
-26
lines changed

4 files changed

+134
-26
lines changed

routers/api/v1/api.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,9 @@ func Routes() *web.Router {
13971397
m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
13981398
}, reqToken())
13991399
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
1400-
m.Post("/files", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), bind(api.GetFilesOptions{}), repo.GetFiles)
1400+
m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()).
1401+
Get(repo.GetFileContentsGet).
1402+
Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // POST method requires "write" permission, so we also support "GET" method above
14011403
m.Get("/signing-key.gpg", misc.SigningKey)
14021404
m.Group("/topics", func() {
14031405
m.Combo("").Get(repo.ListTopics).

routers/api/v1/repo/file.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"code.gitea.io/gitea/modules/git"
2020
"code.gitea.io/gitea/modules/gitrepo"
2121
"code.gitea.io/gitea/modules/httpcache"
22+
"code.gitea.io/gitea/modules/json"
2223
"code.gitea.io/gitea/modules/lfs"
2324
"code.gitea.io/gitea/modules/log"
2425
"code.gitea.io/gitea/modules/setting"
@@ -985,16 +986,53 @@ func GetContentsList(ctx *context.APIContext) {
985986
GetContents(ctx)
986987
}
987988

988-
// GetFiles Get the metadata and contents of requested files
989-
func GetFiles(ctx *context.APIContext) {
990-
// swagger:operation POST /repos/{owner}/{repo}/files repository repoGetFiles
989+
func GetFileContentsGet(ctx *context.APIContext) {
990+
// swagger:operation GET /repos/{owner}/{repo}/file-contents repository repoGetFileContents
991+
// ---
992+
// summary: Get the metadata and contents of requested files
993+
// description: See the POST method. This GET method supports to use JSON encoded request body in parameter.
994+
// produces:
995+
// - application/json
996+
// parameters:
997+
// - name: owner
998+
// in: path
999+
// description: owner of the repo
1000+
// type: string
1001+
// required: true
1002+
// - name: repo
1003+
// in: path
1004+
// description: name of the repo
1005+
// type: string
1006+
// required: true
1007+
// - name: ref
1008+
// in: query
1009+
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
1010+
// type: string
1011+
// required: false
1012+
// - name: body
1013+
// in: query
1014+
// description: "The JSON encoded body (see the POST request): {files: [\"filename1\", \"filename2\"]}"
1015+
// type: string
1016+
// required: true
1017+
// responses:
1018+
// "200":
1019+
// "$ref": "#/responses/ContentsListResponse"
1020+
// "404":
1021+
// "$ref": "#/responses/notFound"
1022+
1023+
// POST method requires "write" permission, so we also support this "GET" method
1024+
handleGetFileContents(ctx)
1025+
}
1026+
1027+
func GetFileContentsPost(ctx *context.APIContext) {
1028+
// swagger:operation POST /repos/{owner}/{repo}/file-contents repository repoGetFileContentsPost
9911029
// ---
9921030
// summary: Get the metadata and contents of requested files
9931031
// description: Uses automatic pagination based on default page size and
9941032
// max response size and returns the maximum allowed number of files.
995-
// Files which could not be retrieved are null. Blobs which are too large
996-
// are being returned with `content = ""` and `size > 0`, they can be
997-
// requested using the `download_url`.
1033+
// Files which could not be retrieved are null. Files which are too large
1034+
// are being returned with `encoding == null`, `content == null` and `size > 0`,
1035+
// they can be requested separately by using the `download_url`.
9981036
// produces:
9991037
// - application/json
10001038
// parameters:
@@ -1023,12 +1061,22 @@ func GetFiles(ctx *context.APIContext) {
10231061
// "$ref": "#/responses/ContentsListResponse"
10241062
// "404":
10251063
// "$ref": "#/responses/notFound"
1064+
handleGetFileContents(ctx)
1065+
}
10261066

1027-
apiOpts := web.GetForm(ctx).(*api.GetFilesOptions)
1067+
func handleGetFileContents(ctx *context.APIContext) {
1068+
opts, ok := web.GetForm(ctx).(*api.GetFilesOptions)
1069+
if !ok {
1070+
err := json.Unmarshal(util.UnsafeStringToBytes(ctx.FormString("body")), &opts)
1071+
if err != nil {
1072+
ctx.APIError(http.StatusBadRequest, "invalid body parameter")
1073+
return
1074+
}
1075+
}
10281076
refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
10291077
if ctx.Written() {
10301078
return
10311079
}
1032-
filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, refCommit, apiOpts.Files)
1080+
filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, refCommit, opts.Files)
10331081
ctx.JSON(http.StatusOK, util.SliceNilAsEmpty(filesResponse))
10341082
}

templates/swagger/v1_json.tmpl

Lines changed: 51 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/api_repo_files_get_test.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package integration
66
import (
77
"fmt"
88
"net/http"
9+
"net/url"
910
"testing"
1011

1112
auth_model "code.gitea.io/gitea/models/auth"
@@ -14,6 +15,7 @@ import (
1415
user_model "code.gitea.io/gitea/models/user"
1516
"code.gitea.io/gitea/modules/git"
1617
"code.gitea.io/gitea/modules/gitrepo"
18+
"code.gitea.io/gitea/modules/json"
1719
"code.gitea.io/gitea/modules/setting"
1820
api "code.gitea.io/gitea/modules/structs"
1921
"code.gitea.io/gitea/modules/test"
@@ -34,8 +36,6 @@ func TestAPIGetRequestedFiles(t *testing.T) {
3436
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
3537
repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
3638

37-
// TODO: add "GET" support
38-
3939
// Get user2's token
4040
session := loginUser(t, user2.Name)
4141
token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
@@ -58,41 +58,51 @@ func TestAPIGetRequestedFiles(t *testing.T) {
5858
return ret
5959
}
6060

61+
t.Run("User2Get", func(t *testing.T) {
62+
reqBodyOpt := &api.GetFilesOptions{Files: []string{"README.md"}}
63+
reqBodyParam, _ := json.Marshal(reqBodyOpt)
64+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/file-contents?body=%s", url.QueryEscape(string(reqBodyParam))))
65+
resp := MakeRequest(t, req, http.StatusOK)
66+
var ret []*api.ContentsResponse
67+
DecodeJSON(t, resp, &ret)
68+
expected := []*api.ContentsResponse{getExpectedContentsResponseForContents(repo1.DefaultBranch, "branch", lastCommit.ID.String())}
69+
assert.Equal(t, expected, ret)
70+
})
6171
t.Run("User2NoRef", func(t *testing.T) {
62-
ret := requestFiles(t, "/api/v1/repos/user2/repo1/files", []string{"README.md"})
72+
ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents", []string{"README.md"})
6373
expected := []*api.ContentsResponse{getExpectedContentsResponseForContents(repo1.DefaultBranch, "branch", lastCommit.ID.String())}
6474
assert.Equal(t, expected, ret)
6575
})
6676
t.Run("User2RefBranch", func(t *testing.T) {
67-
ret := requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=master", []string{"README.md"})
77+
ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=master", []string{"README.md"})
6878
expected := []*api.ContentsResponse{getExpectedContentsResponseForContents(repo1.DefaultBranch, "branch", lastCommit.ID.String())}
6979
assert.Equal(t, expected, ret)
7080
})
7181
t.Run("User2RefTag", func(t *testing.T) {
72-
ret := requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=v1.1", []string{"README.md"})
82+
ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=v1.1", []string{"README.md"})
7383
expected := []*api.ContentsResponse{getExpectedContentsResponseForContents("v1.1", "tag", lastCommit.ID.String())}
7484
assert.Equal(t, expected, ret)
7585
})
7686
t.Run("User2RefCommit", func(t *testing.T) {
77-
ret := requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=65f1bf27bc3bf70f64657658635e66094edbcb4d", []string{"README.md"})
87+
ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=65f1bf27bc3bf70f64657658635e66094edbcb4d", []string{"README.md"})
7888
expected := []*api.ContentsResponse{getExpectedContentsResponseForContents("65f1bf27bc3bf70f64657658635e66094edbcb4d", "commit", lastCommit.ID.String())}
7989
assert.Equal(t, expected, ret)
8090
})
8191
t.Run("User2RefNotExist", func(t *testing.T) {
82-
ret := requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=not-exist", []string{"README.md"}, http.StatusNotFound)
92+
ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=not-exist", []string{"README.md"}, http.StatusNotFound)
8393
assert.Empty(t, ret)
8494
})
8595

8696
t.Run("PermissionCheck", func(t *testing.T) {
8797
filesOptions := &api.GetFilesOptions{Files: []string{"README.md"}}
8898
// Test accessing private ref with user token that does not have access - should fail
89-
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token4)
99+
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token4)
90100
MakeRequest(t, req, http.StatusNotFound)
91101
// Test access private ref of owner of token
92-
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token2)
102+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", user2.Name, repo16.Name), &filesOptions).AddTokenAuth(token2)
93103
MakeRequest(t, req, http.StatusOK)
94104
// Test access of org org3 private repo file by owner user2
95-
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/files", org3.Name, repo3.Name), &filesOptions).AddTokenAuth(token2)
105+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/file-contents", org3.Name, repo3.Name), &filesOptions).AddTokenAuth(token2)
96106
MakeRequest(t, req, http.StatusOK)
97107
})
98108

@@ -123,24 +133,24 @@ func TestAPIGetRequestedFiles(t *testing.T) {
123133
}
124134

125135
// repo1 "DefaultBranch" has 2 files: LICENSE (1064 bytes), README.md (30 bytes)
126-
ret := requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
136+
ret := requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
127137
assertResponse(t, []*expected{nil, {"LICENSE", true}, {"README.md", true}}, ret)
128138

129139
// the returned file list is limited by the DefaultPagingNum
130140
setting.API.DefaultPagingNum = 2
131-
ret = requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
141+
ret = requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
132142
assertResponse(t, []*expected{nil, {"LICENSE", true}}, ret)
133143
setting.API.DefaultPagingNum = 100
134144

135145
// if a file exceeds the DefaultMaxBlobSize, the content is not returned
136146
setting.API.DefaultMaxBlobSize = 200
137-
ret = requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
147+
ret = requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
138148
assertResponse(t, []*expected{nil, {"LICENSE", false}, {"README.md", true}}, ret)
139149
setting.API.DefaultMaxBlobSize = 20000
140150

141151
// if the total response size would exceed the DefaultMaxResponseSize, then the list stops
142152
setting.API.DefaultMaxResponseSize = ret[1].Size*4/3 + 1
143-
ret = requestFiles(t, "/api/v1/repos/user2/repo1/files?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
153+
ret = requestFiles(t, "/api/v1/repos/user2/repo1/file-contents?ref=DefaultBranch", []string{"no-such.txt", "LICENSE", "README.md"})
144154
assertResponse(t, []*expected{nil, {"LICENSE", true}}, ret)
145155
setting.API.DefaultMaxBlobSize = 20000
146156
})

0 commit comments

Comments
 (0)