gitea源码

commits.go 11KB

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