Feature - Pagination for git tree API (#5838)

* Feature - Pagination for git tree API

* Handles case when page is negative

* Does a for loop over the start and end rather than all entries

* Removed redundent logic

* Adds per_page as a query parameter

* Adds DEFAULT_GIT_TREES_PER_PAGE for settings, ran make fmt

* Fix typo in cheat-sheet en

* Makes page start at 1, generated swagger

* Use updates to SDK

* Updates to use latest sdk

* Updates swagger for tree api

* Adds test for GetTreeBySHA

* Updates per PR reviews

* Updates per PR reviews

* Remove file

* Formatting

* Fix to swagger file

* Fix to swagger

* Update v1_json.tmpl

* Fix to swagger file
release/v1.15
Richard Mahn 2019-02-06 11:19:26 -07:00 committed by zeripath
parent 0c840a924a
commit da1edbfb79
9 changed files with 145 additions and 31 deletions

4
Gopkg.lock generated
View File

@ -11,11 +11,11 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:8df1f0527f30a02b76d0ac397118d71c0e9093c7dfa59723762a88fce6ac1170" digest = "1:17c6c3f4af27f721e3176aceeb2ee30621547a44c81ada0ce733170b9bdfee19"
name = "code.gitea.io/sdk" name = "code.gitea.io/sdk"
packages = ["gitea"] packages = ["gitea"]
pruneopts = "NUT" pruneopts = "NUT"
revision = "d5a42771e7e851e8a89c5c6ffa0f5b075342f9df" revision = "b9e72373fbe3001d98ce7395221d0134b9456679"
[[projects]] [[projects]]
digest = "1:5d72bbcc9c8667b11c3dc3cbe681c5a6f71e5096744c0bf7726ab5c6425d5dc4" digest = "1:5d72bbcc9c8667b11c3dc3cbe681c5a6f71e5096744c0bf7726ab5c6425d5dc4"

View File

@ -629,6 +629,8 @@ ENABLE_SWAGGER = true
MAX_RESPONSE_ITEMS = 50 MAX_RESPONSE_ITEMS = 50
; Default paging number of api ; Default paging number of api
DEFAULT_PAGING_NUM = 30 DEFAULT_PAGING_NUM = 30
; Default and maximum number of items per page for git trees api
DEFAULT_GIT_TREES_PER_PAGE = 1000
[i18n] [i18n]
LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR

View File

@ -332,6 +332,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `ENABLE_SWAGGER`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true. - `ENABLE_SWAGGER`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.
- `MAX_RESPONSE_ITEMS`: **50**: Max number of items in a page. - `MAX_RESPONSE_ITEMS`: **50**: Max number of items in a page.
- `DEFAULT_PAGING_NUM`: **30**: Default paging number of api. - `DEFAULT_PAGING_NUM`: **30**: Default paging number of api.
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Default and maximum number of items per page for git trees api.
## i18n (`i18n`) ## i18n (`i18n`)

View File

@ -199,6 +199,7 @@ menu:
- `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 /api/swagger, /api/v1/swagger etc. endpoints. True 或 false; 默认是 true. - `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 /api/swagger, /api/v1/swagger etc. endpoints. True 或 false; 默认是 true.
- `MAX_RESPONSE_ITEMS`: **50**: 一个页面最大的项目数。 - `MAX_RESPONSE_ITEMS`: **50**: 一个页面最大的项目数。
- `DEFAULT_PAGING_NUM`: **30**: API中默认分页条数。 - `DEFAULT_PAGING_NUM`: **30**: API中默认分页条数。
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: GIT TREES API每页的默认和最大项数.
## Markup (`markup`) ## Markup (`markup`)

View File

@ -564,10 +564,12 @@ var (
EnableSwagger bool EnableSwagger bool
MaxResponseItems int MaxResponseItems int
DefaultPagingNum int DefaultPagingNum int
DefaultGitTreesPerPage int
}{ }{
EnableSwagger: true, EnableSwagger: true,
MaxResponseItems: 50, MaxResponseItems: 50,
DefaultPagingNum: 30, DefaultPagingNum: 30,
DefaultGitTreesPerPage: 1000,
} }
U2F = struct { U2F = struct {

View File

@ -37,19 +37,34 @@ func GetTree(ctx *context.APIContext) {
// description: sha of the commit // description: sha of the commit
// type: string // type: string
// required: true // required: true
// - name: recursive
// in: query
// description: show all directories and files
// required: false
// type: boolean
// - name: page
// in: query
// description: page number; the 'truncated' field in the response will be true if there are still more items after this page, false if the last page
// required: false
// type: integer
// - name: per_page
// in: query
// description: number of items per page; default is 1000 or what is set in app.ini as DEFAULT_GIT_TREES_PER_PAGE
// required: false
// type: integer
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/GitTreeResponse" // "$ref": "#/responses/GitTreeResponse"
sha := ctx.Params("sha") sha := ctx.Params("sha")
if len(sha) == 0 { if len(sha) == 0 {
ctx.Error(400, "sha not provided", nil) ctx.Error(400, "", "sha not provided")
return return
} }
tree := GetTreeBySHA(ctx, sha) tree := GetTreeBySHA(ctx, sha)
if tree != nil { if tree != nil {
ctx.JSON(200, tree) ctx.JSON(200, tree)
} else { } else {
ctx.Error(400, "sha invalid", nil) ctx.Error(400, "", "sha invalid")
} }
} }
@ -87,29 +102,44 @@ func GetTreeBySHA(ctx *context.APIContext, sha string) *gitea.GitTreeResponse {
// 40 is the size of the sha1 hash in hexadecimal format. // 40 is the size of the sha1 hash in hexadecimal format.
copyPos := len(treeURL) - 40 copyPos := len(treeURL) - 40
if len(entries) > 1000 { page := ctx.QueryInt("page")
tree.Entries = make([]gitea.GitEntry, 1000) perPage := ctx.QueryInt("per_page")
} else { if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage {
tree.Entries = make([]gitea.GitEntry, len(entries)) perPage = setting.API.DefaultGitTreesPerPage
} }
for e := range entries { if page <= 0 {
if e > 1000 { page = 1
}
tree.Page = page
tree.TotalCount = len(entries)
rangeStart := perPage * (page - 1)
if rangeStart >= len(entries) {
return tree
}
var rangeEnd int
if len(entries) > perPage {
tree.Truncated = true tree.Truncated = true
break
} }
if rangeStart+perPage < len(entries) {
tree.Entries[e].Path = entries[e].Name() rangeEnd = rangeStart + perPage
tree.Entries[e].Mode = fmt.Sprintf("%06x", entries[e].Mode()) } else {
tree.Entries[e].Type = string(entries[e].Type) rangeEnd = len(entries)
tree.Entries[e].Size = entries[e].Size() }
tree.Entries[e].SHA = entries[e].ID.String() tree.Entries = make([]gitea.GitEntry, rangeEnd-rangeStart)
for e := rangeStart; e < rangeEnd; e++ {
i := e - rangeStart
tree.Entries[i].Path = entries[e].Name()
tree.Entries[i].Mode = fmt.Sprintf("%06x", entries[e].Mode())
tree.Entries[i].Type = string(entries[e].Type)
tree.Entries[i].Size = entries[e].Size()
tree.Entries[i].SHA = entries[e].ID.String()
if entries[e].IsDir() { if entries[e].IsDir() {
copy(treeURL[copyPos:], entries[e].ID.String()) copy(treeURL[copyPos:], entries[e].ID.String())
tree.Entries[e].URL = string(treeURL[:]) tree.Entries[i].URL = string(treeURL[:])
} else { } else {
copy(blobURL[copyPos:], entries[e].ID.String()) copy(blobURL[copyPos:], entries[e].ID.String())
tree.Entries[e].URL = string(blobURL[:]) tree.Entries[i].URL = string(blobURL[:])
} }
} }
return tree return tree

View File

@ -0,0 +1,48 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"github.com/stretchr/testify/assert"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/sdk/gitea"
)
func TestGetTreeBySHA(t *testing.T) {
models.PrepareTestEnv(t)
sha := "master"
ctx := test.MockContext(t, "user2/repo1")
ctx.SetParams(":id", "1")
ctx.SetParams(":sha", sha)
test.LoadRepo(t, ctx, 1)
test.LoadRepoCommit(t, ctx)
test.LoadUser(t, ctx, 2)
test.LoadGitRepo(t, ctx)
tree := GetTreeBySHA(&context.APIContext{Context: ctx, Org: nil}, ctx.Params("sha"))
expectedTree := &gitea.GitTreeResponse{
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/65f1bf27bc3bf70f64657658635e66094edbcb4d",
Entries: []gitea.GitEntry{
{
Path: "README.md",
Mode: "100644",
Type: "blob",
Size: 30,
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f",
},
},
Truncated: false,
Page: 1,
TotalCount: 1,
}
assert.EqualValues(t, tree, expectedTree)
}

View File

@ -1775,6 +1775,24 @@
"name": "sha", "name": "sha",
"in": "path", "in": "path",
"required": true "required": true
},
{
"type": "boolean",
"description": "show all directories and files",
"name": "recursive",
"in": "query"
},
{
"type": "integer",
"description": "page number; the 'truncated' field in the response will be true if there are still more items after this page, false if the last page",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "number of items per page; default is 1000 or what is set in app.ini as DEFAULT_GIT_TREES_PER_PAGE",
"name": "per_page",
"in": "query"
} }
], ],
"responses": { "responses": {
@ -7352,10 +7370,20 @@
"description": "GitTreeResponse returns a git tree", "description": "GitTreeResponse returns a git tree",
"type": "object", "type": "object",
"properties": { "properties": {
"page": {
"type": "integer",
"format": "int64",
"x-go-name": "Page"
},
"sha": { "sha": {
"type": "string", "type": "string",
"x-go-name": "SHA" "x-go-name": "SHA"
}, },
"total_count": {
"type": "integer",
"format": "int64",
"x-go-name": "TotalCount"
},
"tree": { "tree": {
"type": "array", "type": "array",
"items": { "items": {

View File

@ -24,6 +24,8 @@ type GitTreeResponse struct {
URL string `json:"url"` URL string `json:"url"`
Entries []GitEntry `json:"tree"` Entries []GitEntry `json:"tree"`
Truncated bool `json:"truncated"` Truncated bool `json:"truncated"`
Page int `json:"page"`
TotalCount int `json:"total_count"`
} }
// GetTrees downloads a file of repository, ref can be branch/tag/commit. // GetTrees downloads a file of repository, ref can be branch/tag/commit.