mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 00:20:25 +08:00 
			
		
		
		
	API method to list all commits of a repository (#6408)
* Added API endpoint ListAllCommits (/repos/{owner}/{repo}/git/commits)
Signed-off-by: Mike Schwörer <mailport@mikescher.de>
* Fixed failing drone build
Signed-off-by: Mike Schwörer <mailport@mikescher.de>
* Implemented requested changes (PR reviews)
Signed-off-by: Mike Schwörer <mailport@mikescher.de>
* gofmt
Signed-off-by: Mike Schwörer <mailport@mikescher.de>
* Changed api route from "/repos/{owner}/{repo}/git/commits" to "/repos/{owner}/{repo}/commits"
* Removed unnecessary line
* better error message when git repo is empty
* make generate-swagger
* fixed removed return
* Update routers/api/v1/repo/commits.go
Co-Authored-By: Lauris BH <lauris@nix.lv>
* Update routers/api/v1/repo/commits.go
Co-Authored-By: Lauris BH <lauris@nix.lv>
* go fmt
* Refactored common code into ToCommit()
* made toCommit not exported
* added check for userCache == nil
			
			
This commit is contained in:
		
				
					committed by
					
						
						Antoine GIRARD
					
				
			
			
				
	
			
			
			
						parent
						
							6b3f52fe5f
						
					
				
				
					commit
					042089fbaf
				
			@@ -744,10 +744,13 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		||||
					m.Combo("/:sha").Get(repo.GetCommitStatuses).
 | 
			
		||||
						Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
 | 
			
		||||
				}, reqRepoReader(models.UnitTypeCode))
 | 
			
		||||
				m.Group("/commits/:ref", func() {
 | 
			
		||||
					// TODO: Add m.Get("") for single commit (https://developer.github.com/v3/repos/commits/#get-a-single-commit)
 | 
			
		||||
					m.Get("/status", repo.GetCombinedCommitStatusByRef)
 | 
			
		||||
					m.Get("/statuses", repo.GetCommitStatusesByRef)
 | 
			
		||||
				m.Group("/commits", func() {
 | 
			
		||||
					m.Get("", repo.GetAllCommits)
 | 
			
		||||
					m.Group("/:ref", func() {
 | 
			
		||||
						// TODO: Add m.Get("") for single commit (https://developer.github.com/v3/repos/commits/#get-a-single-commit)
 | 
			
		||||
						m.Get("/status", repo.GetCombinedCommitStatusByRef)
 | 
			
		||||
						m.Get("/statuses", repo.GetCommitStatusesByRef)
 | 
			
		||||
					})
 | 
			
		||||
				}, reqRepoReader(models.UnitTypeCode))
 | 
			
		||||
				m.Group("/git", func() {
 | 
			
		||||
					m.Group("/commits", func() {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
@@ -55,25 +57,186 @@ func GetSingleCommit(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Retrieve author and committer information
 | 
			
		||||
	var apiAuthor, apiCommitter *api.User
 | 
			
		||||
	author, err := models.GetUserByEmail(commit.Author.Email)
 | 
			
		||||
	if err != nil && !models.IsErrUserNotExist(err) {
 | 
			
		||||
		ctx.ServerError("Get user by author email", err)
 | 
			
		||||
	json, err := toCommit(ctx, ctx.Repo.Repository, commit, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("toCommit", err)
 | 
			
		||||
		return
 | 
			
		||||
	} else if err == nil {
 | 
			
		||||
		apiAuthor = author.APIFormat()
 | 
			
		||||
	}
 | 
			
		||||
	// Save one query if the author is also the committer
 | 
			
		||||
	if commit.Committer.Email == commit.Author.Email {
 | 
			
		||||
		apiCommitter = apiAuthor
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(200, json)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAllCommits get all commits via
 | 
			
		||||
func GetAllCommits(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/commits repository repoGetAllCommits
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a list of all commits 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: sha
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: SHA or branch to start listing commits from (usually 'master')
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// - name: page
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: page number of requested commits
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/CommitList"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "409":
 | 
			
		||||
	//     "$ref": "#/responses/EmptyRepository"
 | 
			
		||||
 | 
			
		||||
	if ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
		ctx.JSON(409, 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	page := ctx.QueryInt("page")
 | 
			
		||||
	if page <= 0 {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sha := ctx.Query("sha")
 | 
			
		||||
 | 
			
		||||
	var baseCommit *git.Commit
 | 
			
		||||
	if len(sha) == 0 {
 | 
			
		||||
		// no sha supplied - use default branch
 | 
			
		||||
		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
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// get commit specified by sha
 | 
			
		||||
		baseCommit, err = gitRepo.GetCommit(sha)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("GetCommit", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Total commit count
 | 
			
		||||
	commitsCountTotal, err := baseCommit.CommitsCount()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetCommitsCount", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(git.CommitsRangeSize)))
 | 
			
		||||
 | 
			
		||||
	// Query commits
 | 
			
		||||
	commits, err := baseCommit.CommitsByRange(page)
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
		// Create json struct
 | 
			
		||||
		apiCommits[i], err = toCommit(ctx, ctx.Repo.Repository, commit, userCache)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("toCommit", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.SetLinkHeader(int(commitsCountTotal), git.CommitsRangeSize)
 | 
			
		||||
 | 
			
		||||
	ctx.Header().Set("X-Page", strconv.Itoa(page))
 | 
			
		||||
	ctx.Header().Set("X-PerPage", strconv.Itoa(git.CommitsRangeSize))
 | 
			
		||||
	ctx.Header().Set("X-Total", strconv.FormatInt(commitsCountTotal, 10))
 | 
			
		||||
	ctx.Header().Set("X-PageCount", strconv.Itoa(pageCount))
 | 
			
		||||
	ctx.Header().Set("X-HasMore", strconv.FormatBool(page < pageCount))
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(200, &apiCommits)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Commit, userCache map[string]*models.User) (*api.Commit, error) {
 | 
			
		||||
 | 
			
		||||
	var apiAuthor, apiCommitter *api.User
 | 
			
		||||
 | 
			
		||||
	// Retrieve author and committer information
 | 
			
		||||
 | 
			
		||||
	var cacheAuthor *models.User
 | 
			
		||||
	var ok bool
 | 
			
		||||
	if userCache == nil {
 | 
			
		||||
		cacheAuthor = ((*models.User)(nil))
 | 
			
		||||
		ok = false
 | 
			
		||||
	} else {
 | 
			
		||||
		cacheAuthor, ok = userCache[commit.Author.Email]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok {
 | 
			
		||||
		apiAuthor = cacheAuthor.APIFormat()
 | 
			
		||||
	} else {
 | 
			
		||||
		author, err := models.GetUserByEmail(commit.Author.Email)
 | 
			
		||||
		if err != nil && !models.IsErrUserNotExist(err) {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		} else if err == nil {
 | 
			
		||||
			apiAuthor = author.APIFormat()
 | 
			
		||||
			if userCache != nil {
 | 
			
		||||
				userCache[commit.Author.Email] = author
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cacheCommitter *models.User
 | 
			
		||||
	if userCache == nil {
 | 
			
		||||
		cacheCommitter = ((*models.User)(nil))
 | 
			
		||||
		ok = false
 | 
			
		||||
	} else {
 | 
			
		||||
		cacheCommitter, ok = userCache[commit.Committer.Email]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok {
 | 
			
		||||
		apiCommitter = cacheCommitter.APIFormat()
 | 
			
		||||
	} else {
 | 
			
		||||
		committer, err := models.GetUserByEmail(commit.Committer.Email)
 | 
			
		||||
		if err != nil && !models.IsErrUserNotExist(err) {
 | 
			
		||||
			ctx.ServerError("Get user by committer email", err)
 | 
			
		||||
			return
 | 
			
		||||
			return nil, err
 | 
			
		||||
		} else if err == nil {
 | 
			
		||||
			apiCommitter = committer.APIFormat()
 | 
			
		||||
			if userCache != nil {
 | 
			
		||||
				userCache[commit.Committer.Email] = committer
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -82,23 +245,23 @@ func GetSingleCommit(ctx *context.APIContext) {
 | 
			
		||||
	for i := 0; i < commit.ParentCount(); i++ {
 | 
			
		||||
		sha, _ := commit.ParentID(i)
 | 
			
		||||
		apiParents[i] = &api.CommitMeta{
 | 
			
		||||
			URL: ctx.Repo.Repository.APIURL() + "/git/commits/" + sha.String(),
 | 
			
		||||
			URL: repo.APIURL() + "/git/commits/" + sha.String(),
 | 
			
		||||
			SHA: sha.String(),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(200, &api.Commit{
 | 
			
		||||
	return &api.Commit{
 | 
			
		||||
		CommitMeta: &api.CommitMeta{
 | 
			
		||||
			URL: setting.AppURL + ctx.Link[1:],
 | 
			
		||||
			URL: repo.APIURL() + "/git/commits/" + commit.ID.String(),
 | 
			
		||||
			SHA: commit.ID.String(),
 | 
			
		||||
		},
 | 
			
		||||
		HTMLURL: ctx.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(),
 | 
			
		||||
		HTMLURL: repo.HTMLURL() + "/commit/" + commit.ID.String(),
 | 
			
		||||
		RepoCommit: &api.RepoCommit{
 | 
			
		||||
			URL: setting.AppURL + ctx.Link[1:],
 | 
			
		||||
			URL: repo.APIURL() + "/git/commits/" + commit.ID.String(),
 | 
			
		||||
			Author: &api.CommitUser{
 | 
			
		||||
				Identity: api.Identity{
 | 
			
		||||
					Name:  commit.Author.Name,
 | 
			
		||||
					Email: commit.Author.Email,
 | 
			
		||||
					Name:  commit.Committer.Name,
 | 
			
		||||
					Email: commit.Committer.Email,
 | 
			
		||||
				},
 | 
			
		||||
				Date: commit.Author.When.Format(time.RFC3339),
 | 
			
		||||
			},
 | 
			
		||||
@@ -109,14 +272,14 @@ func GetSingleCommit(ctx *context.APIContext) {
 | 
			
		||||
				},
 | 
			
		||||
				Date: commit.Committer.When.Format(time.RFC3339),
 | 
			
		||||
			},
 | 
			
		||||
			Message: commit.Message(),
 | 
			
		||||
			Message: commit.Summary(),
 | 
			
		||||
			Tree: &api.CommitMeta{
 | 
			
		||||
				URL: ctx.Repo.Repository.APIURL() + "/git/trees/" + commit.ID.String(),
 | 
			
		||||
				URL: repo.APIURL() + "/git/trees/" + commit.ID.String(),
 | 
			
		||||
				SHA: commit.ID.String(),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Author:    apiAuthor,
 | 
			
		||||
		Committer: apiCommitter,
 | 
			
		||||
		Parents:   apiParents,
 | 
			
		||||
	})
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -190,6 +190,35 @@ type swaggerCommit struct {
 | 
			
		||||
	Body api.Commit `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CommitList
 | 
			
		||||
// swagger:response CommitList
 | 
			
		||||
type swaggerCommitList 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 {
 | 
			
		||||
	//in: body
 | 
			
		||||
	Body api.APIError `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FileResponse
 | 
			
		||||
// swagger:response FileResponse
 | 
			
		||||
type swaggerFileResponse struct {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user