722a7c902d
In investigating #7947 it has become clear that the storage component of go-git repositories needs closing. This PR adds this Close function and adds the Close functions as necessary. In TransferOwnership the ctx.Repo.GitRepo is closed if it is open to help prevent the risk of multiple open files. Fixes #7947
287 lines
7 KiB
Go
287 lines
7 KiB
Go
// Copyright 2018 The Gogs Authors. All rights reserved.
|
|
// 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 (
|
|
"math"
|
|
"strconv"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/models"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
)
|
|
|
|
// GetSingleCommit get a commit via
|
|
func GetSingleCommit(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha} repository repoGetSingleCommit
|
|
// ---
|
|
// summary: Get a single commit 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: path
|
|
// description: the commit hash
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/Commit"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
|
|
if err != nil {
|
|
ctx.ServerError("OpenRepository", err)
|
|
return
|
|
}
|
|
defer gitRepo.Close()
|
|
commit, err := gitRepo.GetCommit(ctx.Params(":sha"))
|
|
if err != nil {
|
|
ctx.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err)
|
|
return
|
|
}
|
|
|
|
json, err := toCommit(ctx, ctx.Repo.Repository, commit, nil)
|
|
if err != nil {
|
|
ctx.ServerError("toCommit", err)
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
defer gitRepo.Close()
|
|
|
|
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) {
|
|
return nil, err
|
|
} else if err == nil {
|
|
apiCommitter = committer.APIFormat()
|
|
if userCache != nil {
|
|
userCache[commit.Committer.Email] = committer
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve parent(s) of the commit
|
|
apiParents := make([]*api.CommitMeta, commit.ParentCount())
|
|
for i := 0; i < commit.ParentCount(); i++ {
|
|
sha, _ := commit.ParentID(i)
|
|
apiParents[i] = &api.CommitMeta{
|
|
URL: repo.APIURL() + "/git/commits/" + sha.String(),
|
|
SHA: sha.String(),
|
|
}
|
|
}
|
|
|
|
return &api.Commit{
|
|
CommitMeta: &api.CommitMeta{
|
|
URL: repo.APIURL() + "/git/commits/" + commit.ID.String(),
|
|
SHA: commit.ID.String(),
|
|
},
|
|
HTMLURL: repo.HTMLURL() + "/commit/" + commit.ID.String(),
|
|
RepoCommit: &api.RepoCommit{
|
|
URL: repo.APIURL() + "/git/commits/" + commit.ID.String(),
|
|
Author: &api.CommitUser{
|
|
Identity: api.Identity{
|
|
Name: commit.Committer.Name,
|
|
Email: commit.Committer.Email,
|
|
},
|
|
Date: commit.Author.When.Format(time.RFC3339),
|
|
},
|
|
Committer: &api.CommitUser{
|
|
Identity: api.Identity{
|
|
Name: commit.Committer.Name,
|
|
Email: commit.Committer.Email,
|
|
},
|
|
Date: commit.Committer.When.Format(time.RFC3339),
|
|
},
|
|
Message: commit.Summary(),
|
|
Tree: &api.CommitMeta{
|
|
URL: repo.APIURL() + "/git/trees/" + commit.ID.String(),
|
|
SHA: commit.ID.String(),
|
|
},
|
|
},
|
|
Author: apiAuthor,
|
|
Committer: apiCommitter,
|
|
Parents: apiParents,
|
|
}, nil
|
|
}
|