mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-05 18:16:17 +00:00
9dc04289aa
before: gitrepo vs git packages after: git package fully handle all git operations by the way, use `WithRepo(repo)` instead of `WithDir(repo.Path)` to hide path details. benefits: 1. remove all unnecessary wrappers, developers no need to struggle with "which package should be used" 2. simplify code, RepositoryFacade can (will) be used everywhere, all "path" details are (will be) hidden
421 lines
11 KiB
Go
421 lines
11 KiB
Go
// Copyright 2018 The Gogs Authors. All rights reserved.
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
issues_model "gitea.dev/models/issues"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/git"
|
|
"gitea.dev/modules/setting"
|
|
api "gitea.dev/modules/structs"
|
|
"gitea.dev/routers/api/v1/utils"
|
|
"gitea.dev/services/context"
|
|
"gitea.dev/services/convert"
|
|
)
|
|
|
|
// GetSingleCommit get a commit via sha
|
|
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: a git ref or commit sha
|
|
// type: string
|
|
// required: true
|
|
// - name: stat
|
|
// in: query
|
|
// description: include diff stats for every commit (disable for speedup, default 'true')
|
|
// type: boolean
|
|
// - name: verification
|
|
// in: query
|
|
// description: include verification for every commit (disable for speedup, default 'true')
|
|
// type: boolean
|
|
// - name: files
|
|
// in: query
|
|
// description: include a list of affected files for every commit (disable for speedup, default 'true')
|
|
// type: boolean
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/Commit"
|
|
// "422":
|
|
// "$ref": "#/responses/validationError"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
sha := ctx.PathParam("sha")
|
|
if !git.IsValidRefPattern(sha) {
|
|
ctx.APIError(http.StatusUnprocessableEntity, "no valid ref or sha: "+sha)
|
|
return
|
|
}
|
|
|
|
getCommit(ctx, sha, convert.ParseCommitOptions(ctx))
|
|
}
|
|
|
|
func getCommit(ctx *context.APIContext, identifier string, toCommitOpts convert.ToCommitOptions) {
|
|
commit, err := ctx.Repo.GitRepo.GetCommit(ctx, identifier)
|
|
if err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
ctx.APIErrorNotFound("commit doesn't exist: " + identifier)
|
|
return
|
|
}
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
json, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil, toCommitOpts)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, 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: path
|
|
// in: query
|
|
// description: filepath of a file/dir
|
|
// type: string
|
|
// - name: since
|
|
// in: query
|
|
// description: Only commits after this date will be returned (ISO 8601 format)
|
|
// type: string
|
|
// format: date-time
|
|
// - name: until
|
|
// in: query
|
|
// description: Only commits before this date will be returned (ISO 8601 format)
|
|
// type: string
|
|
// format: date-time
|
|
// - name: stat
|
|
// in: query
|
|
// description: include diff stats for every commit (disable for speedup, default 'true')
|
|
// type: boolean
|
|
// - name: verification
|
|
// in: query
|
|
// description: include verification for every commit (disable for speedup, default 'true')
|
|
// type: boolean
|
|
// - name: files
|
|
// in: query
|
|
// description: include a list of affected files for every commit (disable for speedup, default 'true')
|
|
// type: boolean
|
|
// - name: page
|
|
// in: query
|
|
// description: page number of results to return (1-based)
|
|
// type: integer
|
|
// - name: limit
|
|
// in: query
|
|
// description: page size of results (ignored if used with 'path')
|
|
// type: integer
|
|
// - name: not
|
|
// in: query
|
|
// description: commits that match the given specifier will not be listed.
|
|
// type: string
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/CommitList"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
// "409":
|
|
// "$ref": "#/responses/EmptyRepository"
|
|
|
|
since := ctx.FormString("since")
|
|
until := ctx.FormString("until")
|
|
|
|
// Validate since/until as ISO 8601 (RFC3339)
|
|
if since != "" {
|
|
if _, err := time.Parse(time.RFC3339, since); err != nil {
|
|
ctx.APIError(http.StatusUnprocessableEntity, "invalid 'since' format, expected ISO 8601 (RFC3339)")
|
|
return
|
|
}
|
|
}
|
|
if until != "" {
|
|
if _, err := time.Parse(time.RFC3339, until); err != nil {
|
|
ctx.APIError(http.StatusUnprocessableEntity, "invalid 'until' format, expected ISO 8601 (RFC3339)")
|
|
return
|
|
}
|
|
}
|
|
|
|
if ctx.Repo.Repository.IsEmpty {
|
|
ctx.JSON(http.StatusConflict, api.APIError{
|
|
Message: "Git Repository is empty.",
|
|
URL: setting.API.SwaggerURL,
|
|
})
|
|
return
|
|
}
|
|
|
|
listOptions := utils.GetListOptions(ctx)
|
|
if listOptions.Page <= 0 {
|
|
listOptions.Page = 1
|
|
}
|
|
|
|
if listOptions.PageSize > setting.Git.CommitsRangeSize {
|
|
listOptions.PageSize = setting.Git.CommitsRangeSize
|
|
}
|
|
|
|
sha := ctx.FormString("sha")
|
|
path := ctx.FormString("path")
|
|
not := ctx.FormString("not")
|
|
|
|
var (
|
|
commitsCountTotal int64
|
|
commits []*git.Commit
|
|
err error
|
|
)
|
|
|
|
if len(path) == 0 {
|
|
var baseCommit *git.Commit
|
|
if len(sha) == 0 {
|
|
// no sha supplied - use default branch
|
|
baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx, ctx.Repo.Repository.DefaultBranch)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
} else {
|
|
// get commit specified by sha
|
|
baseCommit, err = ctx.Repo.GitRepo.GetCommit(ctx, sha)
|
|
if err != nil {
|
|
ctx.APIErrorAuto(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Total commit count
|
|
commitsCountTotal, err = git.CommitsCount(ctx, ctx.Repo.Repository, git.CommitsCountOptions{
|
|
Not: not,
|
|
Revision: []string{baseCommit.ID.String()},
|
|
Since: since,
|
|
Until: until,
|
|
})
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
// Query commits
|
|
commits, err = baseCommit.CommitsByRange(ctx, ctx.Repo.GitRepo, listOptions.Page, listOptions.PageSize, not, since, until)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
} else {
|
|
if len(sha) == 0 {
|
|
sha = ctx.Repo.Repository.DefaultBranch
|
|
}
|
|
|
|
commitsCountTotal, err = git.CommitsCount(ctx, ctx.Repo.Repository,
|
|
git.CommitsCountOptions{
|
|
Not: not,
|
|
Revision: []string{sha},
|
|
RelPath: []string{path},
|
|
Since: since,
|
|
Until: until,
|
|
})
|
|
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
} else if commitsCountTotal == 0 {
|
|
// when date filters are active, a zero count may just mean no
|
|
// commits in the requested range — not that the path is invalid
|
|
if since == "" && until == "" {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
// verify the path actually exists in the revision history
|
|
totalWithoutDate, err := git.CommitsCount(ctx, ctx.Repo.Repository,
|
|
git.CommitsCountOptions{
|
|
Not: not,
|
|
Revision: []string{sha},
|
|
RelPath: []string{path},
|
|
})
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
if totalWithoutDate == 0 {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
}
|
|
|
|
commits, _, err = ctx.Repo.GitRepo.CommitsByFileAndRange(ctx,
|
|
git.CommitsByFileAndRangeOptions{
|
|
Revision: sha,
|
|
File: path,
|
|
Not: not,
|
|
Page: listOptions.Page,
|
|
Since: since,
|
|
Until: until,
|
|
})
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize)))
|
|
userCache := make(map[string]*user_model.User)
|
|
apiCommits := make([]*api.Commit, len(commits))
|
|
|
|
for i, commit := range commits {
|
|
// Create json struct
|
|
apiCommits[i], err = convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache, convert.ParseCommitOptions(ctx))
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.SetLinkHeader(commitsCountTotal, listOptions.PageSize)
|
|
ctx.SetTotalCountHeader(commitsCountTotal)
|
|
|
|
// kept for backwards compatibility
|
|
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
|
|
ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
|
|
ctx.RespHeader().Set("X-Total", strconv.FormatInt(commitsCountTotal, 10))
|
|
ctx.RespHeader().Set("X-PageCount", strconv.Itoa(pageCount))
|
|
ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < pageCount))
|
|
ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-Total", "X-PageCount", "X-HasMore")
|
|
|
|
ctx.JSON(http.StatusOK, &apiCommits)
|
|
}
|
|
|
|
// DownloadCommitDiffOrPatch render a commit's raw diff or patch
|
|
func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha}.{diffType} repository repoDownloadCommitDiffOrPatch
|
|
// ---
|
|
// summary: Get a commit's diff or patch
|
|
// produces:
|
|
// - text/plain
|
|
// 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: SHA of the commit to get
|
|
// type: string
|
|
// required: true
|
|
// - name: diffType
|
|
// in: path
|
|
// description: whether the output is diff or patch
|
|
// type: string
|
|
// enum: [diff, patch]
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/string"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
sha := ctx.PathParam("sha")
|
|
diffType := git.RawDiffType(ctx.PathParam("diffType"))
|
|
|
|
if err := git.GetRawDiff(ctx, ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
ctx.APIErrorNotFound("commit doesn't exist: " + sha)
|
|
return
|
|
}
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// GetCommitPullRequest returns the merged pull request of the commit
|
|
func GetCommitPullRequest(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest
|
|
// ---
|
|
// summary: Get the merged pull request of the commit
|
|
// 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: SHA of the commit to get
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/PullRequest"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.PathParam("sha"))
|
|
if err != nil {
|
|
if issues_model.IsErrPullRequestNotExist(err) {
|
|
ctx.APIError(http.StatusNotFound, err.Error())
|
|
} else {
|
|
ctx.APIErrorInternal(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err = pr.LoadBaseRepo(ctx); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
if err = pr.LoadHeadRepo(ctx); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
|
|
}
|