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 34KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. // Copyright 2018 The Gitea Authors.
  2. // Copyright 2014 The Gogs Authors.
  3. // All rights reserved.
  4. // Use of this source code is governed by a MIT-style
  5. // license that can be found in the LICENSE file.
  6. package repo
  7. import (
  8. "container/list"
  9. "crypto/subtle"
  10. "fmt"
  11. "net/http"
  12. "path"
  13. "strings"
  14. "time"
  15. "code.gitea.io/gitea/models"
  16. "code.gitea.io/gitea/modules/auth"
  17. "code.gitea.io/gitea/modules/base"
  18. "code.gitea.io/gitea/modules/context"
  19. "code.gitea.io/gitea/modules/git"
  20. "code.gitea.io/gitea/modules/log"
  21. "code.gitea.io/gitea/modules/notification"
  22. "code.gitea.io/gitea/modules/repofiles"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/util"
  25. "code.gitea.io/gitea/routers/utils"
  26. "code.gitea.io/gitea/services/gitdiff"
  27. pull_service "code.gitea.io/gitea/services/pull"
  28. repo_service "code.gitea.io/gitea/services/repository"
  29. "github.com/unknwon/com"
  30. )
  31. const (
  32. tplFork base.TplName = "repo/pulls/fork"
  33. tplCompareDiff base.TplName = "repo/diff/compare"
  34. tplPullCommits base.TplName = "repo/pulls/commits"
  35. tplPullFiles base.TplName = "repo/pulls/files"
  36. pullRequestTemplateKey = "PullRequestTemplate"
  37. )
  38. var (
  39. pullRequestTemplateCandidates = []string{
  40. "PULL_REQUEST_TEMPLATE.md",
  41. "pull_request_template.md",
  42. ".gitea/PULL_REQUEST_TEMPLATE.md",
  43. ".gitea/pull_request_template.md",
  44. ".github/PULL_REQUEST_TEMPLATE.md",
  45. ".github/pull_request_template.md",
  46. }
  47. )
  48. func getRepository(ctx *context.Context, repoID int64) *models.Repository {
  49. repo, err := models.GetRepositoryByID(repoID)
  50. if err != nil {
  51. if models.IsErrRepoNotExist(err) {
  52. ctx.NotFound("GetRepositoryByID", nil)
  53. } else {
  54. ctx.ServerError("GetRepositoryByID", err)
  55. }
  56. return nil
  57. }
  58. perm, err := models.GetUserRepoPermission(repo, ctx.User)
  59. if err != nil {
  60. ctx.ServerError("GetUserRepoPermission", err)
  61. return nil
  62. }
  63. if !perm.CanRead(models.UnitTypeCode) {
  64. log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+
  65. "User in repo has Permissions: %-+v",
  66. ctx.User,
  67. models.UnitTypeCode,
  68. ctx.Repo,
  69. perm)
  70. ctx.NotFound("getRepository", nil)
  71. return nil
  72. }
  73. return repo
  74. }
  75. func getForkRepository(ctx *context.Context) *models.Repository {
  76. forkRepo := getRepository(ctx, ctx.ParamsInt64(":repoid"))
  77. if ctx.Written() {
  78. return nil
  79. }
  80. if forkRepo.IsEmpty {
  81. log.Trace("Empty repository %-v", forkRepo)
  82. ctx.NotFound("getForkRepository", nil)
  83. return nil
  84. }
  85. ctx.Data["repo_name"] = forkRepo.Name
  86. ctx.Data["description"] = forkRepo.Description
  87. ctx.Data["IsPrivate"] = forkRepo.IsPrivate
  88. canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID)
  89. if err := forkRepo.GetOwner(); err != nil {
  90. ctx.ServerError("GetOwner", err)
  91. return nil
  92. }
  93. ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name
  94. ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID
  95. if err := ctx.User.GetOwnedOrganizations(); err != nil {
  96. ctx.ServerError("GetOwnedOrganizations", err)
  97. return nil
  98. }
  99. var orgs []*models.User
  100. for _, org := range ctx.User.OwnedOrgs {
  101. if forkRepo.OwnerID != org.ID && !org.HasForkedRepo(forkRepo.ID) {
  102. orgs = append(orgs, org)
  103. }
  104. }
  105. var traverseParentRepo = forkRepo
  106. var err error
  107. for {
  108. if ctx.User.ID == traverseParentRepo.OwnerID {
  109. canForkToUser = false
  110. } else {
  111. for i, org := range orgs {
  112. if org.ID == traverseParentRepo.OwnerID {
  113. orgs = append(orgs[:i], orgs[i+1:]...)
  114. break
  115. }
  116. }
  117. }
  118. if !traverseParentRepo.IsFork {
  119. break
  120. }
  121. traverseParentRepo, err = models.GetRepositoryByID(traverseParentRepo.ForkID)
  122. if err != nil {
  123. ctx.ServerError("GetRepositoryByID", err)
  124. return nil
  125. }
  126. }
  127. ctx.Data["CanForkToUser"] = canForkToUser
  128. ctx.Data["Orgs"] = orgs
  129. if canForkToUser {
  130. ctx.Data["ContextUser"] = ctx.User
  131. } else if len(orgs) > 0 {
  132. ctx.Data["ContextUser"] = orgs[0]
  133. }
  134. return forkRepo
  135. }
  136. // Fork render repository fork page
  137. func Fork(ctx *context.Context) {
  138. ctx.Data["Title"] = ctx.Tr("new_fork")
  139. getForkRepository(ctx)
  140. if ctx.Written() {
  141. return
  142. }
  143. ctx.HTML(200, tplFork)
  144. }
  145. // ForkPost response for forking a repository
  146. func ForkPost(ctx *context.Context, form auth.CreateRepoForm) {
  147. ctx.Data["Title"] = ctx.Tr("new_fork")
  148. ctxUser := checkContextUser(ctx, form.UID)
  149. if ctx.Written() {
  150. return
  151. }
  152. forkRepo := getForkRepository(ctx)
  153. if ctx.Written() {
  154. return
  155. }
  156. ctx.Data["ContextUser"] = ctxUser
  157. if ctx.HasError() {
  158. ctx.HTML(200, tplFork)
  159. return
  160. }
  161. var err error
  162. var traverseParentRepo = forkRepo
  163. for {
  164. if ctxUser.ID == traverseParentRepo.OwnerID {
  165. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
  166. return
  167. }
  168. repo, has := models.HasForkedRepo(ctxUser.ID, traverseParentRepo.ID)
  169. if has {
  170. ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
  171. return
  172. }
  173. if !traverseParentRepo.IsFork {
  174. break
  175. }
  176. traverseParentRepo, err = models.GetRepositoryByID(traverseParentRepo.ForkID)
  177. if err != nil {
  178. ctx.ServerError("GetRepositoryByID", err)
  179. return
  180. }
  181. }
  182. // Check ownership of organization.
  183. if ctxUser.IsOrganization() {
  184. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  185. if err != nil {
  186. ctx.ServerError("IsOwnedBy", err)
  187. return
  188. } else if !isOwner {
  189. ctx.Error(403)
  190. return
  191. }
  192. }
  193. repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description)
  194. if err != nil {
  195. ctx.Data["Err_RepoName"] = true
  196. switch {
  197. case models.IsErrRepoAlreadyExist(err):
  198. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
  199. case models.IsErrNameReserved(err):
  200. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplFork, &form)
  201. case models.IsErrNamePatternNotAllowed(err):
  202. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
  203. default:
  204. ctx.ServerError("ForkPost", err)
  205. }
  206. return
  207. }
  208. log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
  209. ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
  210. }
  211. func checkPullInfo(ctx *context.Context) *models.Issue {
  212. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  213. if err != nil {
  214. if models.IsErrIssueNotExist(err) {
  215. ctx.NotFound("GetIssueByIndex", err)
  216. } else {
  217. ctx.ServerError("GetIssueByIndex", err)
  218. }
  219. return nil
  220. }
  221. if err = issue.LoadPoster(); err != nil {
  222. ctx.ServerError("LoadPoster", err)
  223. return nil
  224. }
  225. if err := issue.LoadRepo(); err != nil {
  226. ctx.ServerError("LoadRepo", err)
  227. return nil
  228. }
  229. ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
  230. ctx.Data["Issue"] = issue
  231. if !issue.IsPull {
  232. ctx.NotFound("ViewPullCommits", nil)
  233. return nil
  234. }
  235. if err = issue.LoadPullRequest(); err != nil {
  236. ctx.ServerError("LoadPullRequest", err)
  237. return nil
  238. }
  239. if err = issue.PullRequest.LoadHeadRepo(); err != nil {
  240. ctx.ServerError("LoadHeadRepo", err)
  241. return nil
  242. }
  243. if ctx.IsSigned {
  244. // Update issue-user.
  245. if err = issue.ReadBy(ctx.User.ID); err != nil {
  246. ctx.ServerError("ReadBy", err)
  247. return nil
  248. }
  249. }
  250. return issue
  251. }
  252. func setMergeTarget(ctx *context.Context, pull *models.PullRequest) {
  253. if ctx.Repo.Owner.Name == pull.MustHeadUserName() {
  254. ctx.Data["HeadTarget"] = pull.HeadBranch
  255. } else if pull.HeadRepo == nil {
  256. ctx.Data["HeadTarget"] = pull.MustHeadUserName() + ":" + pull.HeadBranch
  257. } else {
  258. ctx.Data["HeadTarget"] = pull.MustHeadUserName() + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
  259. }
  260. ctx.Data["BaseTarget"] = pull.BaseBranch
  261. }
  262. // PrepareMergedViewPullInfo show meta information for a merged pull request view page
  263. func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
  264. pull := issue.PullRequest
  265. setMergeTarget(ctx, pull)
  266. ctx.Data["HasMerged"] = true
  267. compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(),
  268. pull.MergeBase, pull.GetGitRefName())
  269. if err != nil {
  270. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  271. ctx.Data["IsPullRequestBroken"] = true
  272. ctx.Data["BaseTarget"] = pull.BaseBranch
  273. ctx.Data["NumCommits"] = 0
  274. ctx.Data["NumFiles"] = 0
  275. return nil
  276. }
  277. ctx.ServerError("GetCompareInfo", err)
  278. return nil
  279. }
  280. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  281. ctx.Data["NumFiles"] = compareInfo.NumFiles
  282. return compareInfo
  283. }
  284. // PrepareViewPullInfo show meta information for a pull request preview page
  285. func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
  286. repo := ctx.Repo.Repository
  287. pull := issue.PullRequest
  288. if err := pull.LoadHeadRepo(); err != nil {
  289. ctx.ServerError("LoadHeadRepo", err)
  290. return nil
  291. }
  292. if err := pull.LoadBaseRepo(); err != nil {
  293. ctx.ServerError("LoadBaseRepo", err)
  294. return nil
  295. }
  296. setMergeTarget(ctx, pull)
  297. if err := pull.LoadProtectedBranch(); err != nil {
  298. ctx.ServerError("LoadProtectedBranch", err)
  299. return nil
  300. }
  301. ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
  302. baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath())
  303. if err != nil {
  304. ctx.ServerError("OpenRepository", err)
  305. return nil
  306. }
  307. defer baseGitRepo.Close()
  308. if !baseGitRepo.IsBranchExist(pull.BaseBranch) {
  309. ctx.Data["IsPullRequestBroken"] = true
  310. ctx.Data["BaseTarget"] = pull.BaseBranch
  311. ctx.Data["HeadTarget"] = pull.HeadBranch
  312. sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
  313. if err != nil {
  314. ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
  315. return nil
  316. }
  317. commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
  318. if err != nil {
  319. ctx.ServerError("GetLatestCommitStatus", err)
  320. return nil
  321. }
  322. if len(commitStatuses) > 0 {
  323. ctx.Data["LatestCommitStatuses"] = commitStatuses
  324. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
  325. }
  326. compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
  327. pull.MergeBase, pull.GetGitRefName())
  328. if err != nil {
  329. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  330. ctx.Data["IsPullRequestBroken"] = true
  331. ctx.Data["BaseTarget"] = pull.BaseBranch
  332. ctx.Data["NumCommits"] = 0
  333. ctx.Data["NumFiles"] = 0
  334. return nil
  335. }
  336. ctx.ServerError("GetCompareInfo", err)
  337. return nil
  338. }
  339. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  340. ctx.Data["NumFiles"] = compareInfo.NumFiles
  341. return compareInfo
  342. }
  343. var headBranchExist bool
  344. var headBranchSha string
  345. // HeadRepo may be missing
  346. if pull.HeadRepo != nil {
  347. var err error
  348. headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath())
  349. if err != nil {
  350. ctx.ServerError("OpenRepository", err)
  351. return nil
  352. }
  353. defer headGitRepo.Close()
  354. headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
  355. if headBranchExist {
  356. headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
  357. if err != nil {
  358. ctx.ServerError("GetBranchCommitID", err)
  359. return nil
  360. }
  361. }
  362. }
  363. if headBranchExist {
  364. allowUpdate, err := pull_service.IsUserAllowedToUpdate(pull, ctx.User)
  365. if err != nil {
  366. ctx.ServerError("IsUserAllowedToUpdate", err)
  367. return nil
  368. }
  369. ctx.Data["UpdateAllowed"] = allowUpdate
  370. divergence, err := pull_service.GetDiverging(pull)
  371. if err != nil {
  372. ctx.ServerError("GetDiverging", err)
  373. return nil
  374. }
  375. ctx.Data["Divergence"] = divergence
  376. }
  377. sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
  378. if err != nil {
  379. ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
  380. return nil
  381. }
  382. commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
  383. if err != nil {
  384. ctx.ServerError("GetLatestCommitStatus", err)
  385. return nil
  386. }
  387. if len(commitStatuses) > 0 {
  388. ctx.Data["LatestCommitStatuses"] = commitStatuses
  389. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
  390. }
  391. if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
  392. ctx.Data["is_context_required"] = func(context string) bool {
  393. for _, c := range pull.ProtectedBranch.StatusCheckContexts {
  394. if c == context {
  395. return true
  396. }
  397. }
  398. return false
  399. }
  400. state := pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
  401. ctx.Data["RequiredStatusCheckState"] = state
  402. ctx.Data["IsRequiredStatusCheckSuccess"] = state.IsSuccess()
  403. }
  404. ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
  405. ctx.Data["HeadBranchCommitID"] = headBranchSha
  406. ctx.Data["PullHeadCommitID"] = sha
  407. if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha {
  408. ctx.Data["IsPullRequestBroken"] = true
  409. if pull.IsSameRepo() {
  410. ctx.Data["HeadTarget"] = pull.HeadBranch
  411. } else {
  412. if pull.HeadRepo == nil {
  413. ctx.Data["HeadTarget"] = "<deleted>:" + pull.HeadBranch
  414. } else {
  415. ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch
  416. }
  417. }
  418. }
  419. compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
  420. pull.BaseBranch, pull.GetGitRefName())
  421. if err != nil {
  422. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  423. ctx.Data["IsPullRequestBroken"] = true
  424. ctx.Data["BaseTarget"] = pull.BaseBranch
  425. ctx.Data["NumCommits"] = 0
  426. ctx.Data["NumFiles"] = 0
  427. return nil
  428. }
  429. ctx.ServerError("GetCompareInfo", err)
  430. return nil
  431. }
  432. if pull.IsWorkInProgress() {
  433. ctx.Data["IsPullWorkInProgress"] = true
  434. ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix()
  435. }
  436. if pull.IsFilesConflicted() {
  437. ctx.Data["IsPullFilesConflicted"] = true
  438. ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
  439. }
  440. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  441. ctx.Data["NumFiles"] = compareInfo.NumFiles
  442. return compareInfo
  443. }
  444. // ViewPullCommits show commits for a pull request
  445. func ViewPullCommits(ctx *context.Context) {
  446. ctx.Data["PageIsPullList"] = true
  447. ctx.Data["PageIsPullCommits"] = true
  448. issue := checkPullInfo(ctx)
  449. if ctx.Written() {
  450. return
  451. }
  452. pull := issue.PullRequest
  453. var commits *list.List
  454. var prInfo *git.CompareInfo
  455. if pull.HasMerged {
  456. prInfo = PrepareMergedViewPullInfo(ctx, issue)
  457. } else {
  458. prInfo = PrepareViewPullInfo(ctx, issue)
  459. }
  460. if ctx.Written() {
  461. return
  462. } else if prInfo == nil {
  463. ctx.NotFound("ViewPullCommits", nil)
  464. return
  465. }
  466. ctx.Data["Username"] = ctx.Repo.Owner.Name
  467. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  468. commits = prInfo.Commits
  469. commits = models.ValidateCommitsWithEmails(commits)
  470. commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository)
  471. commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
  472. ctx.Data["Commits"] = commits
  473. ctx.Data["CommitCount"] = commits.Len()
  474. getBranchData(ctx, issue)
  475. ctx.HTML(200, tplPullCommits)
  476. }
  477. // ViewPullFiles render pull request changed files list page
  478. func ViewPullFiles(ctx *context.Context) {
  479. ctx.Data["PageIsPullList"] = true
  480. ctx.Data["PageIsPullFiles"] = true
  481. issue := checkPullInfo(ctx)
  482. if ctx.Written() {
  483. return
  484. }
  485. pull := issue.PullRequest
  486. whitespaceFlags := map[string]string{
  487. "ignore-all": "-w",
  488. "ignore-change": "-b",
  489. "ignore-eol": "--ignore-space-at-eol",
  490. "": ""}
  491. var (
  492. diffRepoPath string
  493. startCommitID string
  494. endCommitID string
  495. gitRepo *git.Repository
  496. )
  497. var headTarget string
  498. var prInfo *git.CompareInfo
  499. if pull.HasMerged {
  500. prInfo = PrepareMergedViewPullInfo(ctx, issue)
  501. } else {
  502. prInfo = PrepareViewPullInfo(ctx, issue)
  503. }
  504. if ctx.Written() {
  505. return
  506. } else if prInfo == nil {
  507. ctx.NotFound("ViewPullFiles", nil)
  508. return
  509. }
  510. diffRepoPath = ctx.Repo.GitRepo.Path
  511. gitRepo = ctx.Repo.GitRepo
  512. headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
  513. if err != nil {
  514. ctx.ServerError("GetRefCommitID", err)
  515. return
  516. }
  517. startCommitID = prInfo.MergeBase
  518. endCommitID = headCommitID
  519. headTarget = path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  520. ctx.Data["Username"] = ctx.Repo.Owner.Name
  521. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  522. ctx.Data["AfterCommitID"] = endCommitID
  523. diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(diffRepoPath,
  524. startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
  525. setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles,
  526. whitespaceFlags[ctx.Data["WhitespaceBehavior"].(string)])
  527. if err != nil {
  528. ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
  529. return
  530. }
  531. if err = diff.LoadComments(issue, ctx.User); err != nil {
  532. ctx.ServerError("LoadComments", err)
  533. return
  534. }
  535. ctx.Data["Diff"] = diff
  536. ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
  537. baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID)
  538. if err != nil {
  539. ctx.ServerError("GetCommit", err)
  540. return
  541. }
  542. commit, err := gitRepo.GetCommit(endCommitID)
  543. if err != nil {
  544. ctx.ServerError("GetCommit", err)
  545. return
  546. }
  547. setImageCompareContext(ctx, baseCommit, commit)
  548. setPathsCompareContext(ctx, baseCommit, commit, headTarget)
  549. ctx.Data["RequireHighlightJS"] = true
  550. ctx.Data["RequireSimpleMDE"] = true
  551. ctx.Data["RequireTribute"] = true
  552. if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil {
  553. ctx.ServerError("GetAssignees", err)
  554. return
  555. }
  556. ctx.Data["CurrentReview"], err = models.GetCurrentReview(ctx.User, issue)
  557. if err != nil && !models.IsErrReviewNotExist(err) {
  558. ctx.ServerError("GetCurrentReview", err)
  559. return
  560. }
  561. getBranchData(ctx, issue)
  562. ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
  563. ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
  564. ctx.HTML(200, tplPullFiles)
  565. }
  566. // UpdatePullRequest merge master into PR
  567. func UpdatePullRequest(ctx *context.Context) {
  568. issue := checkPullInfo(ctx)
  569. if ctx.Written() {
  570. return
  571. }
  572. if issue.IsClosed {
  573. ctx.NotFound("MergePullRequest", nil)
  574. return
  575. }
  576. if issue.PullRequest.HasMerged {
  577. ctx.NotFound("MergePullRequest", nil)
  578. return
  579. }
  580. if err := issue.PullRequest.LoadBaseRepo(); err != nil {
  581. ctx.InternalServerError(err)
  582. return
  583. }
  584. if err := issue.PullRequest.LoadHeadRepo(); err != nil {
  585. ctx.InternalServerError(err)
  586. return
  587. }
  588. allowedUpdate, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User)
  589. if err != nil {
  590. ctx.ServerError("IsUserAllowedToMerge", err)
  591. return
  592. }
  593. // ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
  594. if !allowedUpdate {
  595. ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
  596. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  597. return
  598. }
  599. // default merge commit message
  600. message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
  601. if err = pull_service.Update(issue.PullRequest, ctx.User, message); err != nil {
  602. if models.IsErrMergeConflicts(err) {
  603. conflictError := err.(models.ErrMergeConflicts)
  604. ctx.Flash.Error(ctx.Tr("repo.pulls.merge_conflict", utils.SanitizeFlashErrorString(conflictError.StdErr), utils.SanitizeFlashErrorString(conflictError.StdOut)))
  605. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  606. return
  607. }
  608. ctx.Flash.Error(err.Error())
  609. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  610. }
  611. time.Sleep(1 * time.Second)
  612. ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success"))
  613. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  614. }
  615. // MergePullRequest response for merging pull request
  616. func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
  617. issue := checkPullInfo(ctx)
  618. if ctx.Written() {
  619. return
  620. }
  621. if issue.IsClosed {
  622. if issue.IsPull {
  623. ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
  624. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  625. return
  626. }
  627. ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
  628. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
  629. return
  630. }
  631. pr := issue.PullRequest
  632. allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.User)
  633. if err != nil {
  634. ctx.ServerError("IsUserAllowedToMerge", err)
  635. return
  636. }
  637. if !allowedMerge {
  638. ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
  639. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  640. return
  641. }
  642. if !pr.CanAutoMerge() {
  643. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
  644. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  645. return
  646. }
  647. if pr.HasMerged {
  648. ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
  649. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  650. return
  651. }
  652. if pr.IsWorkInProgress() {
  653. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
  654. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  655. return
  656. }
  657. if err := pull_service.CheckPRReadyToMerge(pr); err != nil {
  658. if !models.IsErrNotAllowedToMerge(err) {
  659. ctx.ServerError("Merge PR status", err)
  660. return
  661. }
  662. if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.User); err != nil {
  663. ctx.ServerError("IsUserRepoAdmin", err)
  664. return
  665. } else if !isRepoAdmin {
  666. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
  667. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  668. return
  669. }
  670. }
  671. if ctx.HasError() {
  672. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  673. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  674. return
  675. }
  676. message := strings.TrimSpace(form.MergeTitleField)
  677. if len(message) == 0 {
  678. if models.MergeStyle(form.Do) == models.MergeStyleMerge {
  679. message = pr.GetDefaultMergeMessage()
  680. }
  681. if models.MergeStyle(form.Do) == models.MergeStyleRebaseMerge {
  682. message = pr.GetDefaultMergeMessage()
  683. }
  684. if models.MergeStyle(form.Do) == models.MergeStyleSquash {
  685. message = pr.GetDefaultSquashMessage()
  686. }
  687. }
  688. form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
  689. if len(form.MergeMessageField) > 0 {
  690. message += "\n\n" + form.MergeMessageField
  691. }
  692. pr.Issue = issue
  693. pr.Issue.Repo = ctx.Repo.Repository
  694. noDeps, err := models.IssueNoDependenciesLeft(issue)
  695. if err != nil {
  696. return
  697. }
  698. if !noDeps {
  699. ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
  700. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  701. return
  702. }
  703. if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
  704. if models.IsErrInvalidMergeStyle(err) {
  705. ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
  706. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  707. return
  708. } else if models.IsErrMergeConflicts(err) {
  709. conflictError := err.(models.ErrMergeConflicts)
  710. ctx.Flash.Error(ctx.Tr("repo.pulls.merge_conflict", utils.SanitizeFlashErrorString(conflictError.StdErr), utils.SanitizeFlashErrorString(conflictError.StdOut)))
  711. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  712. return
  713. } else if models.IsErrRebaseConflicts(err) {
  714. conflictError := err.(models.ErrRebaseConflicts)
  715. ctx.Flash.Error(ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA), utils.SanitizeFlashErrorString(conflictError.StdErr), utils.SanitizeFlashErrorString(conflictError.StdOut)))
  716. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  717. return
  718. } else if models.IsErrMergeUnrelatedHistories(err) {
  719. log.Debug("MergeUnrelatedHistories error: %v", err)
  720. ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
  721. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  722. return
  723. } else if git.IsErrPushOutOfDate(err) {
  724. log.Debug("MergePushOutOfDate error: %v", err)
  725. ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date"))
  726. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  727. return
  728. } else if git.IsErrPushRejected(err) {
  729. log.Debug("MergePushRejected error: %v", err)
  730. pushrejErr := err.(*git.ErrPushRejected)
  731. message := pushrejErr.Message
  732. if len(message) == 0 {
  733. ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
  734. } else {
  735. ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected", utils.SanitizeFlashErrorString(pushrejErr.Message)))
  736. }
  737. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  738. return
  739. }
  740. ctx.ServerError("Merge", err)
  741. return
  742. }
  743. if err := stopTimerIfAvailable(ctx.User, issue); err != nil {
  744. ctx.ServerError("CreateOrStopIssueStopwatch", err)
  745. return
  746. }
  747. log.Trace("Pull request merged: %d", pr.ID)
  748. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  749. }
  750. func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
  751. if models.StopwatchExists(user.ID, issue.ID) {
  752. if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
  753. return err
  754. }
  755. }
  756. return nil
  757. }
  758. // CompareAndPullRequestPost response for creating pull request
  759. func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) {
  760. ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
  761. ctx.Data["PageIsComparePull"] = true
  762. ctx.Data["IsDiffCompare"] = true
  763. ctx.Data["RequireHighlightJS"] = true
  764. ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
  765. renderAttachmentSettings(ctx)
  766. var (
  767. repo = ctx.Repo.Repository
  768. attachments []string
  769. )
  770. headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
  771. if ctx.Written() {
  772. return
  773. }
  774. defer headGitRepo.Close()
  775. labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, true)
  776. if ctx.Written() {
  777. return
  778. }
  779. if setting.AttachmentEnabled {
  780. attachments = form.Files
  781. }
  782. if ctx.HasError() {
  783. auth.AssignForm(form, ctx.Data)
  784. // This stage is already stop creating new pull request, so it does not matter if it has
  785. // something to compare or not.
  786. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
  787. if ctx.Written() {
  788. return
  789. }
  790. ctx.HTML(200, tplCompareDiff)
  791. return
  792. }
  793. if util.IsEmptyString(form.Title) {
  794. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
  795. if ctx.Written() {
  796. return
  797. }
  798. ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
  799. return
  800. }
  801. pullIssue := &models.Issue{
  802. RepoID: repo.ID,
  803. Title: form.Title,
  804. PosterID: ctx.User.ID,
  805. Poster: ctx.User,
  806. MilestoneID: milestoneID,
  807. IsPull: true,
  808. Content: form.Content,
  809. }
  810. pullRequest := &models.PullRequest{
  811. HeadRepoID: headRepo.ID,
  812. BaseRepoID: repo.ID,
  813. HeadBranch: headBranch,
  814. BaseBranch: baseBranch,
  815. HeadRepo: headRepo,
  816. BaseRepo: repo,
  817. MergeBase: prInfo.MergeBase,
  818. Type: models.PullRequestGitea,
  819. }
  820. // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
  821. // instead of 500.
  822. if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil {
  823. if models.IsErrUserDoesNotHaveAccessToRepo(err) {
  824. ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error())
  825. return
  826. }
  827. ctx.ServerError("NewPullRequest", err)
  828. return
  829. }
  830. log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
  831. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index))
  832. }
  833. // TriggerTask response for a trigger task request
  834. func TriggerTask(ctx *context.Context) {
  835. pusherID := ctx.QueryInt64("pusher")
  836. branch := ctx.Query("branch")
  837. secret := ctx.Query("secret")
  838. if len(branch) == 0 || len(secret) == 0 || pusherID <= 0 {
  839. ctx.Error(404)
  840. log.Trace("TriggerTask: branch or secret is empty, or pusher ID is not valid")
  841. return
  842. }
  843. owner, repo := parseOwnerAndRepo(ctx)
  844. if ctx.Written() {
  845. return
  846. }
  847. got := []byte(base.EncodeMD5(owner.Salt))
  848. want := []byte(secret)
  849. if subtle.ConstantTimeCompare(got, want) != 1 {
  850. ctx.Error(404)
  851. log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name)
  852. return
  853. }
  854. pusher, err := models.GetUserByID(pusherID)
  855. if err != nil {
  856. if models.IsErrUserNotExist(err) {
  857. ctx.Error(404)
  858. } else {
  859. ctx.ServerError("GetUserByID", err)
  860. }
  861. return
  862. }
  863. log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
  864. go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, "", "")
  865. ctx.Status(202)
  866. }
  867. // CleanUpPullRequest responses for delete merged branch when PR has been merged
  868. func CleanUpPullRequest(ctx *context.Context) {
  869. issue := checkPullInfo(ctx)
  870. if ctx.Written() {
  871. return
  872. }
  873. pr := issue.PullRequest
  874. // Don't cleanup unmerged and unclosed PRs
  875. if !pr.HasMerged && !issue.IsClosed {
  876. ctx.NotFound("CleanUpPullRequest", nil)
  877. return
  878. }
  879. if err := pr.LoadHeadRepo(); err != nil {
  880. ctx.ServerError("LoadHeadRepo", err)
  881. return
  882. } else if pr.HeadRepo == nil {
  883. // Forked repository has already been deleted
  884. ctx.NotFound("CleanUpPullRequest", nil)
  885. return
  886. } else if err = pr.LoadBaseRepo(); err != nil {
  887. ctx.ServerError("LoadBaseRepo", err)
  888. return
  889. } else if err = pr.HeadRepo.GetOwner(); err != nil {
  890. ctx.ServerError("HeadRepo.GetOwner", err)
  891. return
  892. }
  893. perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.User)
  894. if err != nil {
  895. ctx.ServerError("GetUserRepoPermission", err)
  896. return
  897. }
  898. if !perm.CanWrite(models.UnitTypeCode) {
  899. ctx.NotFound("CleanUpPullRequest", nil)
  900. return
  901. }
  902. fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch
  903. gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  904. if err != nil {
  905. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
  906. return
  907. }
  908. defer gitRepo.Close()
  909. gitBaseRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  910. if err != nil {
  911. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err)
  912. return
  913. }
  914. defer gitBaseRepo.Close()
  915. defer func() {
  916. ctx.JSON(200, map[string]interface{}{
  917. "redirect": pr.BaseRepo.Link() + "/pulls/" + com.ToStr(issue.Index),
  918. })
  919. }()
  920. if pr.HeadBranch == pr.HeadRepo.DefaultBranch || !gitRepo.IsBranchExist(pr.HeadBranch) {
  921. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  922. return
  923. }
  924. // Check if branch is not protected
  925. if protected, err := pr.HeadRepo.IsProtectedBranch(pr.HeadBranch, ctx.User); err != nil || protected {
  926. if err != nil {
  927. log.Error("HeadRepo.IsProtectedBranch: %v", err)
  928. }
  929. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  930. return
  931. }
  932. // Check if branch has no new commits
  933. headCommitID, err := gitBaseRepo.GetRefCommitID(pr.GetGitRefName())
  934. if err != nil {
  935. log.Error("GetRefCommitID: %v", err)
  936. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  937. return
  938. }
  939. branchCommitID, err := gitRepo.GetBranchCommitID(pr.HeadBranch)
  940. if err != nil {
  941. log.Error("GetBranchCommitID: %v", err)
  942. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  943. return
  944. }
  945. if headCommitID != branchCommitID {
  946. ctx.Flash.Error(ctx.Tr("repo.branch.delete_branch_has_new_commits", fullBranchName))
  947. return
  948. }
  949. if err := gitRepo.DeleteBranch(pr.HeadBranch, git.DeleteBranchOptions{
  950. Force: true,
  951. }); err != nil {
  952. log.Error("DeleteBranch: %v", err)
  953. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  954. return
  955. }
  956. if err := repofiles.PushUpdate(
  957. pr.HeadRepo,
  958. pr.HeadBranch,
  959. repofiles.PushUpdateOptions{
  960. RefFullName: git.BranchPrefix + pr.HeadBranch,
  961. OldCommitID: branchCommitID,
  962. NewCommitID: git.EmptySHA,
  963. PusherID: ctx.User.ID,
  964. PusherName: ctx.User.Name,
  965. RepoUserName: pr.HeadRepo.Owner.Name,
  966. RepoName: pr.HeadRepo.Name,
  967. }); err != nil {
  968. log.Error("Update: %v", err)
  969. }
  970. if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil {
  971. // Do not fail here as branch has already been deleted
  972. log.Error("DeleteBranch: %v", err)
  973. }
  974. ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
  975. }
  976. // DownloadPullDiff render a pull's raw diff
  977. func DownloadPullDiff(ctx *context.Context) {
  978. DownloadPullDiffOrPatch(ctx, false)
  979. }
  980. // DownloadPullPatch render a pull's raw patch
  981. func DownloadPullPatch(ctx *context.Context) {
  982. DownloadPullDiffOrPatch(ctx, true)
  983. }
  984. // DownloadPullDiffOrPatch render a pull's raw diff or patch
  985. func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) {
  986. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  987. if err != nil {
  988. if models.IsErrIssueNotExist(err) {
  989. ctx.NotFound("GetIssueByIndex", err)
  990. } else {
  991. ctx.ServerError("GetIssueByIndex", err)
  992. }
  993. return
  994. }
  995. // Return not found if it's not a pull request
  996. if !issue.IsPull {
  997. ctx.NotFound("DownloadPullDiff",
  998. fmt.Errorf("Issue is not a pull request"))
  999. return
  1000. }
  1001. if err = issue.LoadPullRequest(); err != nil {
  1002. ctx.ServerError("LoadPullRequest", err)
  1003. return
  1004. }
  1005. pr := issue.PullRequest
  1006. if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch); err != nil {
  1007. ctx.ServerError("DownloadDiffOrPatch", err)
  1008. return
  1009. }
  1010. }
  1011. // UpdatePullRequestTarget change pull request's target branch
  1012. func UpdatePullRequestTarget(ctx *context.Context) {
  1013. issue := GetActionIssue(ctx)
  1014. pr := issue.PullRequest
  1015. if ctx.Written() {
  1016. return
  1017. }
  1018. if !issue.IsPull {
  1019. ctx.Error(http.StatusNotFound)
  1020. return
  1021. }
  1022. if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) {
  1023. ctx.Error(http.StatusForbidden)
  1024. return
  1025. }
  1026. targetBranch := ctx.QueryTrim("target_branch")
  1027. if len(targetBranch) == 0 {
  1028. ctx.Error(http.StatusNoContent)
  1029. return
  1030. }
  1031. if err := pull_service.ChangeTargetBranch(pr, ctx.User, targetBranch); err != nil {
  1032. if models.IsErrPullRequestAlreadyExists(err) {
  1033. err := err.(models.ErrPullRequestAlreadyExists)
  1034. RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
  1035. errorMessage := ctx.Tr("repo.pulls.has_pull_request", ctx.Repo.RepoLink, RepoRelPath, err.IssueID)
  1036. ctx.Flash.Error(errorMessage)
  1037. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1038. "error": err.Error(),
  1039. "user_error": errorMessage,
  1040. })
  1041. } else if models.IsErrIssueIsClosed(err) {
  1042. errorMessage := ctx.Tr("repo.pulls.is_closed")
  1043. ctx.Flash.Error(errorMessage)
  1044. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1045. "error": err.Error(),
  1046. "user_error": errorMessage,
  1047. })
  1048. } else if models.IsErrPullRequestHasMerged(err) {
  1049. errorMessage := ctx.Tr("repo.pulls.has_merged")
  1050. ctx.Flash.Error(errorMessage)
  1051. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1052. "error": err.Error(),
  1053. "user_error": errorMessage,
  1054. })
  1055. } else if models.IsErrBranchesEqual(err) {
  1056. errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
  1057. ctx.Flash.Error(errorMessage)
  1058. ctx.JSON(http.StatusBadRequest, map[string]interface{}{
  1059. "error": err.Error(),
  1060. "user_error": errorMessage,
  1061. })
  1062. } else {
  1063. ctx.ServerError("UpdatePullRequestTarget", err)
  1064. }
  1065. return
  1066. }
  1067. notification.NotifyPullRequestChangeTargetBranch(ctx.User, pr, targetBranch)
  1068. ctx.JSON(http.StatusOK, map[string]interface{}{
  1069. "base_branch": pr.BaseBranch,
  1070. })
  1071. }