mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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
This commit is contained in:
		
							
								
								
									
										4
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							| @@ -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" | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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`) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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`) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -561,13 +561,15 @@ var ( | |||||||
|  |  | ||||||
| 	// API settings | 	// API settings | ||||||
| 	API = struct { | 	API = struct { | ||||||
| 		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 { | ||||||
|   | |||||||
| @@ -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.Truncated = true | 	} | ||||||
| 			break | 	tree.Page = page | ||||||
| 		} | 	tree.TotalCount = len(entries) | ||||||
|  | 	rangeStart := perPage * (page - 1) | ||||||
| 		tree.Entries[e].Path = entries[e].Name() | 	if rangeStart >= len(entries) { | ||||||
| 		tree.Entries[e].Mode = fmt.Sprintf("%06x", entries[e].Mode()) | 		return tree | ||||||
| 		tree.Entries[e].Type = string(entries[e].Type) | 	} | ||||||
| 		tree.Entries[e].Size = entries[e].Size() | 	var rangeEnd int | ||||||
| 		tree.Entries[e].SHA = entries[e].ID.String() | 	if len(entries) > perPage { | ||||||
|  | 		tree.Truncated = true | ||||||
|  | 	} | ||||||
|  | 	if rangeStart+perPage < len(entries) { | ||||||
|  | 		rangeEnd = rangeStart + perPage | ||||||
|  | 	} else { | ||||||
|  | 		rangeEnd = len(entries) | ||||||
|  | 	} | ||||||
|  | 	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 | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								routers/api/v1/repo/tree_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								routers/api/v1/repo/tree_test.go
									
									
									
									
									
										Normal 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) | ||||||
|  | } | ||||||
| @@ -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": { | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								vendor/code.gitea.io/sdk/gitea/repo_tree.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								vendor/code.gitea.io/sdk/gitea/repo_tree.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -20,10 +20,12 @@ type GitEntry struct { | |||||||
|  |  | ||||||
| // GitTreeResponse returns a git tree | // GitTreeResponse returns a git tree | ||||||
| type GitTreeResponse struct { | type GitTreeResponse struct { | ||||||
| 	SHA       string     `json:"sha"` | 	SHA        string     `json:"sha"` | ||||||
| 	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. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user