Skip to content

Commit 104eecc

Browse files
authored
Merge branch 'go-gitea:main' into main
2 parents e11a339 + 9a071a5 commit 104eecc

File tree

32 files changed

+945
-466
lines changed

32 files changed

+945
-466
lines changed

custom/conf/app.example.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2439,6 +2439,8 @@ LEVEL = Info
24392439
;DEFAULT_GIT_TREES_PER_PAGE = 1000
24402440
;; Default max size of a blob returned by the blobs API (default is 10MiB)
24412441
;DEFAULT_MAX_BLOB_SIZE = 10485760
2442+
;; Default max combined size of all blobs returned by the files API (default is 100MiB)
2443+
;DEFAULT_MAX_RESPONSE_SIZE = 104857600
24422444

24432445
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24442446
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

models/issues/issue_lock.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import (
1212

1313
// IssueLockOptions defines options for locking and/or unlocking an issue/PR
1414
type IssueLockOptions struct {
15-
Doer *user_model.User
16-
Issue *Issue
15+
Doer *user_model.User
16+
Issue *Issue
17+
18+
// Reason is the doer-provided comment message for the locked issue
19+
// GitHub doesn't support changing the "reasons" by config file, so GitHub has pre-defined "reason" enum values.
20+
// Gitea is not like GitHub, it allows site admin to define customized "reasons" in the config file.
21+
// So the API caller might not know what kind of "reasons" are valid, and the customized reasons are not translatable.
22+
// To make things clear and simple: doer have the chance to use any reason they like, we do not do validation.
1723
Reason string
1824
}
1925

modules/git/repo_object.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,3 @@ func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error)
8585
}
8686
return strings.TrimSpace(stdout.String()), nil
8787
}
88-
89-
// GetRefType gets the type of the ref based on the string
90-
func (repo *Repository) GetRefType(ref string) ObjectType {
91-
if repo.IsTagExist(ref) {
92-
return ObjectTag
93-
} else if repo.IsBranchExist(ref) {
94-
return ObjectBranch
95-
} else if repo.IsCommitExist(ref) {
96-
return ObjectCommit
97-
} else if _, err := repo.GetBlob(ref); err == nil {
98-
return ObjectBlob
99-
}
100-
return ObjectType("invalid")
101-
}

modules/setting/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ var API = struct {
1818
DefaultPagingNum int
1919
DefaultGitTreesPerPage int
2020
DefaultMaxBlobSize int64
21+
DefaultMaxResponseSize int64
2122
}{
2223
EnableSwagger: true,
2324
SwaggerURL: "",
2425
MaxResponseItems: 50,
2526
DefaultPagingNum: 30,
2627
DefaultGitTreesPerPage: 1000,
2728
DefaultMaxBlobSize: 10485760,
29+
DefaultMaxResponseSize: 104857600,
2830
}
2931

