diff --git a/modules/git/commit.go b/modules/git/commit.go index 2ae35c9f5..1f0a573f2 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -293,6 +293,11 @@ func CommitsCount(repoPath, revision string) (int64, error) { return commitsCount(repoPath, []string{revision}, []string{}) } +// CommitsCountByFile returns number of total commits of unitl given revision and file +func CommitsCountByFile(repoPath, revision, file string) (int64, error) { + return commitsCount(repoPath, []string{revision}, []string{file}) +} + // CommitsCount returns number of total commits of until current revision. func (c *Commit) CommitsCount() (int64, error) { return CommitsCount(c.repo.Path, c.ID.String()) @@ -303,6 +308,11 @@ func (c *Commit) CommitsByRange(page, pageSize int) (*list.List, error) { return c.repo.commitsByRange(c.ID, page, pageSize) } +// CommitsByFileAndRange returns the specific page page commits before current revision and file, every page's number default by CommitsRangeSize +func (c *Commit) CommitsByFileAndRange(file string, page, pageSize int) (*list.List, error) { + return c.repo.CommitsByFileAndRange(c.ID.String(), file, page, pageSize) +} + // CommitsBefore returns all the commits before current revision func (c *Commit) CommitsBefore() (*list.List, error) { return c.repo.getCommitsBefore(c.ID) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 8c28d5faa..d6f37349c 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -335,8 +335,8 @@ func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) { } // CommitsByFileAndRange return the commits according revison file and the page -func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) { - stdout, err := NewCommand("log", revision, "--follow", "--skip="+strconv.Itoa((page-1)*50), +func (repo *Repository) CommitsByFileAndRange(revision, file string, page, pageSize int) (*list.List, error) { + stdout, err := NewCommand("log", revision, "--follow", "--skip="+strconv.Itoa((page-1)*pageSize), "--max-count="+strconv.Itoa(CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path) if err != nil { return nil, err @@ -345,8 +345,8 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) ( } // CommitsByFileAndRangeNoFollow return the commits according revison file and the page -func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page int) (*list.List, error) { - stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*50), +func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page, pageSize int) (*list.List, error) { + stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*pageSize), "--max-count="+strconv.Itoa(CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path) if err != nil { return nil, err diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 918dee6ba..a4516c4e8 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -957,6 +957,9 @@ func RegisterRoutes(m *macaron.Macaron) { }) }, reqRepoReader(models.UnitTypeCode)) m.Get("/commits_slice", repo.GetAllCommitsSliceByTime) + m.Group("/file_commits", func() { + m.Get("/*", repo.GetFileAllCommits) + }) m.Group("/git", func() { m.Group("/commits", func() { m.Get("/:sha", repo.GetSingleCommit) diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index ecfd4266c..58aee3c7c 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -84,6 +84,140 @@ func getCommit(ctx *context.APIContext, identifier string) { ctx.JSON(http.StatusOK, json) } +// GetFileCommits get all commits by path on a repository +func GetFileAllCommits(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/file_commits/{filepath} repository repoGetFileAllCommits + // --- + // summary: Get a list of all commits by filepath from a repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: filepath + // in: path + // description: filepath of the file to get + // type: string + // required: true + // - name: sha + // in: query + // description: SHA or branch to start listing commits from (usually 'master') + // type: string + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/FileCommitList" + // "404": + // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/responses/EmptyRepository" + + if ctx.Repo.Repository.IsEmpty { + ctx.JSON(http.StatusConflict, api.APIError{ + Message: "Git Repository is empty.", + URL: setting.API.SwaggerURL, + }) + return + } + + gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return + } + defer gitRepo.Close() + + listOptions := utils.GetListOptions(ctx) + if listOptions.Page <= 0 { + listOptions.Page = 1 + } + + if listOptions.PageSize > git.CommitsRangeSize { + listOptions.PageSize = git.CommitsRangeSize + } + + sha := ctx.Query("sha") + treePath := ctx.Params("*") + var baseCommit *git.Commit + var commitsCountTotal int64 + if len(sha) == 0 { + head, err := gitRepo.GetHEADBranch() + if err != nil { + ctx.ServerError("GetHEADBranch", err) + return + } + + baseCommit, err = gitRepo.GetBranchCommit(head.Name) + if err != nil { + ctx.ServerError("GetCommit", err) + return + } + commitsCountTotal, err = git.CommitsCountByFile(gitRepo.Path, head.Name, treePath) + if err != nil { + ctx.ServerError("CommitsCount", err) + return + } + } else { + baseCommit, err = gitRepo.GetCommit(sha) + if err != nil { + ctx.ServerError("GetCommit", err) + return + } + commitsCountTotal, err = git.CommitsCountByFile(gitRepo.Path, sha, treePath) + if err != nil { + ctx.ServerError("CommitsCount", err) + return + } + } + + pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize))) + + commits, err := baseCommit.CommitsByFileAndRange(treePath, listOptions.Page, listOptions.PageSize) + if err != nil { + ctx.ServerError("CommitsByRange", err) + return + } + + userCache := make(map[string]*models.User) + apiCommits := make([]*api.Commit, commits.Len()) + i := 0 + for commitPointer := commits.Front(); commitPointer != nil; commitPointer = commitPointer.Next() { + commit := commitPointer.Value.(*git.Commit) + apiCommits[i], err = toCommit(ctx, ctx.Repo.Repository, commit, userCache) + if err != nil { + ctx.ServerError("toCommit", err) + return + } + + i++ + } + + ctx.Header().Set("X-Page", strconv.Itoa(listOptions.Page)) + ctx.Header().Set("X-PerPage", strconv.Itoa(listOptions.PageSize)) + ctx.Header().Set("X-Total", strconv.FormatInt(commitsCountTotal, 10)) + ctx.Header().Set("X-PageCount", strconv.Itoa(pageCount)) + ctx.Header().Set("X-HasMore", strconv.FormatBool(listOptions.Page < pageCount)) + ctx.SetLinkHeader(int(commitsCountTotal), listOptions.PageSize) + ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", commitsCountTotal)) + + ctx.JSON(http.StatusOK, apiCommits) +} + // GetAllCommits get all commits via func GetAllCommits(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/commits repository repoGetAllCommits @@ -358,9 +492,9 @@ func GetAllCommitsSliceByTime(ctx *context.APIContext) { } func CommitSplitSlice(CommitsList []api.Commit) []api.CommitsSlice { - // sort by time + // sort by time sort.Sort(api.SortCommit(CommitsList)) - Commits := make([]api.CommitsSlice,0) + Commits := make([]api.CommitsSlice, 0) i := 0 var j int for { @@ -372,18 +506,17 @@ func CommitSplitSlice(CommitsList []api.Commit) []api.CommitsSlice { } // if equal, put commitdata in an array commitDate := CommitsList[i].CommitDate - commitDatalist := CommitsList[i:j] - i = j // variable value + commitDatalist := CommitsList[i:j] + i = j // variable value // get all the values,,,Commits Commits = append(Commits, api.CommitsSlice{ CommitDate: commitDate, - Commits: commitDatalist, + Commits: commitDatalist, }) } return Commits } - func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Commit, userCache map[string]*models.User) (*api.Commit, error) { var apiAuthor, apiCommitter *api.User @@ -483,7 +616,7 @@ func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Comm // add by qiubing // 获取 Graph func GetGraph(ctx *context.APIContext) { - + if ctx.Repo.Repository.IsEmpty { // 项目是否为空 ctx.JSON(http.StatusConflict, api.APIError{ Message: "Git Repository is empty.", diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index 04b5eedbd..4782c57a3 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -275,6 +275,28 @@ type swaggerCommitList struct { Body []api.Commit `json:"body"` } +// FileCommitList +// swagger:response FileCommitList +type swaggerFileCommitList struct { + // The current page + Page int `json:"X-Page"` + + // Commits per page + PerPage int `json:"X-PerPage"` + + // Total commit count + Total int `json:"X-Total"` + + // Total number of pages + PageCount int `json:"X-PageCount"` + + // True if there is another page + HasMore bool `json:"X-HasMore"` + + // in: body + Body []api.Commit `json:"body"` +} + // EmptyRepository // swagger:response EmptyRepository type swaggerEmptyRepository struct { diff --git a/routers/repo/commit.go b/routers/repo/commit.go index 004d4915d..cd1d7eb3c 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -179,7 +179,7 @@ func FileHistory(ctx *context.Context) { page = 1 } - commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(branchName, fileName, page) + commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(branchName, fileName, page, 50) if err != nil { ctx.ServerError("CommitsByFileAndRange", err) return diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index ac650d3fc..7fb8b3631 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -277,7 +277,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) } // get Commit Count - commitsHistory, err := wikiRepo.CommitsByFileAndRangeNoFollow("master", pageFilename, page) + commitsHistory, err := wikiRepo.CommitsByFileAndRangeNoFollow("master", pageFilename, page, 50) if err != nil { if wikiRepo != nil { wikiRepo.Close() diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index adeb06510..d0027bd7b 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -3286,6 +3286,70 @@ } } }, + "/repos/{owner}/{repo}/file_commits/{filepath}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get a list of all commits by filepath from a repository", + "operationId": "repoGetFileAllCommits", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "filepath of the file to get", + "name": "filepath", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "SHA or branch to start listing commits from (usually 'master')", + "name": "sha", + "in": "query" + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/FileCommitList" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "409": { + "$ref": "#/responses/EmptyRepository" + } + } + } + }, "/repos/{owner}/{repo}/find": { "get": { "produces": [ @@ -15370,14 +15434,6 @@ "commit": { "$ref": "#/definitions/CommitMeta" }, - "commiter": { - "$ref": "#/definitions/CommitUser" - }, - "create_unix": { - "type": "string", - "format": "date-time", - "x-go-name": "CreatedUnix" - }, "id": { "type": "string", "x-go-name": "ID" @@ -16001,6 +16057,41 @@ "$ref": "#/definitions/APIError" } }, + "FileCommitList": { + "description": "FileCommitList", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Commit" + } + }, + "headers": { + "X-HasMore": { + "type": "boolean", + "description": "True if there is another page" + }, + "X-Page": { + "type": "integer", + "format": "int64", + "description": "The current page" + }, + "X-PageCount": { + "type": "integer", + "format": "int64", + "description": "Total number of pages" + }, + "X-PerPage": { + "type": "integer", + "format": "int64", + "description": "Commits per page" + }, + "X-Total": { + "type": "integer", + "format": "int64", + "description": "Total commit count" + } + } + }, "FileDeleteResponse": { "description": "FileDeleteResponse", "schema": { @@ -16613,4 +16704,4 @@ "TOTPHeader": [] } ] -} +} \ No newline at end of file