You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pull.go 47KB

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