code.gitea.io/gitea@v1.22.3/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  	issues_model "code.gitea.io/gitea/models/issues"
    14  	user_model "code.gitea.io/gitea/models/user"
    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/context"
    20  	"code.gitea.io/gitea/services/convert"
    21  )
    22  
    23  // GetSingleCommit get a commit via sha
    24  func GetSingleCommit(ctx *context.APIContext) {
    25  	// swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha} repository repoGetSingleCommit
    26  	// ---
    27  	// summary: Get a single commit from a repository
    28  	// produces:
    29  	// - application/json
    30  	// parameters:
    31  	// - name: owner
    32  	//   in: path
    33  	//   description: owner of the repo
    34  	//   type: string
    35  	//   required: true
    36  	// - name: repo
    37  	//   in: path
    38  	//   description: name of the repo
    39  	//   type: string
    40  	//   required: true
    41  	// - name: sha
    42  	//   in: path
    43  	//   description: a git ref or commit sha
    44  	//   type: string
    45  	//   required: true
    46  	// - name: stat
    47  	//   in: query
    48  	//   description: include diff stats for every commit (disable for speedup, default 'true')
    49  	//   type: boolean
    50  	// - name: verification
    51  	//   in: query
    52  	//   description: include verification for every commit (disable for speedup, default 'true')
    53  	//   type: boolean
    54  	// - name: files
    55  	//   in: query
    56  	//   description: include a list of affected files for every commit (disable for speedup, default 'true')
    57  	//   type: boolean
    58  	// responses:
    59  	//   "200":
    60  	//     "$ref": "#/responses/Commit"
    61  	//   "422":
    62  	//     "$ref": "#/responses/validationError"
    63  	//   "404":
    64  	//     "$ref": "#/responses/notFound"
    65  
    66  	sha := ctx.Params(":sha")
    67  	if !git.IsValidRefPattern(sha) {
    68  		ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
    69  		return
    70  	}
    71  
    72  	getCommit(ctx, sha, convert.ParseCommitOptions(ctx))
    73  }
    74  
    75  func getCommit(ctx *context.APIContext, identifier string, toCommitOpts convert.ToCommitOptions) {
    76  	commit, err := ctx.Repo.GitRepo.GetCommit(identifier)
    77  	if err != nil {
    78  		if git.IsErrNotExist(err) {
    79  			ctx.NotFound(identifier)
    80  			return
    81  		}
    82  		ctx.Error(http.StatusInternalServerError, "gitRepo.GetCommit", err)
    83  		return
    84  	}
    85  
    86  	json, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil, toCommitOpts)
    87  	if err != nil {
    88  		ctx.Error(http.StatusInternalServerError, "toCommit", err)
    89  		return
    90  	}
    91  	ctx.JSON(http.StatusOK, json)
    92  }
    93  
    94  // GetAllCommits get all commits via
    95  func GetAllCommits(ctx *context.APIContext) {
    96  	// swagger:operation GET /repos/{owner}/{repo}/commits repository repoGetAllCommits
    97  	// ---
    98  	// summary: Get a list of all commits from a repository
    99  	// produces:
   100  	// - application/json
   101  	// parameters:
   102  	// - name: owner
   103  	//   in: path
   104  	//   description: owner of the repo
   105  	//   type: string
   106  	//   required: true
   107  	// - name: repo
   108  	//   in: path
   109  	//   description: name of the repo
   110  	//   type: string
   111  	//   required: true
   112  	// - name: sha
   113  	//   in: query
   114  	//   description: SHA or branch to start listing commits from (usually 'master')
   115  	//   type: string
   116  	// - name: path
   117  	//   in: query
   118  	//   description: filepath of a file/dir
   119  	//   type: string
   120  	// - name: stat
   121  	//   in: query
   122  	//   description: include diff stats for every commit (disable for speedup, default 'true')
   123  	//   type: boolean
   124  	// - name: verification
   125  	//   in: query
   126  	//   description: include verification for every commit (disable for speedup, default 'true')
   127  	//   type: boolean
   128  	// - name: files
   129  	//   in: query
   130  	//   description: include a list of affected files for every commit (disable for speedup, default 'true')
   131  	//   type: boolean
   132  	// - name: page
   133  	//   in: query
   134  	//   description: page number of results to return (1-based)
   135  	//   type: integer
   136  	// - name: limit
   137  	//   in: query
   138  	//   description: page size of results (ignored if used with 'path')
   139  	//   type: integer
   140  	// - name: not
   141  	//   in: query
   142  	//   description: commits that match the given specifier will not be listed.
   143  	//   type: string
   144  	// responses:
   145  	//   "200":
   146  	//     "$ref": "#/responses/CommitList"
   147  	//   "404":
   148  	//     "$ref": "#/responses/notFound"
   149  	//   "409":
   150  	//     "$ref": "#/responses/EmptyRepository"
   151  
   152  	if ctx.Repo.Repository.IsEmpty {
   153  		ctx.JSON(http.StatusConflict, api.APIError{
   154  			Message: "Git Repository is empty.",
   155  			URL:     setting.API.SwaggerURL,
   156  		})
   157  		return
   158  	}
   159  
   160  	listOptions := utils.GetListOptions(ctx)
   161  	if listOptions.Page <= 0 {
   162  		listOptions.Page = 1
   163  	}
   164  
   165  	if listOptions.PageSize > setting.Git.CommitsRangeSize {
   166  		listOptions.PageSize = setting.Git.CommitsRangeSize
   167  	}
   168  
   169  	sha := ctx.FormString("sha")
   170  	path := ctx.FormString("path")
   171  	not := ctx.FormString("not")
   172  
   173  	var (
   174  		commitsCountTotal int64
   175  		commits           []*git.Commit
   176  		err               error
   177  	)
   178  
   179  	if len(path) == 0 {
   180  		var baseCommit *git.Commit
   181  		if len(sha) == 0 {
   182  			// no sha supplied - use default branch
   183  			head, err := ctx.Repo.GitRepo.GetHEADBranch()
   184  			if err != nil {
   185  				ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err)
   186  				return
   187  			}
   188  
   189  			baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(head.Name)
   190  			if err != nil {
   191  				ctx.Error(http.StatusInternalServerError, "GetCommit", err)
   192  				return
   193  			}
   194  		} else {
   195  			// get commit specified by sha
   196  			baseCommit, err = ctx.Repo.GitRepo.GetCommit(sha)
   197  			if err != nil {
   198  				ctx.Error(http.StatusInternalServerError, "GetCommit", err)
   199  				return
   200  			}
   201  		}
   202  
   203  		// Total commit count
   204  		commitsCountTotal, err = git.CommitsCount(ctx.Repo.GitRepo.Ctx, git.CommitsCountOptions{
   205  			RepoPath: ctx.Repo.GitRepo.Path,
   206  			Not:      not,
   207  			Revision: []string{baseCommit.ID.String()},
   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  		if err != nil {
   249  			ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err)
   250  			return
   251  		}
   252  	}
   253  
   254  	pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize)))
   255  	userCache := make(map[string]*user_model.User)
   256  	apiCommits := make([]*api.Commit, len(commits))
   257  
   258  	for i, commit := range commits {
   259  		// Create json struct
   260  		apiCommits[i], err = convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache, convert.ParseCommitOptions(ctx))
   261  		if err != nil {
   262  			ctx.Error(http.StatusInternalServerError, "toCommit", err)
   263  			return
   264  		}
   265  	}
   266  
   267  	ctx.SetLinkHeader(int(commitsCountTotal), listOptions.PageSize)
   268  	ctx.SetTotalCountHeader(commitsCountTotal)
   269  
   270  	// kept for backwards compatibility
   271  	ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
   272  	ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
   273  	ctx.RespHeader().Set("X-Total", strconv.FormatInt(commitsCountTotal, 10))
   274  	ctx.RespHeader().Set("X-PageCount", strconv.Itoa(pageCount))
   275  	ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < pageCount))
   276  	ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-Total", "X-PageCount", "X-HasMore")
   277  
   278  	ctx.JSON(http.StatusOK, &apiCommits)
   279  }
   280  
   281  // DownloadCommitDiffOrPatch render a commit's raw diff or patch
   282  func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
   283  	// swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha}.{diffType} repository repoDownloadCommitDiffOrPatch
   284  	// ---
   285  	// summary: Get a commit's diff or patch
   286  	// produces:
   287  	// - text/plain
   288  	// parameters:
   289  	// - name: owner
   290  	//   in: path
   291  	//   description: owner of the repo
   292  	//   type: string
   293  	//   required: true
   294  	// - name: repo
   295  	//   in: path
   296  	//   description: name of the repo
   297  	//   type: string
   298  	//   required: true
   299  	// - name: sha
   300  	//   in: path
   301  	//   description: SHA of the commit to get
   302  	//   type: string
   303  	//   required: true
   304  	// - name: diffType
   305  	//   in: path
   306  	//   description: whether the output is diff or patch
   307  	//   type: string
   308  	//   enum: [diff, patch]
   309  	//   required: true
   310  	// responses:
   311  	//   "200":
   312  	//     "$ref": "#/responses/string"
   313  	//   "404":
   314  	//     "$ref": "#/responses/notFound"
   315  	sha := ctx.Params(":sha")
   316  	diffType := git.RawDiffType(ctx.Params(":diffType"))
   317  
   318  	if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil {
   319  		if git.IsErrNotExist(err) {
   320  			ctx.NotFound(sha)
   321  			return
   322  		}
   323  		ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err)
   324  		return
   325  	}
   326  }
   327  
   328  // GetCommitPullRequest returns the merged pull request of the commit
   329  func GetCommitPullRequest(ctx *context.APIContext) {
   330  	// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest
   331  	// ---
   332  	// summary: Get the merged pull request of the commit
   333  	// produces:
   334  	// - application/json
   335  	// parameters:
   336  	// - name: owner
   337  	//   in: path
   338  	//   description: owner of the repo
   339  	//   type: string
   340  	//   required: true
   341  	// - name: repo
   342  	//   in: path
   343  	//   description: name of the repo
   344  	//   type: string
   345  	//   required: true
   346  	// - name: sha
   347  	//   in: path
   348  	//   description: SHA of the commit to get
   349  	//   type: string
   350  	//   required: true
   351  	// responses:
   352  	//   "200":
   353  	//     "$ref": "#/responses/PullRequest"
   354  	//   "404":
   355  	//     "$ref": "#/responses/notFound"
   356  
   357  	pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.Params("sha"))
   358  	if err != nil {
   359  		if issues_model.IsErrPullRequestNotExist(err) {
   360  			ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err)
   361  		} else {
   362  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
   363  		}
   364  		return
   365  	}
   366  
   367  	if err = pr.LoadBaseRepo(ctx); err != nil {
   368  		ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
   369  		return
   370  	}
   371  	if err = pr.LoadHeadRepo(ctx); err != nil {
   372  		ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
   373  		return
   374  	}
   375  	ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
   376  }