3032
func loadAPIFrom(rootCfg ConfigProvider) {

modules/structs/git_blob.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ package structs
55

66
// GitBlobResponse represents a git blob
77
type GitBlobResponse struct {
8-
Content string `json:"content"`
9-
Encoding string `json:"encoding"`
10-
URL string `json:"url"`
11-
SHA string `json:"sha"`
12-
Size int64 `json:"size"`
8+
Content *string `json:"content"`
9+
Encoding *string `json:"encoding"`
10+
URL string `json:"url"`
11+
SHA string `json:"sha"`
12+
Size int64 `json:"size"`
1313
}

modules/structs/issue.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,8 @@ type IssueMeta struct {
266266
Owner string `json:"owner"`
267267
Name string `json:"repo"`
268268
}
269+
270+
// LockIssueOption options to lock an issue
271+
type LockIssueOption struct {
272+
Reason string `json:"lock_reason"`
273+
}

modules/structs/repo_file.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,8 @@ type FileDeleteResponse struct {
176176
Commit *FileCommitResponse `json:"commit"`
177177
Verification *PayloadCommitVerification `json:"verification"`
178178
}
179+
180+
// GetFilesOptions options for retrieving metadate and content of multiple files
181+
type GetFilesOptions struct {
182+
Files []string `json:"files" binding:"Required"`
183+
}

modules/structs/settings.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type GeneralAPISettings struct {
2626
DefaultPagingNum int `json:"default_paging_num"`
2727
DefaultGitTreesPerPage int `json:"default_git_trees_per_page"`
2828
DefaultMaxBlobSize int64 `json:"default_max_blob_size"`
29+
DefaultMaxResponseSize int64 `json:"default_max_response_size"`
2930
}
3031

3132
// GeneralAttachmentSettings contains global Attachment settings exposed by API

options/locale/locale_en-US.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1681,7 +1681,6 @@ issues.pin_comment = "pinned this %s"
16811681
issues.unpin_comment = "unpinned this %s"
16821682
issues.lock = Lock conversation
16831683
issues.unlock = Unlock conversation
1684-
issues.lock.unknown_reason = Cannot lock an issue with an unknown reason.
16851684
issues.lock_duplicate = An issue cannot be locked twice.
16861685
issues.unlock_error = Cannot unlock an issue that is not locked.
16871686
issues.lock_with_reason = "locked as <strong>%s</strong> and limited conversation to collaborators %s"

routers/api/v1/api.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,14 +1389,17 @@ func Routes() *web.Router {
13891389
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
13901390
m.Group("/contents", func() {
13911391
m.Get("", repo.GetContentsList)
1392-
m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
13931392
m.Get("/*", repo.GetContents)
1393+
m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
13941394
m.Group("/*", func() {
13951395
m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
13961396
m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
13971397
m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
13981398
}, reqToken())
1399-
}, reqRepoReader(unit.TypeCode))
1399+
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
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
14001403
m.Get("/signing-key.gpg", misc.SigningKey)
14011404
m.Group("/topics", func() {
14021405
m.Combo("").Get(repo.ListTopics).
@@ -1530,6 +1533,11 @@ func Routes() *web.Router {
15301533
Delete(reqToken(), reqAdmin(), repo.UnpinIssue)
15311534
m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin)
15321535
})
1536+
m.Group("/lock", func() {
1537+
m.Combo("").
1538+
Put(bind(api.LockIssueOption{}), repo.LockIssue).
1539+
Delete(repo.UnlockIssue)
1540+
}, reqToken(), reqAdmin())
15331541
})
15341542
}, mustEnableIssuesOrPulls)
15351543
m.Group("/labels", func() {

routers/api/v1/repo/file.go

Lines changed: 120 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ import (
1616

1717
git_model "code.gitea.io/gitea/models/git"
1818
repo_model "code.gitea.io/gitea/models/repo"
19-
"code.gitea.io/gitea/models/unit"
2019
"code.gitea.io/gitea/modules/git"
2120
"code.gitea.io/gitea/modules/gitrepo"
2221
"code.gitea.io/gitea/modules/httpcache"
22+
"code.gitea.io/gitea/modules/json"
2323
"code.gitea.io/gitea/modules/lfs"
2424
"code.gitea.io/gitea/modules/log"
2525
"code.gitea.io/gitea/modules/setting"
2626
"code.gitea.io/gitea/modules/storage"
2727
api "code.gitea.io/gitea/modules/structs"
28+
"code.gitea.io/gitea/modules/util"
2829
"code.gitea.io/gitea/modules/web"
30+
"code.gitea.io/gitea/routers/api/v1/utils"
2931
"code.gitea.io/gitea/routers/common"
3032
"code.gitea.io/gitea/services/context"
3133
pull_service "code.gitea.io/gitea/services/pull"
@@ -375,7 +377,7 @@ func GetEditorconfig(ctx *context.APIContext) {
375377
// required: true
376378
// - name: ref
377379
// in: query
378-
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
380+
// description: "The name of the commit/branch/tag. Default to the repository’s default branch."
379381
// type: string
380382
// required: false
381383
// responses:
@@ -410,11 +412,6 @@ func canWriteFiles(ctx *context.APIContext, branch string) bool {
410412
!ctx.Repo.Repository.IsArchived
411413
}
412414

413-
// canReadFiles returns true if repository is readable and user has proper access level.
414-
func canReadFiles(r *context.Repository) bool {
415-
return r.Permission.CanRead(unit.TypeCode)
416-
}
417-
418415
func base64Reader(s string) (io.ReadSeeker, error) {
419416
b, err := base64.StdEncoding.DecodeString(s)
420417
if err != nil {
@@ -894,6 +891,17 @@ func DeleteFile(ctx *context.APIContext) {
894891
}
895892
}
896893

894+
func resolveRefCommit(ctx *context.APIContext, ref string, minCommitIDLen ...int) *utils.RefCommit {
895+
ref = util.IfZero(ref, ctx.Repo.Repository.DefaultBranch)
896+
refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ref, minCommitIDLen...)
897+
if errors.Is(err, util.ErrNotExist) {
898+
ctx.APIErrorNotFound(err)
899+
} else if err != nil {
900+
ctx.APIErrorInternal(err)
901+
}
902+
return refCommit
903+
}
904+
897905
// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
898906
func GetContents(ctx *context.APIContext) {
899907
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
@@ -919,7 +927,7 @@ func GetContents(ctx *context.APIContext) {
919927
// required: true
920928
// - name: ref
921929
// in: query
922-
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
930+
// description: "The name of the commit/branch/tag. Default to the repository’s default branch."
923931
// type: string
924932
// required: false
925933
// responses:
@@ -928,18 +936,13 @@ func GetContents(ctx *context.APIContext) {
928936
// "404":
929937
// "$ref": "#/responses/notFound"
930938

931-
if !canReadFiles(ctx.Repo) {
932-
ctx.APIErrorInternal(repo_model.ErrUserDoesNotHaveAccessToRepo{
933-
UserID: ctx.Doer.ID,
934-
RepoName: ctx.Repo.Repository.LowerName,
935-
})
939+
treePath := ctx.PathParam("*")
940+
refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
941+
if ctx.Written() {
936942
return
937943
}
938944

939-
treePath := ctx.PathParam("*")
940-
ref := ctx.FormTrim("ref")
941-
942-
if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref); err != nil {
945+
if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, refCommit, treePath); err != nil {
943946
if git.IsErrNotExist(err) {
944947
ctx.APIErrorNotFound("GetContentsOrList", err)
945948
return
@@ -970,7 +973,7 @@ func GetContentsList(ctx *context.APIContext) {
970973
// required: true
971974
// - name: ref
972975
// in: query
973-
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
976+
// description: "The name of the commit/branch/tag. Default to the repository’s default branch."
974977
// type: string
975978
// required: false
976979
// responses:
@@ -982,3 +985,102 @@ func GetContentsList(ctx *context.APIContext) {
982985
// same as GetContents(), this function is here because swagger fails if path is empty in GetContents() interface
983986
GetContents(ctx)
984987
}
988+
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 query 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 to the repository’s default branch."
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
1029+
// ---
1030+
// summary: Get the metadata and contents of requested files
1031+
// description: Uses automatic pagination based on default page size and
1032+
// max response size and returns the maximum allowed number of files.
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`.
1036+
// produces:
1037+
// - application/json
1038+
// parameters:
1039+
// - name: owner
1040+
// in: path
1041+
// description: owner of the repo
1042+
// type: string
1043+
// required: true
1044+
// - name: repo
1045+
// in: path
1046+
// description: name of the repo
1047+
// type: string
1048+
// required: true
1049+
// - name: ref
1050+
// in: query
1051+
// description: "The name of the commit/branch/tag. Default to the repository’s default branch."
1052+
// type: string
1053+
// required: false
1054+
// - name: body
1055+
// in: body
1056+
// required: true
1057+
// schema:
1058+
// "$ref": "#/definitions/GetFilesOptions"
1059+
// responses:
1060+
// "200":
1061+
// "$ref": "#/responses/ContentsListResponse"
1062+
// "404":
1063+
// "$ref": "#/responses/notFound"
1064+
1065+
// This is actually a "read" request, but we need to accept a "files" list, then POST method seems easy to use.
1066+
// But the permission system requires that the caller must have "write" permission to use POST method.
1067+
// At the moment there is no other way to get around the permission check, so there is a "GET" workaround method above.
1068+
handleGetFileContents(ctx)
1069+
}
1070+
1071+
func handleGetFileContents(ctx *context.APIContext) {
1072+
opts, ok := web.GetForm(ctx).(*api.GetFilesOptions)
1073+
if !ok {
1074+
err := json.Unmarshal(util.UnsafeStringToBytes(ctx.FormString("body")), &opts)
1075+
if err != nil {
1076+
ctx.APIError(http.StatusBadRequest, "invalid body parameter")
1077+
return
1078+
}
1079+
}
1080+
refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
1081+
if ctx.Written() {
1082+
return
1083+
}
1084+
filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, refCommit, opts.Files)
1085+
ctx.JSON(http.StatusOK, util.SliceNilAsEmpty(filesResponse))
1086+
}

0 commit comments

Comments
 (0)