gitea源码

pull.go 52KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660
  1. // Copyright 2018 The Gitea Authors.
  2. // Copyright 2014 The Gogs Authors.
  3. // All rights reserved.
  4. // SPDX-License-Identifier: MIT
  5. package repo
  6. import (
  7. "errors"
  8. "fmt"
  9. "html"
  10. "net/http"
  11. "strconv"
  12. "strings"
  13. "time"
  14. activities_model "code.gitea.io/gitea/models/activities"
  15. "code.gitea.io/gitea/models/db"
  16. git_model "code.gitea.io/gitea/models/git"
  17. issues_model "code.gitea.io/gitea/models/issues"
  18. access_model "code.gitea.io/gitea/models/perm/access"
  19. pull_model "code.gitea.io/gitea/models/pull"
  20. repo_model "code.gitea.io/gitea/models/repo"
  21. "code.gitea.io/gitea/models/unit"
  22. user_model "code.gitea.io/gitea/models/user"
  23. "code.gitea.io/gitea/modules/emoji"
  24. "code.gitea.io/gitea/modules/fileicon"
  25. "code.gitea.io/gitea/modules/git"
  26. "code.gitea.io/gitea/modules/git/gitcmd"
  27. "code.gitea.io/gitea/modules/gitrepo"
  28. "code.gitea.io/gitea/modules/glob"
  29. "code.gitea.io/gitea/modules/graceful"
  30. issue_template "code.gitea.io/gitea/modules/issue/template"
  31. "code.gitea.io/gitea/modules/log"
  32. "code.gitea.io/gitea/modules/setting"
  33. "code.gitea.io/gitea/modules/templates"
  34. "code.gitea.io/gitea/modules/util"
  35. "code.gitea.io/gitea/modules/web"
  36. "code.gitea.io/gitea/routers/utils"
  37. shared_user "code.gitea.io/gitea/routers/web/shared/user"
  38. asymkey_service "code.gitea.io/gitea/services/asymkey"
  39. "code.gitea.io/gitea/services/automerge"
  40. "code.gitea.io/gitea/services/context"
  41. "code.gitea.io/gitea/services/context/upload"
  42. "code.gitea.io/gitea/services/forms"
  43. "code.gitea.io/gitea/services/gitdiff"
  44. notify_service "code.gitea.io/gitea/services/notify"
  45. pull_service "code.gitea.io/gitea/services/pull"
  46. repo_service "code.gitea.io/gitea/services/repository"
  47. user_service "code.gitea.io/gitea/services/user"
  48. )
  49. const (
  50. tplCompareDiff templates.TplName = "repo/diff/compare"
  51. tplPullCommits templates.TplName = "repo/pulls/commits"
  52. tplPullFiles templates.TplName = "repo/pulls/files"
  53. pullRequestTemplateKey = "PullRequestTemplate"
  54. )
  55. var pullRequestTemplateCandidates = []string{
  56. "PULL_REQUEST_TEMPLATE.md",
  57. "PULL_REQUEST_TEMPLATE.yaml",
  58. "PULL_REQUEST_TEMPLATE.yml",
  59. "pull_request_template.md",
  60. "pull_request_template.yaml",
  61. "pull_request_template.yml",
  62. ".gitea/PULL_REQUEST_TEMPLATE.md",
  63. ".gitea/PULL_REQUEST_TEMPLATE.yaml",
  64. ".gitea/PULL_REQUEST_TEMPLATE.yml",
  65. ".gitea/pull_request_template.md",
  66. ".gitea/pull_request_template.yaml",
  67. ".gitea/pull_request_template.yml",
  68. ".github/PULL_REQUEST_TEMPLATE.md",
  69. ".github/PULL_REQUEST_TEMPLATE.yaml",
  70. ".github/PULL_REQUEST_TEMPLATE.yml",
  71. ".github/pull_request_template.md",
  72. ".github/pull_request_template.yaml",
  73. ".github/pull_request_template.yml",
  74. }
  75. func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
  76. repo, err := repo_model.GetRepositoryByID(ctx, repoID)
  77. if err != nil {
  78. if repo_model.IsErrRepoNotExist(err) {
  79. ctx.NotFound(nil)
  80. } else {
  81. ctx.ServerError("GetRepositoryByID", err)
  82. }
  83. return nil
  84. }
  85. perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  86. if err != nil {
  87. ctx.ServerError("GetUserRepoPermission", err)
  88. return nil
  89. }
  90. if !perm.CanRead(unit.TypeCode) {
  91. log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+
  92. "User in repo has Permissions: %-+v",
  93. ctx.Doer,
  94. unit.TypeCode,
  95. ctx.Repo,
  96. perm)
  97. ctx.NotFound(nil)
  98. return nil
  99. }
  100. return repo
  101. }
  102. func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) {
  103. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  104. if err != nil {
  105. if issues_model.IsErrIssueNotExist(err) {
  106. ctx.NotFound(err)
  107. } else {
  108. ctx.ServerError("GetIssueByIndex", err)
  109. }
  110. return nil, false
  111. }
  112. if err = issue.LoadPoster(ctx); err != nil {
  113. ctx.ServerError("LoadPoster", err)
  114. return nil, false
  115. }
  116. if err := issue.LoadRepo(ctx); err != nil {
  117. ctx.ServerError("LoadRepo", err)
  118. return nil, false
  119. }
  120. ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, emoji.ReplaceAliases(issue.Title))
  121. ctx.Data["Issue"] = issue
  122. if !issue.IsPull {
  123. ctx.NotFound(nil)
  124. return nil, false
  125. }
  126. if err = issue.LoadPullRequest(ctx); err != nil {
  127. ctx.ServerError("LoadPullRequest", err)
  128. return nil, false
  129. }
  130. if err = issue.PullRequest.LoadHeadRepo(ctx); err != nil {
  131. ctx.ServerError("LoadHeadRepo", err)
  132. return nil, false
  133. }
  134. if ctx.IsSigned {
  135. // Update issue-user.
  136. if err = activities_model.SetIssueReadBy(ctx, issue.ID, ctx.Doer.ID); err != nil {
  137. ctx.ServerError("ReadBy", err)
  138. return nil, false
  139. }
  140. }
  141. return issue, true
  142. }
  143. func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) {
  144. if ctx.Repo.Owner.Name == pull.MustHeadUserName(ctx) {
  145. ctx.Data["HeadTarget"] = pull.HeadBranch
  146. } else if pull.HeadRepo == nil {
  147. ctx.Data["HeadTarget"] = pull.MustHeadUserName(ctx) + ":" + pull.HeadBranch
  148. } else {
  149. ctx.Data["HeadTarget"] = pull.MustHeadUserName(ctx) + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
  150. }
  151. ctx.Data["BaseTarget"] = pull.BaseBranch
  152. headBranchLink := ""
  153. if pull.Flow == issues_model.PullRequestFlowGithub {
  154. b, err := git_model.GetBranch(ctx, pull.HeadRepoID, pull.HeadBranch)
  155. switch {
  156. case err == nil:
  157. if !b.IsDeleted {
  158. headBranchLink = pull.GetHeadBranchLink(ctx)
  159. }
  160. case !git_model.IsErrBranchNotExist(err):
  161. log.Error("GetBranch: %v", err)
  162. }
  163. }
  164. ctx.Data["HeadBranchLink"] = headBranchLink
  165. ctx.Data["BaseBranchLink"] = pull.GetBaseBranchLink(ctx)
  166. }
  167. // GetPullDiffStats get Pull Requests diff stats
  168. func GetPullDiffStats(ctx *context.Context) {
  169. // FIXME: this getPullInfo seems to be a duplicate call with other route handlers
  170. issue, ok := getPullInfo(ctx)
  171. if !ok {
  172. return
  173. }
  174. pull := issue.PullRequest
  175. mergeBaseCommitID := GetMergedBaseCommitID(ctx, issue)
  176. if mergeBaseCommitID == "" {
  177. return // no merge base, do nothing, do not stop the route handler, see below
  178. }
  179. // do not report 500 server error to end users if error occurs, otherwise a PR missing ref won't be able to view.
  180. headCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitHeadRefName())
  181. if err != nil {
  182. log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName())
  183. return
  184. }
  185. diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID)
  186. if err != nil {
  187. log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName())
  188. return
  189. }
  190. ctx.Data["DiffShortStat"] = diffShortStat
  191. }
  192. func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) string {
  193. pull := issue.PullRequest
  194. var baseCommit string
  195. // Some migrated PR won't have any Base SHA and lose history, try to get one
  196. if pull.MergeBase == "" {
  197. var commitSHA, parentCommit string
  198. // If there is a head or a patch file, and it is readable, grab info
  199. commitSHA, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitHeadRefName())
  200. if err != nil {
  201. // Head File does not exist, try the patch
  202. commitSHA, err = ctx.Repo.GitRepo.ReadPatchCommit(pull.Index)
  203. if err == nil {
  204. // Recreate pull head in files for next time
  205. if err := gitrepo.UpdateRef(ctx, ctx.Repo.Repository, pull.GetGitHeadRefName(), commitSHA); err != nil {
  206. log.Error("Could not write head file", err)
  207. }
  208. } else {
  209. // There is no history available
  210. log.Trace("No history file available for PR %d", pull.Index)
  211. }
  212. }
  213. if commitSHA != "" {
  214. // Get immediate parent of the first commit in the patch, grab history back
  215. parentCommit, _, err = gitcmd.NewCommand("rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: ctx.Repo.GitRepo.Path})
  216. if err == nil {
  217. parentCommit = strings.TrimSpace(parentCommit)
  218. }
  219. // Special case on Git < 2.25 that doesn't fail on immediate empty history
  220. if err != nil || parentCommit == "" {
  221. log.Info("No known parent commit for PR %d, error: %v", pull.Index, err)
  222. // bring at least partial history if it can work
  223. parentCommit = commitSHA
  224. }
  225. }
  226. baseCommit = parentCommit
  227. } else {
  228. // Keep an empty history or original commit
  229. baseCommit = pull.MergeBase
  230. }
  231. return baseCommit
  232. }
  233. func preparePullViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_service.CompareInfo {
  234. if !issue.IsPull {
  235. return nil
  236. }
  237. if issue.PullRequest.HasMerged {
  238. return prepareMergedViewPullInfo(ctx, issue)
  239. }
  240. return prepareViewPullInfo(ctx, issue)
  241. }
  242. // prepareMergedViewPullInfo show meta information for a merged pull request view page
  243. func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_service.CompareInfo {
  244. pull := issue.PullRequest
  245. setMergeTarget(ctx, pull)
  246. ctx.Data["HasMerged"] = true
  247. baseCommit := GetMergedBaseCommitID(ctx, issue)
  248. compareInfo, err := pull_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, ctx.Repo.GitRepo,
  249. baseCommit, pull.GetGitHeadRefName(), false, false)
  250. if err != nil {
  251. if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
  252. ctx.Data["IsPullRequestBroken"] = true
  253. ctx.Data["BaseTarget"] = pull.BaseBranch
  254. ctx.Data["NumCommits"] = 0
  255. ctx.Data["NumFiles"] = 0
  256. return nil
  257. }
  258. ctx.ServerError("GetCompareInfo", err)
  259. return nil
  260. }
  261. ctx.Data["NumCommits"] = len(compareInfo.Commits)
  262. ctx.Data["NumFiles"] = compareInfo.NumFiles
  263. if len(compareInfo.Commits) != 0 {
  264. sha := compareInfo.Commits[0].ID.String()
  265. commitStatuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll)
  266. if err != nil {
  267. ctx.ServerError("GetLatestCommitStatus", err)
  268. return nil
  269. }
  270. if !ctx.Repo.CanRead(unit.TypeActions) {
  271. git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
  272. }
  273. if len(commitStatuses) != 0 {
  274. ctx.Data["LatestCommitStatuses"] = commitStatuses
  275. ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
  276. }
  277. }
  278. return compareInfo
  279. }
  280. // prepareViewPullInfo show meta information for a pull request preview page
  281. func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_service.CompareInfo {
  282. ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
  283. repo := ctx.Repo.Repository
  284. pull := issue.PullRequest
  285. if err := pull.LoadHeadRepo(ctx); err != nil {
  286. ctx.ServerError("LoadHeadRepo", err)
  287. return nil
  288. }
  289. if err := pull.LoadBaseRepo(ctx); err != nil {
  290. ctx.ServerError("LoadBaseRepo", err)
  291. return nil
  292. }
  293. setMergeTarget(ctx, pull)
  294. pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pull.BaseBranch)
  295. if err != nil {
  296. ctx.ServerError("LoadProtectedBranch", err)
  297. return nil
  298. }
  299. ctx.Data["EnableStatusCheck"] = pb != nil && pb.EnableStatusCheck
  300. var baseGitRepo *git.Repository
  301. if pull.BaseRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
  302. baseGitRepo = ctx.Repo.GitRepo
  303. } else {
  304. baseGitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo)
  305. if err != nil {
  306. ctx.ServerError("OpenRepository", err)
  307. return nil
  308. }
  309. defer baseGitRepo.Close()
  310. }
  311. if !gitrepo.IsBranchExist(ctx, pull.BaseRepo, pull.BaseBranch) {
  312. ctx.Data["BaseBranchNotExist"] = true
  313. ctx.Data["IsPullRequestBroken"] = true
  314. ctx.Data["BaseTarget"] = pull.BaseBranch
  315. ctx.Data["HeadTarget"] = pull.HeadBranch
  316. sha, err := baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName())
  317. if err != nil {
  318. ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitHeadRefName()), err)
  319. return nil
  320. }
  321. commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
  322. if err != nil {
  323. ctx.ServerError("GetLatestCommitStatus", err)
  324. return nil
  325. }
  326. if !ctx.Repo.CanRead(unit.TypeActions) {
  327. git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
  328. }
  329. if len(commitStatuses) > 0 {
  330. ctx.Data["LatestCommitStatuses"] = commitStatuses
  331. ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
  332. }
  333. compareInfo, err := pull_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
  334. pull.MergeBase, pull.GetGitHeadRefName(), false, false)
  335. if err != nil {
  336. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  337. ctx.Data["IsPullRequestBroken"] = true
  338. ctx.Data["BaseTarget"] = pull.BaseBranch
  339. ctx.Data["NumCommits"] = 0
  340. ctx.Data["NumFiles"] = 0
  341. return nil
  342. }
  343. ctx.ServerError("GetCompareInfo", err)
  344. return nil
  345. }
  346. ctx.Data["NumCommits"] = len(compareInfo.Commits)
  347. ctx.Data["NumFiles"] = compareInfo.NumFiles
  348. return compareInfo
  349. }
  350. var headBranchExist bool
  351. var headBranchSha string
  352. // HeadRepo may be missing
  353. if pull.HeadRepo != nil {
  354. headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pull.HeadRepo)
  355. if err != nil {
  356. ctx.ServerError("RepositoryFromContextOrOpen", err)
  357. return nil
  358. }
  359. defer closer.Close()
  360. if pull.Flow == issues_model.PullRequestFlowGithub {
  361. headBranchExist = gitrepo.IsBranchExist(ctx, pull.HeadRepo, pull.HeadBranch)
  362. } else {
  363. headBranchExist = gitrepo.IsReferenceExist(ctx, pull.BaseRepo, pull.GetGitHeadRefName())
  364. }
  365. if headBranchExist {
  366. if pull.Flow != issues_model.PullRequestFlowGithub {
  367. headBranchSha, err = baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName())
  368. } else {
  369. headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
  370. }
  371. if err != nil {
  372. ctx.ServerError("GetBranchCommitID", err)
  373. return nil
  374. }
  375. }
  376. }
  377. if headBranchExist {
  378. var err error
  379. ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(ctx, pull, ctx.Doer)
  380. if err != nil {
  381. ctx.ServerError("IsUserAllowedToUpdate", err)
  382. return nil
  383. }
  384. ctx.Data["GetCommitMessages"] = pull_service.GetSquashMergeCommitMessages(ctx, pull)
  385. } else {
  386. ctx.Data["GetCommitMessages"] = ""
  387. }
  388. sha, err := baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName())
  389. if err != nil {
  390. if git.IsErrNotExist(err) {
  391. ctx.Data["IsPullRequestBroken"] = true
  392. if pull.IsSameRepo() {
  393. ctx.Data["HeadTarget"] = pull.HeadBranch
  394. } else if pull.HeadRepo == nil {
  395. ctx.Data["HeadTarget"] = ctx.Locale.Tr("repo.pull.deleted_branch", pull.HeadBranch)
  396. } else {
  397. ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch
  398. }
  399. ctx.Data["BaseTarget"] = pull.BaseBranch
  400. ctx.Data["NumCommits"] = 0
  401. ctx.Data["NumFiles"] = 0
  402. return nil
  403. }
  404. ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitHeadRefName()), err)
  405. return nil
  406. }
  407. commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
  408. if err != nil {
  409. ctx.ServerError("GetLatestCommitStatus", err)
  410. return nil
  411. }
  412. if !ctx.Repo.CanRead(unit.TypeActions) {
  413. git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
  414. }
  415. if len(commitStatuses) > 0 {
  416. ctx.Data["LatestCommitStatuses"] = commitStatuses
  417. ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
  418. }
  419. if pb != nil && pb.EnableStatusCheck {
  420. var missingRequiredChecks []string
  421. for _, requiredContext := range pb.StatusCheckContexts {
  422. contextFound := false
  423. matchesRequiredContext := createRequiredContextMatcher(requiredContext)
  424. for _, presentStatus := range commitStatuses {
  425. if matchesRequiredContext(presentStatus.Context) {
  426. contextFound = true
  427. break
  428. }
  429. }
  430. if !contextFound {
  431. missingRequiredChecks = append(missingRequiredChecks, requiredContext)
  432. }
  433. }
  434. ctx.Data["MissingRequiredChecks"] = missingRequiredChecks
  435. ctx.Data["is_context_required"] = func(context string) bool {
  436. for _, c := range pb.StatusCheckContexts {
  437. if c == context {
  438. return true
  439. }
  440. if gp, err := glob.Compile(c); err != nil {
  441. // All newly created status_check_contexts are checked to ensure they are valid glob expressions before being stored in the database.
  442. // But some old status_check_context created before glob was introduced may be invalid glob expressions.
  443. // So log the error here for debugging.
  444. log.Error("compile glob %q: %v", c, err)
  445. } else if gp.Match(context) {
  446. return true
  447. }
  448. }
  449. return false
  450. }
  451. ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pb.StatusCheckContexts)
  452. }
  453. ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
  454. ctx.Data["HeadBranchCommitID"] = headBranchSha
  455. ctx.Data["PullHeadCommitID"] = sha
  456. if pull.HeadRepo == nil || !headBranchExist || (!pull.Issue.IsClosed && (headBranchSha != sha)) {
  457. ctx.Data["IsPullRequestBroken"] = true
  458. if pull.IsSameRepo() {
  459. ctx.Data["HeadTarget"] = pull.HeadBranch
  460. } else if pull.HeadRepo == nil {
  461. ctx.Data["HeadTarget"] = ctx.Locale.Tr("repo.pull.deleted_branch", pull.HeadBranch)
  462. } else {
  463. ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch
  464. }
  465. }
  466. compareInfo, err := pull_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
  467. git.BranchPrefix+pull.BaseBranch, pull.GetGitHeadRefName(), false, false)
  468. if err != nil {
  469. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  470. ctx.Data["IsPullRequestBroken"] = true
  471. ctx.Data["BaseTarget"] = pull.BaseBranch
  472. ctx.Data["NumCommits"] = 0
  473. ctx.Data["NumFiles"] = 0
  474. return nil
  475. }
  476. ctx.ServerError("GetCompareInfo", err)
  477. return nil
  478. }
  479. if compareInfo.HeadCommitID == compareInfo.MergeBase {
  480. ctx.Data["IsNothingToCompare"] = true
  481. }
  482. if pull.IsWorkInProgress(ctx) {
  483. ctx.Data["IsPullWorkInProgress"] = true
  484. ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix(ctx)
  485. }
  486. if pull.IsFilesConflicted() {
  487. ctx.Data["IsPullFilesConflicted"] = true
  488. ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
  489. }
  490. ctx.Data["NumCommits"] = len(compareInfo.Commits)
  491. ctx.Data["NumFiles"] = compareInfo.NumFiles
  492. return compareInfo
  493. }
  494. func createRequiredContextMatcher(requiredContext string) func(string) bool {
  495. if gp, err := glob.Compile(requiredContext); err == nil {
  496. return func(contextToCheck string) bool {
  497. return gp.Match(contextToCheck)
  498. }
  499. }
  500. return func(contextToCheck string) bool {
  501. return requiredContext == contextToCheck
  502. }
  503. }
  504. type pullCommitList struct {
  505. Commits []pull_service.CommitInfo `json:"commits"`
  506. LastReviewCommitSha string `json:"last_review_commit_sha"`
  507. Locale map[string]any `json:"locale"`
  508. }
  509. // GetPullCommits get all commits for given pull request
  510. func GetPullCommits(ctx *context.Context) {
  511. issue, ok := getPullInfo(ctx)
  512. if !ok {
  513. return
  514. }
  515. resp := &pullCommitList{}
  516. commits, lastReviewCommitSha, err := pull_service.GetPullCommits(ctx, ctx.Repo.GitRepo, ctx.Doer, issue)
  517. if err != nil {
  518. ctx.JSON(http.StatusInternalServerError, err)
  519. return
  520. }
  521. // Get the needed locale
  522. resp.Locale = map[string]any{
  523. "lang": ctx.Locale.Language(),
  524. "show_all_commits": ctx.Tr("repo.pulls.show_all_commits"),
  525. "stats_num_commits": ctx.TrN(len(commits), "repo.activity.git_stats_commit_1", "repo.activity.git_stats_commit_n", len(commits)),
  526. "show_changes_since_your_last_review": ctx.Tr("repo.pulls.show_changes_since_your_last_review"),
  527. "select_commit_hold_shift_for_range": ctx.Tr("repo.pulls.select_commit_hold_shift_for_range"),
  528. }
  529. resp.Commits = commits
  530. resp.LastReviewCommitSha = lastReviewCommitSha
  531. ctx.JSON(http.StatusOK, resp)
  532. }
  533. // ViewPullCommits show commits for a pull request
  534. func ViewPullCommits(ctx *context.Context) {
  535. ctx.Data["PageIsPullList"] = true
  536. ctx.Data["PageIsPullCommits"] = true
  537. issue, ok := getPullInfo(ctx)
  538. if !ok {
  539. return
  540. }
  541. prInfo := preparePullViewPullInfo(ctx, issue)
  542. if ctx.Written() {
  543. return
  544. } else if prInfo == nil {
  545. ctx.NotFound(nil)
  546. return
  547. }
  548. ctx.Data["Username"] = ctx.Repo.Owner.Name
  549. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  550. commits, err := processGitCommits(ctx, prInfo.Commits)
  551. if err != nil {
  552. ctx.ServerError("processGitCommits", err)
  553. return
  554. }
  555. ctx.Data["Commits"] = commits
  556. ctx.Data["CommitCount"] = len(commits)
  557. ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
  558. ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
  559. // For PR commits page
  560. PrepareBranchList(ctx)
  561. if ctx.Written() {
  562. return
  563. }
  564. getBranchData(ctx, issue)
  565. ctx.HTML(http.StatusOK, tplPullCommits)
  566. }
  567. func indexCommit(commits []*git.Commit, commitID string) *git.Commit {
  568. for i := range commits {
  569. if commits[i].ID.String() == commitID {
  570. return commits[i]
  571. }
  572. }
  573. return nil
  574. }
  575. // ViewPullFiles render pull request changed files list page
  576. func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
  577. ctx.Data["PageIsPullList"] = true
  578. ctx.Data["PageIsPullFiles"] = true
  579. issue, ok := getPullInfo(ctx)
  580. if !ok {
  581. return
  582. }
  583. pull := issue.PullRequest
  584. gitRepo := ctx.Repo.GitRepo
  585. prInfo := preparePullViewPullInfo(ctx, issue)
  586. if ctx.Written() {
  587. return
  588. } else if prInfo == nil {
  589. ctx.NotFound(nil)
  590. return
  591. }
  592. headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitHeadRefName())
  593. if err != nil {
  594. ctx.ServerError("GetRefCommitID", err)
  595. return
  596. }
  597. isSingleCommit := beforeCommitID == "" && afterCommitID != ""
  598. ctx.Data["IsShowingOnlySingleCommit"] = isSingleCommit
  599. isShowAllCommits := (beforeCommitID == "" || beforeCommitID == prInfo.MergeBase) && (afterCommitID == "" || afterCommitID == headCommitID)
  600. ctx.Data["IsShowingAllCommits"] = isShowAllCommits
  601. if afterCommitID == "" || afterCommitID == headCommitID {
  602. afterCommitID = headCommitID
  603. }
  604. afterCommit := indexCommit(prInfo.Commits, afterCommitID)
  605. if afterCommit == nil {
  606. ctx.HTTPError(http.StatusBadRequest, "after commit not found in PR commits")
  607. return
  608. }
  609. var beforeCommit *git.Commit
  610. if !isSingleCommit {
  611. if beforeCommitID == "" || beforeCommitID == prInfo.MergeBase {
  612. beforeCommitID = prInfo.MergeBase
  613. // mergebase commit is not in the list of the pull request commits
  614. beforeCommit, err = gitRepo.GetCommit(beforeCommitID)
  615. if err != nil {
  616. ctx.ServerError("GetCommit", err)
  617. return
  618. }
  619. } else {
  620. beforeCommit = indexCommit(prInfo.Commits, beforeCommitID)
  621. if beforeCommit == nil {
  622. ctx.HTTPError(http.StatusBadRequest, "before commit not found in PR commits")
  623. return
  624. }
  625. }
  626. } else {
  627. beforeCommit, err = afterCommit.Parent(0)
  628. if err != nil {
  629. ctx.ServerError("Parent", err)
  630. return
  631. }
  632. beforeCommitID = beforeCommit.ID.String()
  633. }
  634. ctx.Data["Username"] = ctx.Repo.Owner.Name
  635. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  636. ctx.Data["MergeBase"] = prInfo.MergeBase
  637. ctx.Data["AfterCommitID"] = afterCommitID
  638. ctx.Data["BeforeCommitID"] = beforeCommitID
  639. maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
  640. files := ctx.FormStrings("files")
  641. fileOnly := ctx.FormBool("file-only")
  642. if fileOnly && (len(files) == 2 || len(files) == 1) {
  643. maxLines, maxFiles = -1, -1
  644. }
  645. diffOptions := &gitdiff.DiffOptions{
  646. BeforeCommitID: beforeCommitID,
  647. AfterCommitID: afterCommitID,
  648. SkipTo: ctx.FormString("skip-to"),
  649. MaxLines: maxLines,
  650. MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
  651. MaxFiles: maxFiles,
  652. WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
  653. }
  654. diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, diffOptions, files...)
  655. if err != nil {
  656. ctx.ServerError("GetDiff", err)
  657. return
  658. }
  659. // if we're not logged in or only a single commit (or commit range) is shown we
  660. // have to load only the diff and not get the viewed information
  661. // as the viewed information is designed to be loaded only on latest PR
  662. // diff and if you're signed in.
  663. var reviewState *pull_model.ReviewState
  664. if ctx.IsSigned && isShowAllCommits {
  665. reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions)
  666. if err != nil {
  667. ctx.ServerError("SyncUserSpecificDiff", err)
  668. return
  669. }
  670. }
  671. diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, beforeCommitID, afterCommitID)
  672. if err != nil {
  673. ctx.ServerError("GetDiffShortStat", err)
  674. return
  675. }
  676. ctx.Data["DiffShortStat"] = diffShortStat
  677. ctx.PageData["prReview"] = map[string]any{
  678. "numberOfFiles": diffShortStat.NumFiles,
  679. "numberOfViewedFiles": diff.NumViewedFiles,
  680. }
  681. if err = diff.LoadComments(ctx, issue, ctx.Doer, ctx.Data["ShowOutdatedComments"].(bool)); err != nil {
  682. ctx.ServerError("LoadComments", err)
  683. return
  684. }
  685. allComments := issues_model.CommentList{}
  686. for _, file := range diff.Files {
  687. for _, section := range file.Sections {
  688. for _, line := range section.Lines {
  689. allComments = append(allComments, line.Comments...)
  690. }
  691. }
  692. }
  693. if err := allComments.LoadAttachments(ctx); err != nil {
  694. ctx.ServerError("LoadAttachments", err)
  695. return
  696. }
  697. pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
  698. if err != nil {
  699. ctx.ServerError("LoadProtectedBranch", err)
  700. return
  701. }
  702. if pb != nil {
  703. glob := pb.GetProtectedFilePatterns()
  704. if len(glob) != 0 {
  705. for _, file := range diff.Files {
  706. file.IsProtected = pb.IsProtectedFile(glob, file.Name)
  707. }
  708. }
  709. }
  710. if !fileOnly {
  711. // note: use mergeBase is set to false because we already have the merge base from the pull request info
  712. diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, beforeCommitID, afterCommitID)
  713. if err != nil {
  714. ctx.ServerError("GetDiffTree", err)
  715. return
  716. }
  717. var filesViewedState map[string]pull_model.ViewedState
  718. if reviewState != nil {
  719. filesViewedState = reviewState.UpdatedFiles
  720. }
  721. renderedIconPool := fileicon.NewRenderedIconPool()
  722. ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, filesViewedState)
  723. ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
  724. ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
  725. ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
  726. }
  727. ctx.Data["Diff"] = diff
  728. ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
  729. if ctx.IsSigned && ctx.Doer != nil {
  730. if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil {
  731. ctx.ServerError("CanMarkConversation", err)
  732. return
  733. }
  734. }
  735. setCompareContext(ctx, beforeCommit, afterCommit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  736. assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
  737. if err != nil {
  738. ctx.ServerError("GetRepoAssignees", err)
  739. return
  740. }
  741. handleMentionableAssigneesAndTeams(ctx, shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers))
  742. if ctx.Written() {
  743. return
  744. }
  745. currentReview, err := issues_model.GetCurrentReview(ctx, ctx.Doer, issue)
  746. if err != nil && !issues_model.IsErrReviewNotExist(err) {
  747. ctx.ServerError("GetCurrentReview", err)
  748. return
  749. }
  750. numPendingCodeComments := int64(0)
  751. if currentReview != nil {
  752. numPendingCodeComments, err = issues_model.CountComments(ctx, &issues_model.FindCommentsOptions{
  753. Type: issues_model.CommentTypeCode,
  754. ReviewID: currentReview.ID,
  755. IssueID: issue.ID,
  756. })
  757. if err != nil {
  758. ctx.ServerError("CountComments", err)
  759. return
  760. }
  761. }
  762. ctx.Data["CurrentReview"] = currentReview
  763. ctx.Data["PendingCodeCommentNumber"] = numPendingCodeComments
  764. getBranchData(ctx, issue)
  765. ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
  766. ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
  767. ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
  768. // For files changed page
  769. PrepareBranchList(ctx)
  770. if ctx.Written() {
  771. return
  772. }
  773. upload.AddUploadContext(ctx, "comment")
  774. ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
  775. return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
  776. }
  777. if isShowAllCommits && pull.Flow == issues_model.PullRequestFlowGithub {
  778. if err := pull.LoadHeadRepo(ctx); err != nil {
  779. ctx.ServerError("LoadHeadRepo", err)
  780. return
  781. }
  782. if pull.HeadRepo != nil {
  783. if !pull.HasMerged && ctx.Doer != nil {
  784. perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
  785. if err != nil {
  786. ctx.ServerError("GetUserRepoPermission", err)
  787. return
  788. }
  789. if perm.CanWrite(unit.TypeCode) || issues_model.CanMaintainerWriteToBranch(ctx, perm, pull.HeadBranch, ctx.Doer) {
  790. ctx.Data["CanEditFile"] = true
  791. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
  792. ctx.Data["HeadRepoLink"] = pull.HeadRepo.Link()
  793. ctx.Data["HeadBranchName"] = pull.HeadBranch
  794. ctx.Data["BackToLink"] = setting.AppSubURL + ctx.Req.URL.RequestURI()
  795. }
  796. }
  797. }
  798. }
  799. ctx.HTML(http.StatusOK, tplPullFiles)
  800. }
  801. func ViewPullFilesForSingleCommit(ctx *context.Context) {
  802. // it doesn't support showing files from mergebase to the special commit
  803. // otherwise it will be ambiguous
  804. viewPullFiles(ctx, "", ctx.PathParam("sha"))
  805. }
  806. func ViewPullFilesForRange(ctx *context.Context) {
  807. viewPullFiles(ctx, ctx.PathParam("shaFrom"), ctx.PathParam("shaTo"))
  808. }
  809. func ViewPullFilesForAllCommitsOfPr(ctx *context.Context) {
  810. viewPullFiles(ctx, "", "")
  811. }
  812. // UpdatePullRequest merge PR's baseBranch into headBranch
  813. func UpdatePullRequest(ctx *context.Context) {
  814. issue, ok := getPullInfo(ctx)
  815. if !ok {
  816. return
  817. }
  818. if issue.IsClosed {
  819. ctx.NotFound(nil)
  820. return
  821. }
  822. if issue.PullRequest.HasMerged {
  823. ctx.NotFound(nil)
  824. return
  825. }
  826. rebase := ctx.FormString("style") == "rebase"
  827. if err := issue.PullRequest.LoadBaseRepo(ctx); err != nil {
  828. ctx.ServerError("LoadBaseRepo", err)
  829. return
  830. }
  831. if err := issue.PullRequest.LoadHeadRepo(ctx); err != nil {
  832. ctx.ServerError("LoadHeadRepo", err)
  833. return
  834. }
  835. allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(ctx, issue.PullRequest, ctx.Doer)
  836. if err != nil {
  837. ctx.ServerError("IsUserAllowedToMerge", err)
  838. return
  839. }
  840. // ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
  841. if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) {
  842. ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
  843. ctx.Redirect(issue.Link())
  844. return
  845. }
  846. // default merge commit message
  847. message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
  848. // The update process should not be cancelled by the user
  849. // so we set the context to be a background context
  850. if err = pull_service.Update(graceful.GetManager().ShutdownContext(), issue.PullRequest, ctx.Doer, message, rebase); err != nil {
  851. if pull_service.IsErrMergeConflicts(err) {
  852. conflictError := err.(pull_service.ErrMergeConflicts)
  853. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  854. "Message": ctx.Tr("repo.pulls.merge_conflict"),
  855. "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"),
  856. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  857. })
  858. if err != nil {
  859. ctx.ServerError("UpdatePullRequest.HTMLString", err)
  860. return
  861. }
  862. ctx.Flash.Error(flashError)
  863. ctx.Redirect(issue.Link())
  864. return
  865. } else if pull_service.IsErrRebaseConflicts(err) {
  866. conflictError := err.(pull_service.ErrRebaseConflicts)
  867. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  868. "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
  869. "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
  870. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  871. })
  872. if err != nil {
  873. ctx.ServerError("UpdatePullRequest.HTMLString", err)
  874. return
  875. }
  876. ctx.Flash.Error(flashError)
  877. ctx.Redirect(issue.Link())
  878. return
  879. }
  880. ctx.Flash.Error(err.Error())
  881. ctx.Redirect(issue.Link())
  882. return
  883. }
  884. time.Sleep(1 * time.Second)
  885. ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success"))
  886. ctx.Redirect(issue.Link())
  887. }
  888. // MergePullRequest response for merging pull request
  889. func MergePullRequest(ctx *context.Context) {
  890. form := web.GetForm(ctx).(*forms.MergePullRequestForm)
  891. issue, ok := getPullInfo(ctx)
  892. if !ok {
  893. return
  894. }
  895. pr := issue.PullRequest
  896. pr.Issue = issue
  897. pr.Issue.Repo = ctx.Repo.Repository
  898. manuallyMerged := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged
  899. mergeCheckType := pull_service.MergeCheckTypeGeneral
  900. if form.MergeWhenChecksSucceed {
  901. mergeCheckType = pull_service.MergeCheckTypeAuto
  902. }
  903. if manuallyMerged {
  904. mergeCheckType = pull_service.MergeCheckTypeManually
  905. }
  906. // start with merging by checking
  907. if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
  908. switch {
  909. case errors.Is(err, pull_service.ErrIsClosed):
  910. if issue.IsPull {
  911. ctx.JSONError(ctx.Tr("repo.pulls.is_closed"))
  912. } else {
  913. ctx.JSONError(ctx.Tr("repo.issues.closed_title"))
  914. }
  915. case errors.Is(err, pull_service.ErrNoPermissionToMerge):
  916. ctx.JSONError(ctx.Tr("repo.pulls.update_not_allowed"))
  917. case errors.Is(err, pull_service.ErrHasMerged):
  918. ctx.JSONError(ctx.Tr("repo.pulls.has_merged"))
  919. case errors.Is(err, pull_service.ErrIsWorkInProgress):
  920. ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip"))
  921. case errors.Is(err, pull_service.ErrNotMergeableState):
  922. ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
  923. case errors.Is(err, pull_service.ErrNotReadyToMerge):
  924. ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
  925. case asymkey_service.IsErrWontSign(err):
  926. ctx.JSONError(err.Error()) // has no translation ...
  927. case errors.Is(err, pull_service.ErrDependenciesLeft):
  928. ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
  929. default:
  930. ctx.ServerError("WebCheck", err)
  931. }
  932. return
  933. }
  934. // handle manually-merged mark
  935. if manuallyMerged {
  936. if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
  937. switch {
  938. case pull_service.IsErrInvalidMergeStyle(err):
  939. ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
  940. case strings.Contains(err.Error(), "Wrong commit ID"):
  941. ctx.JSONError(ctx.Tr("repo.pulls.wrong_commit_id"))
  942. default:
  943. ctx.ServerError("MergedManually", err)
  944. }
  945. return
  946. }
  947. ctx.JSONRedirect(issue.Link())
  948. return
  949. }
  950. message := strings.TrimSpace(form.MergeTitleField)
  951. if len(message) == 0 {
  952. var err error
  953. message, _, err = pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do))
  954. if err != nil {
  955. ctx.ServerError("GetDefaultMergeMessage", err)
  956. return
  957. }
  958. }
  959. form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
  960. if len(form.MergeMessageField) > 0 {
  961. message += "\n\n" + form.MergeMessageField
  962. }
  963. if form.MergeWhenChecksSucceed {
  964. // delete all scheduled auto merges
  965. _ = pull_model.DeleteScheduledAutoMerge(ctx, pr.ID)
  966. // schedule auto merge
  967. scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, form.DeleteBranchAfterMerge)
  968. if err != nil {
  969. ctx.ServerError("ScheduleAutoMerge", err)
  970. return
  971. } else if scheduled {
  972. // nothing more to do ...
  973. ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
  974. ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
  975. return
  976. }
  977. }
  978. if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
  979. if pull_service.IsErrInvalidMergeStyle(err) {
  980. ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
  981. } else if pull_service.IsErrMergeConflicts(err) {
  982. conflictError := err.(pull_service.ErrMergeConflicts)
  983. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  984. "Message": ctx.Tr("repo.editor.merge_conflict"),
  985. "Summary": ctx.Tr("repo.editor.merge_conflict_summary"),
  986. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  987. })
  988. if err != nil {
  989. ctx.ServerError("MergePullRequest.HTMLString", err)
  990. return
  991. }
  992. ctx.Flash.Error(flashError)
  993. ctx.JSONRedirect(issue.Link())
  994. } else if pull_service.IsErrRebaseConflicts(err) {
  995. conflictError := err.(pull_service.ErrRebaseConflicts)
  996. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  997. "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
  998. "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
  999. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  1000. })
  1001. if err != nil {
  1002. ctx.ServerError("MergePullRequest.HTMLString", err)
  1003. return
  1004. }
  1005. ctx.Flash.Error(flashError)
  1006. ctx.JSONRedirect(issue.Link())
  1007. } else if pull_service.IsErrMergeUnrelatedHistories(err) {
  1008. log.Debug("MergeUnrelatedHistories error: %v", err)
  1009. ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
  1010. ctx.JSONRedirect(issue.Link())
  1011. } else if git.IsErrPushOutOfDate(err) {
  1012. log.Debug("MergePushOutOfDate error: %v", err)
  1013. ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date"))
  1014. ctx.JSONRedirect(issue.Link())
  1015. } else if pull_service.IsErrSHADoesNotMatch(err) {
  1016. log.Debug("MergeHeadOutOfDate error: %v", err)
  1017. ctx.Flash.Error(ctx.Tr("repo.pulls.head_out_of_date"))
  1018. ctx.JSONRedirect(issue.Link())
  1019. } else if git.IsErrPushRejected(err) {
  1020. log.Debug("MergePushRejected error: %v", err)
  1021. pushrejErr := err.(*git.ErrPushRejected)
  1022. message := pushrejErr.Message
  1023. if len(message) == 0 {
  1024. ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
  1025. } else {
  1026. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  1027. "Message": ctx.Tr("repo.pulls.push_rejected"),
  1028. "Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
  1029. "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
  1030. })
  1031. if err != nil {
  1032. ctx.ServerError("MergePullRequest.HTMLString", err)
  1033. return
  1034. }
  1035. ctx.Flash.Error(flashError)
  1036. }
  1037. ctx.JSONRedirect(issue.Link())
  1038. } else {
  1039. ctx.ServerError("Merge", err)
  1040. }
  1041. return
  1042. }
  1043. log.Trace("Pull request merged: %d", pr.ID)
  1044. if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
  1045. ctx.ServerError("stopTimerIfAvailable", err)
  1046. return
  1047. }
  1048. log.Trace("Pull request merged: %d", pr.ID)
  1049. if !form.DeleteBranchAfterMerge {
  1050. ctx.JSONRedirect(issue.Link())
  1051. return
  1052. }
  1053. // Don't cleanup when other pr use this branch as head branch
  1054. exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
  1055. if err != nil {
  1056. ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
  1057. return
  1058. }
  1059. if exist {
  1060. ctx.JSONRedirect(issue.Link())
  1061. return
  1062. }
  1063. var headRepo *git.Repository
  1064. if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
  1065. headRepo = ctx.Repo.GitRepo
  1066. } else {
  1067. headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
  1068. if err != nil {
  1069. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
  1070. return
  1071. }
  1072. defer headRepo.Close()
  1073. }
  1074. deleteBranch(ctx, pr, headRepo)
  1075. ctx.JSONRedirect(issue.Link())
  1076. }
  1077. // CancelAutoMergePullRequest cancels a scheduled pr
  1078. func CancelAutoMergePullRequest(ctx *context.Context) {
  1079. issue, ok := getPullInfo(ctx)
  1080. if !ok {
  1081. return
  1082. }
  1083. if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, issue.PullRequest); err != nil {
  1084. if db.IsErrNotExist(err) {
  1085. ctx.Flash.Error(ctx.Tr("repo.pulls.auto_merge_not_scheduled"))
  1086. ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index))
  1087. return
  1088. }
  1089. ctx.ServerError("RemoveScheduledAutoMerge", err)
  1090. return
  1091. }
  1092. ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_canceled_schedule"))
  1093. ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, issue.Index))
  1094. }
  1095. func stopTimerIfAvailable(ctx *context.Context, user *user_model.User, issue *issues_model.Issue) error {
  1096. _, err := issues_model.FinishIssueStopwatch(ctx, user, issue)
  1097. return err
  1098. }
  1099. func PullsNewRedirect(ctx *context.Context) {
  1100. branch := ctx.PathParam("*")
  1101. redirectRepo := ctx.Repo.Repository
  1102. repo := ctx.Repo.Repository
  1103. if repo.IsFork {
  1104. if err := repo.GetBaseRepo(ctx); err != nil {
  1105. ctx.ServerError("GetBaseRepo", err)
  1106. return
  1107. }
  1108. redirectRepo = repo.BaseRepo
  1109. branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
  1110. }
  1111. ctx.Redirect(fmt.Sprintf("%s/compare/%s...%s?expand=1", redirectRepo.Link(), util.PathEscapeSegments(redirectRepo.DefaultBranch), util.PathEscapeSegments(branch)))
  1112. }
  1113. // CompareAndPullRequestPost response for creating pull request
  1114. func CompareAndPullRequestPost(ctx *context.Context) {
  1115. form := web.GetForm(ctx).(*forms.CreateIssueForm)
  1116. ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
  1117. ctx.Data["PageIsComparePull"] = true
  1118. ctx.Data["IsDiffCompare"] = true
  1119. ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
  1120. ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
  1121. upload.AddUploadContext(ctx, "comment")
  1122. ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypePullRequests)
  1123. var (
  1124. repo = ctx.Repo.Repository
  1125. attachments []string
  1126. )
  1127. ci := ParseCompareInfo(ctx)
  1128. if ctx.Written() {
  1129. return
  1130. }
  1131. validateRet := ValidateRepoMetasForNewIssue(ctx, *form, true)
  1132. if ctx.Written() {
  1133. return
  1134. }
  1135. labelIDs, assigneeIDs, milestoneID, projectID := validateRet.LabelIDs, validateRet.AssigneeIDs, validateRet.MilestoneID, validateRet.ProjectID
  1136. if setting.Attachment.Enabled {
  1137. attachments = form.Files
  1138. }
  1139. if ctx.HasError() {
  1140. ctx.JSONError(ctx.GetErrMsg())
  1141. return
  1142. }
  1143. if util.IsEmptyString(form.Title) {
  1144. ctx.JSONError(ctx.Tr("repo.issues.new.title_empty"))
  1145. return
  1146. }
  1147. content := form.Content
  1148. if filename := ctx.Req.Form.Get("template-file"); filename != "" {
  1149. if template, err := issue_template.UnmarshalFromRepo(ctx.Repo.GitRepo, ctx.Repo.Repository.DefaultBranch, filename); err == nil {
  1150. content = issue_template.RenderToMarkdown(template, ctx.Req.Form)
  1151. }
  1152. }
  1153. pullIssue := &issues_model.Issue{
  1154. RepoID: repo.ID,
  1155. Repo: repo,
  1156. Title: form.Title,
  1157. PosterID: ctx.Doer.ID,
  1158. Poster: ctx.Doer,
  1159. MilestoneID: milestoneID,
  1160. IsPull: true,
  1161. Content: content,
  1162. }
  1163. pullRequest := &issues_model.PullRequest{
  1164. HeadRepoID: ci.HeadRepo.ID,
  1165. BaseRepoID: repo.ID,
  1166. HeadBranch: ci.HeadBranch,
  1167. BaseBranch: ci.BaseBranch,
  1168. HeadRepo: ci.HeadRepo,
  1169. BaseRepo: repo,
  1170. MergeBase: ci.CompareInfo.MergeBase,
  1171. Type: issues_model.PullRequestGitea,
  1172. AllowMaintainerEdit: form.AllowMaintainerEdit,
  1173. }
  1174. // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
  1175. // instead of 500.
  1176. prOpts := &pull_service.NewPullRequestOptions{
  1177. Repo: repo,
  1178. Issue: pullIssue,
  1179. LabelIDs: labelIDs,
  1180. AttachmentUUIDs: attachments,
  1181. PullRequest: pullRequest,
  1182. AssigneeIDs: assigneeIDs,
  1183. Reviewers: validateRet.Reviewers,
  1184. TeamReviewers: validateRet.TeamReviewers,
  1185. }
  1186. if err := pull_service.NewPullRequest(ctx, prOpts); err != nil {
  1187. switch {
  1188. case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
  1189. ctx.HTTPError(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
  1190. case git.IsErrPushRejected(err):
  1191. pushrejErr := err.(*git.ErrPushRejected)
  1192. message := pushrejErr.Message
  1193. if len(message) == 0 {
  1194. ctx.JSONError(ctx.Tr("repo.pulls.push_rejected_no_message"))
  1195. return
  1196. }
  1197. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  1198. "Message": ctx.Tr("repo.pulls.push_rejected"),
  1199. "Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
  1200. "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
  1201. })
  1202. if err != nil {
  1203. ctx.ServerError("CompareAndPullRequest.HTMLString", err)
  1204. return
  1205. }
  1206. ctx.JSONError(flashError)
  1207. case errors.Is(err, user_model.ErrBlockedUser):
  1208. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  1209. "Message": ctx.Tr("repo.pulls.push_rejected"),
  1210. "Summary": ctx.Tr("repo.pulls.new.blocked_user"),
  1211. })
  1212. if err != nil {
  1213. ctx.ServerError("CompareAndPullRequest.HTMLString", err)
  1214. return
  1215. }
  1216. ctx.JSONError(flashError)
  1217. case errors.Is(err, issues_model.ErrMustCollaborator):
  1218. flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
  1219. "Message": ctx.Tr("repo.pulls.push_rejected"),
  1220. "Summary": ctx.Tr("repo.pulls.new.must_collaborator"),
  1221. })
  1222. if err != nil {
  1223. ctx.ServerError("CompareAndPullRequest.HTMLString", err)
  1224. return
  1225. }
  1226. ctx.JSONError(flashError)
  1227. default:
  1228. // It's an unexpected error.
  1229. // If it happens, we should add another case to handle it.
  1230. log.Error("Unexpected error of NewPullRequest: %T %s", err, err)
  1231. ctx.ServerError("CompareAndPullRequest", err)
  1232. }
  1233. return
  1234. }
  1235. if projectID > 0 && ctx.Repo.CanWrite(unit.TypeProjects) {
  1236. if err := issues_model.IssueAssignOrRemoveProject(ctx, pullIssue, ctx.Doer, projectID, 0); err != nil {
  1237. if !errors.Is(err, util.ErrPermissionDenied) {
  1238. ctx.ServerError("IssueAssignOrRemoveProject", err)
  1239. return
  1240. }
  1241. }
  1242. }
  1243. log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
  1244. ctx.JSONRedirect(pullIssue.Link())
  1245. }
  1246. // CleanUpPullRequest responses for delete merged branch when PR has been merged
  1247. func CleanUpPullRequest(ctx *context.Context) {
  1248. issue, ok := getPullInfo(ctx)
  1249. if !ok {
  1250. return
  1251. }
  1252. pr := issue.PullRequest
  1253. // Don't cleanup unmerged and unclosed PRs and agit PRs
  1254. if !pr.HasMerged && !issue.IsClosed && pr.Flow != issues_model.PullRequestFlowGithub {
  1255. ctx.NotFound(nil)
  1256. return
  1257. }
  1258. // Don't cleanup when there are other PR's that use this branch as head branch.
  1259. exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
  1260. if err != nil {
  1261. ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
  1262. return
  1263. }
  1264. if exist {
  1265. ctx.NotFound(nil)
  1266. return
  1267. }
  1268. if err := pr.LoadHeadRepo(ctx); err != nil {
  1269. ctx.ServerError("LoadHeadRepo", err)
  1270. return
  1271. } else if pr.HeadRepo == nil {
  1272. // Forked repository has already been deleted
  1273. ctx.NotFound(nil)
  1274. return
  1275. } else if err = pr.LoadBaseRepo(ctx); err != nil {
  1276. ctx.ServerError("LoadBaseRepo", err)
  1277. return
  1278. } else if err = pr.HeadRepo.LoadOwner(ctx); err != nil {
  1279. ctx.ServerError("HeadRepo.LoadOwner", err)
  1280. return
  1281. }
  1282. if err := repo_service.CanDeleteBranch(ctx, pr.HeadRepo, pr.HeadBranch, ctx.Doer); err != nil {
  1283. if errors.Is(err, util.ErrPermissionDenied) {
  1284. ctx.NotFound(nil)
  1285. } else {
  1286. ctx.ServerError("CanDeleteBranch", err)
  1287. }
  1288. return
  1289. }
  1290. fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch
  1291. var gitBaseRepo *git.Repository
  1292. // Assume that the base repo is the current context (almost certainly)
  1293. if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.BaseRepoID && ctx.Repo.GitRepo != nil {
  1294. gitBaseRepo = ctx.Repo.GitRepo
  1295. } else {
  1296. // If not just open it
  1297. gitBaseRepo, err = gitrepo.OpenRepository(ctx, pr.BaseRepo)
  1298. if err != nil {
  1299. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.FullName()), err)
  1300. return
  1301. }
  1302. defer gitBaseRepo.Close()
  1303. }
  1304. // Now assume that the head repo is the same as the base repo (reasonable chance)
  1305. gitRepo := gitBaseRepo
  1306. // But if not: is it the same as the context?
  1307. if pr.BaseRepoID != pr.HeadRepoID && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
  1308. gitRepo = ctx.Repo.GitRepo
  1309. } else if pr.BaseRepoID != pr.HeadRepoID {
  1310. // Otherwise just load it up
  1311. gitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
  1312. if err != nil {
  1313. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
  1314. return
  1315. }
  1316. defer gitRepo.Close()
  1317. }
  1318. defer func() {
  1319. ctx.JSONRedirect(issue.Link())
  1320. }()
  1321. // Check if branch has no new commits
  1322. headCommitID, err := gitBaseRepo.GetRefCommitID(pr.GetGitHeadRefName())
  1323. if err != nil {
  1324. log.Error("GetRefCommitID: %v", err)
  1325. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1326. return
  1327. }
  1328. branchCommitID, err := gitRepo.GetBranchCommitID(pr.HeadBranch)
  1329. if err != nil {
  1330. log.Error("GetBranchCommitID: %v", err)
  1331. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1332. return
  1333. }
  1334. if headCommitID != branchCommitID {
  1335. ctx.Flash.Error(ctx.Tr("repo.branch.delete_branch_has_new_commits", fullBranchName))
  1336. return
  1337. }
  1338. deleteBranch(ctx, pr, gitRepo)
  1339. }
  1340. func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) {
  1341. fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch
  1342. if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch, pr); err != nil {
  1343. switch {
  1344. case git.IsErrBranchNotExist(err):
  1345. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1346. case errors.Is(err, repo_service.ErrBranchIsDefault):
  1347. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1348. case errors.Is(err, git_model.ErrBranchIsProtected):
  1349. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1350. default:
  1351. log.Error("DeleteBranch: %v", err)
  1352. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1353. }
  1354. return
  1355. }
  1356. ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
  1357. }
  1358. // DownloadPullDiff render a pull's raw diff
  1359. func DownloadPullDiff(ctx *context.Context) {
  1360. DownloadPullDiffOrPatch(ctx, false)
  1361. }
  1362. // DownloadPullPatch render a pull's raw patch
  1363. func DownloadPullPatch(ctx *context.Context) {
  1364. DownloadPullDiffOrPatch(ctx, true)
  1365. }
  1366. // DownloadPullDiffOrPatch render a pull's raw diff or patch
  1367. func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) {
  1368. pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  1369. if err != nil {
  1370. if issues_model.IsErrPullRequestNotExist(err) {
  1371. ctx.NotFound(err)
  1372. } else {
  1373. ctx.ServerError("GetPullRequestByIndex", err)
  1374. }
  1375. return
  1376. }
  1377. binary := ctx.FormBool("binary")
  1378. if err := pull_service.DownloadDiffOrPatch(ctx, pr, ctx, patch, binary); err != nil {
  1379. ctx.ServerError("DownloadDiffOrPatch", err)
  1380. return
  1381. }
  1382. }
  1383. // UpdatePullRequestTarget change pull request's target branch
  1384. func UpdatePullRequestTarget(ctx *context.Context) {
  1385. issue := GetActionIssue(ctx)
  1386. if ctx.Written() {
  1387. return
  1388. }
  1389. pr := issue.PullRequest
  1390. if !issue.IsPull {
  1391. ctx.HTTPError(http.StatusNotFound)
  1392. return
  1393. }
  1394. if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) {
  1395. ctx.HTTPError(http.StatusForbidden)
  1396. return
  1397. }
  1398. targetBranch := ctx.FormTrim("target_branch")
  1399. if len(targetBranch) == 0 {
  1400. ctx.HTTPError(http.StatusNoContent)
  1401. return
  1402. }
  1403. if err := pull_service.ChangeTargetBranch(ctx, pr, ctx.Doer, targetBranch); err != nil {
  1404. if issues_model.IsErrPullRequestAlreadyExists(err) {
  1405. err := err.(issues_model.ErrPullRequestAlreadyExists)
  1406. RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
  1407. errorMessage := ctx.Tr("repo.pulls.has_pull_request", html.EscapeString(ctx.Repo.RepoLink+"/pulls/"+strconv.FormatInt(err.IssueID, 10)), html.EscapeString(RepoRelPath), err.IssueID) // FIXME: Creates url inside locale string
  1408. ctx.Flash.Error(errorMessage)
  1409. ctx.JSON(http.StatusConflict, map[string]any{
  1410. "error": err.Error(),
  1411. "user_error": errorMessage,
  1412. })
  1413. } else if issues_model.IsErrIssueIsClosed(err) {
  1414. errorMessage := ctx.Tr("repo.pulls.is_closed")
  1415. ctx.Flash.Error(errorMessage)
  1416. ctx.JSON(http.StatusConflict, map[string]any{
  1417. "error": err.Error(),
  1418. "user_error": errorMessage,
  1419. })
  1420. } else if pull_service.IsErrPullRequestHasMerged(err) {
  1421. errorMessage := ctx.Tr("repo.pulls.has_merged")
  1422. ctx.Flash.Error(errorMessage)
  1423. ctx.JSON(http.StatusConflict, map[string]any{
  1424. "error": err.Error(),
  1425. "user_error": errorMessage,
  1426. })
  1427. } else if git_model.IsErrBranchesEqual(err) {
  1428. errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
  1429. ctx.Flash.Error(errorMessage)
  1430. ctx.JSON(http.StatusBadRequest, map[string]any{
  1431. "error": err.Error(),
  1432. "user_error": errorMessage,
  1433. })
  1434. } else {
  1435. ctx.ServerError("UpdatePullRequestTarget", err)
  1436. }
  1437. return
  1438. }
  1439. notify_service.PullRequestChangeTargetBranch(ctx, ctx.Doer, pr, targetBranch)
  1440. ctx.JSON(http.StatusOK, map[string]any{
  1441. "base_branch": pr.BaseBranch,
  1442. })
  1443. }
  1444. // SetAllowEdits allow edits from maintainers to PRs
  1445. func SetAllowEdits(ctx *context.Context) {
  1446. form := web.GetForm(ctx).(*forms.UpdateAllowEditsForm)
  1447. pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  1448. if err != nil {
  1449. if issues_model.IsErrPullRequestNotExist(err) {
  1450. ctx.NotFound(err)
  1451. } else {
  1452. ctx.ServerError("GetPullRequestByIndex", err)
  1453. }
  1454. return
  1455. }
  1456. if err := pull_service.SetAllowEdits(ctx, ctx.Doer, pr, form.AllowMaintainerEdit); err != nil {
  1457. if errors.Is(err, pull_service.ErrUserHasNoPermissionForAction) {
  1458. ctx.HTTPError(http.StatusForbidden)
  1459. return
  1460. }
  1461. ctx.ServerError("SetAllowEdits", err)
  1462. return
  1463. }
  1464. ctx.JSON(http.StatusOK, map[string]any{
  1465. "allow_maintainer_edit": pr.AllowMaintainerEdit,
  1466. })
  1467. }