code.gitea.io/gitea@v1.21.7/routers/api/v1/repo/commits.go (about)

     1  // Copyright 2018 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"net/http"
    11  	"strconv"
    12  
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/context"
    15  	"code.gitea.io/gitea/modules/git"
    16  	"code.gitea.io/gitea/modules/setting"
    17  	api "code.gitea.io/gitea/modules/structs"
    18  	"code.gitea.io/gitea/routers/api/v1/utils"
    19  	"code.gitea.io/gitea/services/convert"
    20  )
    21  
    22  // GetSingleCommit get a commit via sha
    23  func GetSingleCommit(ctx *context.APIContext) {
    24  	// swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha} repository repoGetSingleCommit
    25  	// ---
    26  	// summary: Get a single commit from a repository
    27  	// produces:
    28  	// - application/json
    29  	// parameters:
    30  	// - name: owner
    31  	//   in: path
    32  	//   description: owner of the repo
    33  	//   type: string
    34  	//   required: true
    35  	// - name: repo
    36  	//   in: path
    37  	//   description: name of the repo
    38  	//   type: string
    39  	//   required: true
    40  	// - name: sha
    41  	//   in: path
    42  	//   description: a git ref or commit sha
    43  	//   type: string
    44  	//   required: true
    45  	// - name: stat
    46  	//   in: query
    47  	//   description: include diff stats for every commit (disable for speedup, default 'true')
    48  	//   type: boolean
    49  	// - name: verification
    50  	//   in: query
    51  	//   description: include verification for every commit (disable for speedup, default 'true')
    52  	//   type: boolean
    53  	// - name: files
    54  	//   in: query
    55  	//   description: include a list of affected files for every commit (disable for speedup, default 'true')
    56  	//   type: boolean
    57  	// responses:
    58  	//   "200":
    59  	//     "$ref": "#/responses/Commit"
    60  	//   "422":
    61  	//     "$ref": "#/responses/validationError"
    62  	//   "404":
    63  	//     "$ref": "#/responses/notFound"
    64  
    65  	sha := ctx.Params(":sha")
    66  	if !git.IsValidRefPattern(sha) {
    67  		ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
    68  		return
    69  	}
    70  
    71  	getCommit(ctx, sha, convert.ParseCommitOptions(ctx))
    72  }
    73  
    74  func getCommit(ctx *context.APIContext, identifier string, toCommitOpts convert.ToCommitOptions) {
    75  	commit, err := ctx.Repo.GitRepo.GetCommit(identifier)
    76  	if err != nil {
    77  		if git.IsErrNotExist(err) {
    78  			ctx.NotFound(identifier)
    79  			return
    80  		}
    81  		ctx.Error(http.StatusInternalServerError, "gitRepo.GetCommit", err)
    82  		return
    83  	}
    84  
    85  	json, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil, toCommitOpts)
    86  	if err != nil {
    87  		ctx.Error(http.StatusInternalServerError, "toCommit", err)
    88  		return
    89  	}
    90  	ctx.JSON(http.StatusOK, json)
    91  }
    92  
    93  // GetAllCommits get all commits via
    94  func GetAllCommits(ctx *context.APIContext) {
    95  	// swagger:operation GET /repos/{owner}/{repo}/commits repository repoGetAllCommits
    96  	// ---
    97  	// summary: Get a list of all commits from a repository
    98  	// produces:
    99  	// - application/json
   100  	// parameters:
   101  	// - name: owner
   102  	//   in: path
   103  	//   description: owner of the repo
   104  	//   type: string
   105  	//   required: true
   106  	// - name: repo
   107  	//   in: path
   108  	//   description: name of the repo
   109  	//   type: string
   110  	//   required: true
   111  	// - name: sha
   112  	//   in: query
   113  	//   description: SHA or branch to start listing commits from (usually 'master')
   114  	//   type: string
   115  	// - name: path
   116  	//   in: query
   117  	//   description: filepath of a file/dir
   118  	//   type: string
   119  	// - name: stat
   120  	//   in: query
   121  	//   description: include diff stats for every commit (disable for speedup, default 'true')
   122  	//   type: boolean
   123  	// - name: verification
   124  	//   in: query
   125  	//   description: include verification for every commit (disable for speedup, default 'true')
   126  	//   type: boolean
   127  	// - name: files
   128  	//   in: query
   129  	//   description: include a list of affected files for every commit (disable for speedup, default 'true')
   130  	//   type: boolean
   131  	// - name: page
   132  	//   in: query
   133  	//   description: page number of results to return (1-based)
   134  	//   type: integer
   135  	// - name: limit
   136  	//   in: query
   137  	//   description: page size of results (ignored if used with 'path')
   138  	//   type: integer
   139  	// - name: not
   140  	//   in: query
   141  	//   description: commits that match the given specifier will not be listed.
   142  	//   type: string
   143  	// responses:
   144  	//   "200":
   145  	//     "$ref": "#/responses/CommitList"
   146  	//   "404":
   147  	//     "$ref": "#/responses/notFound"
   148  	//   "409":
   149  	//     "$ref": "#/responses/EmptyRepository"
   150  
   151  	if ctx.Repo.Repository.IsEmpty {
   152  		ctx.JSON(http.StatusConflict, api.APIError{
   153  			Message: "Git Repository is empty.",
   154  			URL:     setting.API.SwaggerURL,
   155  		})
   156  		return
   157  	}
   158  
   159  	listOptions := utils.GetListOptions(ctx)
   160  	if listOptions.Page <= 0 {
   161  		listOptions.Page = 1
   162  	}
   163  
   164  	if listOptions.PageSize > setting.Git.CommitsRangeSize {
   165  		listOptions.PageSize = setting.Git.CommitsRangeSize
   166  	}
   167  
   168  	sha := ctx.FormString("sha")
   169  	path := ctx.FormString("path")
   170  	not := ctx.FormString("not")
   171  
   172  	var (
   173  		commitsCountTotal int64
   174  		commits           []*git.Commit
   175  		err               error
   176  	)
   177  
   178  	if len(path) == 0 {
   179  		var baseCommit *git.Commit
   180  		if len(sha) == 0 {
   181  			// no sha supplied - use default branch
   182  			head, err := ctx.Repo.GitRepo.GetHEADBranch()
   183  			if err != nil {
   184  				ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err)
   185  				return
   186  			}
   187  
   188  			baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(head.Name)
   189  			if err != nil {
   190  				ctx.Error(http.StatusInternalServerError, "GetCommit", err)
   191  				return
   192  			}
   193  		} else {
   194  			// get commit specified by sha
   195  			baseCommit, err = ctx.Repo.GitRepo.GetCommit(sha)
   196  			if err != nil {
   197  				ctx.Error(http.StatusInternalServerError, "GetCommit", err)
   198  				return
   199  			}
   200  		}
   201  
   202  		// Total commit count
   203  		commitsCountTotal, err = git.CommitsCount(ctx.Repo.GitRepo.Ctx, git.CommitsCountOptions{
   204  			RepoPath: ctx.Repo.GitRepo.Path,
   205  			Not:      not,
   206  			Revision: []string{baseCommit.ID.String()},
   207  		})
   208  
   209  		if err != nil {
   210  			ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err)
   211  			return
   212  		}
   213  
   214  		// Query commits
   215  		commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not)
   216  		if err != nil {
   217  			ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
   218  			return
   219  		}
   220  	} else {
   221  		if len(sha) == 0 {
   222  			sha = ctx.Repo.Repository.DefaultBranch
   223  		}
   224  
   225  		commitsCountTotal, err = git.CommitsCount(ctx,
   226  			git.CommitsCountOptions{
   227  				RepoPath: ctx.Repo.GitRepo.Path,
   228  				Not:      not,
   229  				Revision: []string{sha},
   230  				RelPath:  []string{path},
   231  			})
   232  
   233  		if err != nil {
   234  			ctx.Error(http.StatusInternalServerError, "FileCommitsCount", err)
   235  			return
   236  		} else if commitsCountTotal == 0 {
   237  			ctx.NotFound("FileCommitsCount", nil)
   238  			return
   239  		}
   240  
   241  		commits, err = ctx.Repo.GitRepo.CommitsByFileAndRange(
   242  			git.CommitsByFileAndRangeOptions{
   243  				Revision: sha,
   244  				File:     path,
   245  				Not:      not,
   246  				Page:     listOptions.Page,
   247  			})
   248  
   249  		if err != nil {
   250  			ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err)
   251  			return
   252  		}
   253  	}
   254  
   255  	pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize)))
   256  	userCache := make(map[string]*user_model.User)
   257  	apiCommits := make([]*api.Commit, len(commits))
   258  
   259  	for i, commit := range commits {
   260  		// Create json struct
   261  		apiCommits[i], err = convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache, convert.ParseCommitOptions(ctx))
   262  		if err != nil {
   263  			ctx.Error(http.StatusInternalServerError, "toCommit", err)
   264  			return
   265  		}
   266  	}
   267  
   268  	ctx.SetLinkHeader(int(commitsCountTotal), listOptions.PageSize)
   269  	ctx.SetTotalCountHeader(commitsCountTotal)
   270  
   271  	// kept for backwards compatibility
   272  	ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
   273  	ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
   274  	ctx.RespHeader().Set("X-Total", strconv.FormatInt(commitsCountTotal, 10))
   275  	ctx.RespHeader().Set("X-PageCount", strconv.Itoa(pageCount))
   276  	ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < pageCount))
   277  	ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-Total", "X-PageCount", "X-HasMore")
   278  
   279  	ctx.JSON(http.StatusOK, &apiCommits)
   280  }
   281  
   282  // DownloadCommitDiffOrPatch render a commit's raw diff or patch
   283  func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
   284  	// swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha}.{diffType} repository repoDownloadCommitDiffOrPatch
   285  	// ---
   286  	// summary: Get a commit's diff or patch
   287  	// produces:
   288  	// - text/plain
   289  	// parameters:
   290  	// - name: owner
   291  	//   in: path
   292  	//   description: owner of the repo
   293  	//   type: string
   294  	//   required: true
   295  	// - name: repo
   296  	//   in: path
   297  	//   description: name of the repo
   298  	//   type: string
   299  	//   required: true
   300  	// - name: sha
   301  	//   in: path
   302  	//   description: SHA of the commit to get
   303  	//   type: string
   304  	//   required: true
   305  	// - name: diffType
   306  	//   in: path
   307  	//   description: whether the output is diff or patch
   308  	//   type: string
   309  	//   enum: [diff, patch]
   310  	//   required: true
   311  	// responses:
   312  	//   "200":
   313  	//     "$ref": "#/responses/string"
   314  	//   "404":
   315  	//     "$ref": "#/responses/notFound"
   316  	sha := ctx.Params(":sha")
   317  	diffType := git.RawDiffType(ctx.Params(":diffType"))
   318  
   319  	if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil {
   320  		if git.IsErrNotExist(err) {
   321  			ctx.NotFound(sha)
   322  			return
   323  		}
   324  		ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err)
   325  		return
   326  	}
   327  }