本站源代码
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.

1025 lines
27KB

  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. "io"
  12. "path"
  13. "strings"
  14. "code.gitea.io/gitea/models"
  15. "code.gitea.io/gitea/modules/auth"
  16. "code.gitea.io/gitea/modules/base"
  17. "code.gitea.io/gitea/modules/context"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/notification"
  21. "code.gitea.io/gitea/modules/setting"
  22. "code.gitea.io/gitea/modules/util"
  23. "code.gitea.io/gitea/services/gitdiff"
  24. pull_service "code.gitea.io/gitea/services/pull"
  25. repo_service "code.gitea.io/gitea/services/repository"
  26. "github.com/unknwon/com"
  27. )
  28. const (
  29. tplFork base.TplName = "repo/pulls/fork"
  30. tplCompareDiff base.TplName = "repo/diff/compare"
  31. tplPullCommits base.TplName = "repo/pulls/commits"
  32. tplPullFiles base.TplName = "repo/pulls/files"
  33. pullRequestTemplateKey = "PullRequestTemplate"
  34. )
  35. var (
  36. pullRequestTemplateCandidates = []string{
  37. "PULL_REQUEST_TEMPLATE.md",
  38. "pull_request_template.md",
  39. ".gitea/PULL_REQUEST_TEMPLATE.md",
  40. ".gitea/pull_request_template.md",
  41. ".github/PULL_REQUEST_TEMPLATE.md",
  42. ".github/pull_request_template.md",
  43. }
  44. )
  45. func getForkRepository(ctx *context.Context) *models.Repository {
  46. forkRepo, err := models.GetRepositoryByID(ctx.ParamsInt64(":repoid"))
  47. if err != nil {
  48. if models.IsErrRepoNotExist(err) {
  49. ctx.NotFound("GetRepositoryByID", nil)
  50. } else {
  51. ctx.ServerError("GetRepositoryByID", err)
  52. }
  53. return nil
  54. }
  55. perm, err := models.GetUserRepoPermission(forkRepo, ctx.User)
  56. if err != nil {
  57. ctx.ServerError("GetUserRepoPermission", err)
  58. return nil
  59. }
  60. if forkRepo.IsEmpty || !perm.CanRead(models.UnitTypeCode) {
  61. if log.IsTrace() {
  62. if forkRepo.IsEmpty {
  63. log.Trace("Empty fork repository %-v", forkRepo)
  64. } else {
  65. log.Trace("Permission Denied: User %-v cannot read %-v of forkRepo %-v\n"+
  66. "User in forkRepo has Permissions: %-+v",
  67. ctx.User,
  68. models.UnitTypeCode,
  69. ctx.Repo,
  70. perm)
  71. }
  72. }
  73. ctx.NotFound("getForkRepository", nil)
  74. return nil
  75. }
  76. ctx.Data["repo_name"] = forkRepo.Name
  77. ctx.Data["description"] = forkRepo.Description
  78. ctx.Data["IsPrivate"] = forkRepo.IsPrivate
  79. canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID)
  80. if err = forkRepo.GetOwner(); err != nil {
  81. ctx.ServerError("GetOwner", err)
  82. return nil
  83. }
  84. ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name
  85. ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID
  86. if err := ctx.User.GetOwnedOrganizations(); err != nil {
  87. ctx.ServerError("GetOwnedOrganizations", err)
  88. return nil
  89. }
  90. var orgs []*models.User
  91. for _, org := range ctx.User.OwnedOrgs {
  92. if forkRepo.OwnerID != org.ID && !org.HasForkedRepo(forkRepo.ID) {
  93. orgs = append(orgs, org)
  94. }
  95. }
  96. var traverseParentRepo = forkRepo
  97. for {
  98. if ctx.User.ID == traverseParentRepo.OwnerID {
  99. canForkToUser = false
  100. } else {
  101. for i, org := range orgs {
  102. if org.ID == traverseParentRepo.OwnerID {
  103. orgs = append(orgs[:i], orgs[i+1:]...)
  104. break
  105. }
  106. }
  107. }
  108. if !traverseParentRepo.IsFork {
  109. break
  110. }
  111. traverseParentRepo, err = models.GetRepositoryByID(traverseParentRepo.ForkID)
  112. if err != nil {
  113. ctx.ServerError("GetRepositoryByID", err)
  114. return nil
  115. }
  116. }
  117. ctx.Data["CanForkToUser"] = canForkToUser
  118. ctx.Data["Orgs"] = orgs
  119. if canForkToUser {
  120. ctx.Data["ContextUser"] = ctx.User
  121. } else if len(orgs) > 0 {
  122. ctx.Data["ContextUser"] = orgs[0]
  123. }
  124. return forkRepo
  125. }
  126. // Fork render repository fork page
  127. func Fork(ctx *context.Context) {
  128. ctx.Data["Title"] = ctx.Tr("new_fork")
  129. getForkRepository(ctx)
  130. if ctx.Written() {
  131. return
  132. }
  133. ctx.HTML(200, tplFork)
  134. }
  135. // ForkPost response for forking a repository
  136. func ForkPost(ctx *context.Context, form auth.CreateRepoForm) {
  137. ctx.Data["Title"] = ctx.Tr("new_fork")
  138. ctxUser := checkContextUser(ctx, form.UID)
  139. if ctx.Written() {
  140. return
  141. }
  142. forkRepo := getForkRepository(ctx)
  143. if ctx.Written() {
  144. return
  145. }
  146. ctx.Data["ContextUser"] = ctxUser
  147. if ctx.HasError() {
  148. ctx.HTML(200, tplFork)
  149. return
  150. }
  151. var err error
  152. var traverseParentRepo = forkRepo
  153. for {
  154. if ctxUser.ID == traverseParentRepo.OwnerID {
  155. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
  156. return
  157. }
  158. repo, has := models.HasForkedRepo(ctxUser.ID, traverseParentRepo.ID)
  159. if has {
  160. ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
  161. return
  162. }
  163. if !traverseParentRepo.IsFork {
  164. break
  165. }
  166. traverseParentRepo, err = models.GetRepositoryByID(traverseParentRepo.ForkID)
  167. if err != nil {
  168. ctx.ServerError("GetRepositoryByID", err)
  169. return
  170. }
  171. }
  172. // Check ownership of organization.
  173. if ctxUser.IsOrganization() {
  174. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  175. if err != nil {
  176. ctx.ServerError("IsOwnedBy", err)
  177. return
  178. } else if !isOwner {
  179. ctx.Error(403)
  180. return
  181. }
  182. }
  183. repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description)
  184. if err != nil {
  185. ctx.Data["Err_RepoName"] = true
  186. switch {
  187. case models.IsErrRepoAlreadyExist(err):
  188. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
  189. case models.IsErrNameReserved(err):
  190. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplFork, &form)
  191. case models.IsErrNamePatternNotAllowed(err):
  192. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
  193. default:
  194. ctx.ServerError("ForkPost", err)
  195. }
  196. return
  197. }
  198. log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
  199. ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
  200. }
  201. func checkPullInfo(ctx *context.Context) *models.Issue {
  202. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  203. if err != nil {
  204. if models.IsErrIssueNotExist(err) {
  205. ctx.NotFound("GetIssueByIndex", err)
  206. } else {
  207. ctx.ServerError("GetIssueByIndex", err)
  208. }
  209. return nil
  210. }
  211. if err = issue.LoadPoster(); err != nil {
  212. ctx.ServerError("LoadPoster", err)
  213. return nil
  214. }
  215. if err := issue.LoadRepo(); err != nil {
  216. ctx.ServerError("LoadRepo", err)
  217. return nil
  218. }
  219. ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
  220. ctx.Data["Issue"] = issue
  221. if !issue.IsPull {
  222. ctx.NotFound("ViewPullCommits", nil)
  223. return nil
  224. }
  225. if err = issue.LoadPullRequest(); err != nil {
  226. ctx.ServerError("LoadPullRequest", err)
  227. return nil
  228. }
  229. if err = issue.PullRequest.GetHeadRepo(); err != nil {
  230. ctx.ServerError("GetHeadRepo", err)
  231. return nil
  232. }
  233. if ctx.IsSigned {
  234. // Update issue-user.
  235. if err = issue.ReadBy(ctx.User.ID); err != nil {
  236. ctx.ServerError("ReadBy", err)
  237. return nil
  238. }
  239. }
  240. return issue
  241. }
  242. func setMergeTarget(ctx *context.Context, pull *models.PullRequest) {
  243. if ctx.Repo.Owner.Name == pull.MustHeadUserName() {
  244. ctx.Data["HeadTarget"] = pull.HeadBranch
  245. } else if pull.HeadRepo == nil {
  246. ctx.Data["HeadTarget"] = pull.MustHeadUserName() + ":" + pull.HeadBranch
  247. } else {
  248. ctx.Data["HeadTarget"] = pull.MustHeadUserName() + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
  249. }
  250. ctx.Data["BaseTarget"] = pull.BaseBranch
  251. }
  252. // PrepareMergedViewPullInfo show meta information for a merged pull request view page
  253. func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
  254. pull := issue.PullRequest
  255. setMergeTarget(ctx, pull)
  256. ctx.Data["HasMerged"] = true
  257. compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(),
  258. pull.MergeBase, pull.GetGitRefName())
  259. if err != nil {
  260. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  261. ctx.Data["IsPullRequestBroken"] = true
  262. ctx.Data["BaseTarget"] = "deleted"
  263. ctx.Data["NumCommits"] = 0
  264. ctx.Data["NumFiles"] = 0
  265. return nil
  266. }
  267. ctx.ServerError("GetCompareInfo", err)
  268. return nil
  269. }
  270. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  271. ctx.Data["NumFiles"] = compareInfo.NumFiles
  272. return compareInfo
  273. }
  274. // PrepareViewPullInfo show meta information for a pull request preview page
  275. func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
  276. repo := ctx.Repo.Repository
  277. pull := issue.PullRequest
  278. var err error
  279. if err = pull.GetHeadRepo(); err != nil {
  280. ctx.ServerError("GetHeadRepo", err)
  281. return nil
  282. }
  283. setMergeTarget(ctx, pull)
  284. if err = pull.LoadProtectedBranch(); err != nil {
  285. ctx.ServerError("GetLatestCommitStatus", err)
  286. return nil
  287. }
  288. ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
  289. var headGitRepo *git.Repository
  290. var headBranchExist bool
  291. // HeadRepo may be missing
  292. if pull.HeadRepo != nil {
  293. headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath())
  294. if err != nil {
  295. ctx.ServerError("OpenRepository", err)
  296. return nil
  297. }
  298. headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
  299. if headBranchExist {
  300. sha, err := headGitRepo.GetBranchCommitID(pull.HeadBranch)
  301. if err != nil {
  302. ctx.ServerError("GetBranchCommitID", err)
  303. return nil
  304. }
  305. commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
  306. if err != nil {
  307. ctx.ServerError("GetLatestCommitStatus", err)
  308. return nil
  309. }
  310. if len(commitStatuses) > 0 {
  311. ctx.Data["LatestCommitStatuses"] = commitStatuses
  312. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
  313. }
  314. if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
  315. ctx.Data["is_context_required"] = func(context string) bool {
  316. for _, c := range pull.ProtectedBranch.StatusCheckContexts {
  317. if c == context {
  318. return true
  319. }
  320. }
  321. return false
  322. }
  323. ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
  324. }
  325. }
  326. }
  327. if pull.HeadRepo == nil || !headBranchExist {
  328. ctx.Data["IsPullRequestBroken"] = true
  329. ctx.Data["HeadTarget"] = "deleted"
  330. ctx.Data["NumCommits"] = 0
  331. ctx.Data["NumFiles"] = 0
  332. return nil
  333. }
  334. compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(repo.Owner.Name, repo.Name),
  335. pull.BaseBranch, pull.HeadBranch)
  336. if err != nil {
  337. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  338. ctx.Data["IsPullRequestBroken"] = true
  339. ctx.Data["BaseTarget"] = "deleted"
  340. ctx.Data["NumCommits"] = 0
  341. ctx.Data["NumFiles"] = 0
  342. return nil
  343. }
  344. ctx.ServerError("GetCompareInfo", err)
  345. return nil
  346. }
  347. if pull.IsWorkInProgress() {
  348. ctx.Data["IsPullWorkInProgress"] = true
  349. ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix()
  350. }
  351. if pull.IsFilesConflicted() {
  352. ctx.Data["IsPullFilesConflicted"] = true
  353. ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
  354. }
  355. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  356. ctx.Data["NumFiles"] = compareInfo.NumFiles
  357. return compareInfo
  358. }
  359. // ViewPullCommits show commits for a pull request
  360. func ViewPullCommits(ctx *context.Context) {
  361. ctx.Data["PageIsPullList"] = true
  362. ctx.Data["PageIsPullCommits"] = true
  363. issue := checkPullInfo(ctx)
  364. if ctx.Written() {
  365. return
  366. }
  367. pull := issue.PullRequest
  368. var commits *list.List
  369. if pull.HasMerged {
  370. prInfo := PrepareMergedViewPullInfo(ctx, issue)
  371. if ctx.Written() {
  372. return
  373. } else if prInfo == nil {
  374. ctx.NotFound("ViewPullCommits", nil)
  375. return
  376. }
  377. ctx.Data["Username"] = ctx.Repo.Owner.Name
  378. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  379. commits = prInfo.Commits
  380. } else {
  381. prInfo := PrepareViewPullInfo(ctx, issue)
  382. if ctx.Written() {
  383. return
  384. } else if prInfo == nil {
  385. ctx.NotFound("ViewPullCommits", nil)
  386. return
  387. }
  388. ctx.Data["Username"] = pull.MustHeadUserName()
  389. ctx.Data["Reponame"] = pull.HeadRepo.Name
  390. commits = prInfo.Commits
  391. }
  392. commits = models.ValidateCommitsWithEmails(commits)
  393. commits = models.ParseCommitsWithSignature(commits)
  394. commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
  395. ctx.Data["Commits"] = commits
  396. ctx.Data["CommitCount"] = commits.Len()
  397. ctx.HTML(200, tplPullCommits)
  398. }
  399. // ViewPullFiles render pull request changed files list page
  400. func ViewPullFiles(ctx *context.Context) {
  401. ctx.Data["PageIsPullList"] = true
  402. ctx.Data["PageIsPullFiles"] = true
  403. issue := checkPullInfo(ctx)
  404. if ctx.Written() {
  405. return
  406. }
  407. pull := issue.PullRequest
  408. whitespaceFlags := map[string]string{
  409. "ignore-all": "-w",
  410. "ignore-change": "-b",
  411. "ignore-eol": "--ignore-space-at-eol",
  412. "": ""}
  413. var (
  414. diffRepoPath string
  415. startCommitID string
  416. endCommitID string
  417. gitRepo *git.Repository
  418. )
  419. var headTarget string
  420. if pull.HasMerged {
  421. prInfo := PrepareMergedViewPullInfo(ctx, issue)
  422. if ctx.Written() {
  423. return
  424. } else if prInfo == nil {
  425. ctx.NotFound("ViewPullFiles", nil)
  426. return
  427. }
  428. diffRepoPath = ctx.Repo.GitRepo.Path
  429. gitRepo = ctx.Repo.GitRepo
  430. headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
  431. if err != nil {
  432. ctx.ServerError("GetRefCommitID", err)
  433. return
  434. }
  435. startCommitID = prInfo.MergeBase
  436. endCommitID = headCommitID
  437. headTarget = path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  438. ctx.Data["Username"] = ctx.Repo.Owner.Name
  439. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  440. } else {
  441. prInfo := PrepareViewPullInfo(ctx, issue)
  442. if ctx.Written() {
  443. return
  444. } else if prInfo == nil {
  445. ctx.NotFound("ViewPullFiles", nil)
  446. return
  447. }
  448. headRepoPath := pull.HeadRepo.RepoPath()
  449. headGitRepo, err := git.OpenRepository(headRepoPath)
  450. if err != nil {
  451. ctx.ServerError("OpenRepository", err)
  452. return
  453. }
  454. headCommitID, err := headGitRepo.GetBranchCommitID(pull.HeadBranch)
  455. if err != nil {
  456. ctx.ServerError("GetBranchCommitID", err)
  457. return
  458. }
  459. diffRepoPath = headRepoPath
  460. startCommitID = prInfo.MergeBase
  461. endCommitID = headCommitID
  462. gitRepo = headGitRepo
  463. headTarget = path.Join(pull.MustHeadUserName(), pull.HeadRepo.Name)
  464. ctx.Data["Username"] = pull.MustHeadUserName()
  465. ctx.Data["Reponame"] = pull.HeadRepo.Name
  466. }
  467. diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(diffRepoPath,
  468. startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
  469. setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles,
  470. whitespaceFlags[ctx.Data["WhitespaceBehavior"].(string)])
  471. if err != nil {
  472. ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
  473. return
  474. }
  475. if err = diff.LoadComments(issue, ctx.User); err != nil {
  476. ctx.ServerError("LoadComments", err)
  477. return
  478. }
  479. ctx.Data["Diff"] = diff
  480. ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
  481. baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID)
  482. if err != nil {
  483. ctx.ServerError("GetCommit", err)
  484. return
  485. }
  486. commit, err := gitRepo.GetCommit(endCommitID)
  487. if err != nil {
  488. ctx.ServerError("GetCommit", err)
  489. return
  490. }
  491. setImageCompareContext(ctx, baseCommit, commit)
  492. setPathsCompareContext(ctx, baseCommit, commit, headTarget)
  493. ctx.Data["RequireHighlightJS"] = true
  494. ctx.Data["RequireTribute"] = true
  495. if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil {
  496. ctx.ServerError("GetAssignees", err)
  497. return
  498. }
  499. ctx.Data["CurrentReview"], err = models.GetCurrentReview(ctx.User, issue)
  500. if err != nil && !models.IsErrReviewNotExist(err) {
  501. ctx.ServerError("GetCurrentReview", err)
  502. return
  503. }
  504. ctx.HTML(200, tplPullFiles)
  505. }
  506. // MergePullRequest response for merging pull request
  507. func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
  508. issue := checkPullInfo(ctx)
  509. if ctx.Written() {
  510. return
  511. }
  512. if issue.IsClosed {
  513. ctx.NotFound("MergePullRequest", nil)
  514. return
  515. }
  516. pr := issue.PullRequest
  517. if !pr.CanAutoMerge() || pr.HasMerged {
  518. ctx.NotFound("MergePullRequest", nil)
  519. return
  520. }
  521. if pr.IsWorkInProgress() {
  522. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
  523. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  524. return
  525. }
  526. isPass, err := pull_service.IsPullCommitStatusPass(pr)
  527. if err != nil {
  528. ctx.ServerError("IsPullCommitStatusPass", err)
  529. return
  530. }
  531. if !isPass && !ctx.IsUserRepoAdmin() {
  532. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_status_check"))
  533. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  534. return
  535. }
  536. if ctx.HasError() {
  537. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  538. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  539. return
  540. }
  541. message := strings.TrimSpace(form.MergeTitleField)
  542. if len(message) == 0 {
  543. if models.MergeStyle(form.Do) == models.MergeStyleMerge {
  544. message = pr.GetDefaultMergeMessage()
  545. }
  546. if models.MergeStyle(form.Do) == models.MergeStyleRebaseMerge {
  547. message = pr.GetDefaultMergeMessage()
  548. }
  549. if models.MergeStyle(form.Do) == models.MergeStyleSquash {
  550. message = pr.GetDefaultSquashMessage()
  551. }
  552. }
  553. form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
  554. if len(form.MergeMessageField) > 0 {
  555. message += "\n\n" + form.MergeMessageField
  556. }
  557. pr.Issue = issue
  558. pr.Issue.Repo = ctx.Repo.Repository
  559. noDeps, err := models.IssueNoDependenciesLeft(issue)
  560. if err != nil {
  561. return
  562. }
  563. if !noDeps {
  564. ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
  565. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  566. return
  567. }
  568. if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
  569. if models.IsErrInvalidMergeStyle(err) {
  570. ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
  571. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  572. return
  573. }
  574. ctx.ServerError("Merge", err)
  575. return
  576. }
  577. if err := stopTimerIfAvailable(ctx.User, issue); err != nil {
  578. ctx.ServerError("CreateOrStopIssueStopwatch", err)
  579. return
  580. }
  581. notification.NotifyMergePullRequest(pr, ctx.User, ctx.Repo.GitRepo)
  582. log.Trace("Pull request merged: %d", pr.ID)
  583. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
  584. }
  585. func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
  586. if models.StopwatchExists(user.ID, issue.ID) {
  587. if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
  588. return err
  589. }
  590. }
  591. return nil
  592. }
  593. // CompareAndPullRequestPost response for creating pull request
  594. func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) {
  595. ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
  596. ctx.Data["PageIsComparePull"] = true
  597. ctx.Data["IsDiffCompare"] = true
  598. ctx.Data["RequireHighlightJS"] = true
  599. ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
  600. renderAttachmentSettings(ctx)
  601. var (
  602. repo = ctx.Repo.Repository
  603. attachments []string
  604. )
  605. headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
  606. if ctx.Written() {
  607. return
  608. }
  609. labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, true)
  610. if ctx.Written() {
  611. return
  612. }
  613. if setting.AttachmentEnabled {
  614. attachments = form.Files
  615. }
  616. if ctx.HasError() {
  617. auth.AssignForm(form, ctx.Data)
  618. // This stage is already stop creating new pull request, so it does not matter if it has
  619. // something to compare or not.
  620. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
  621. if ctx.Written() {
  622. return
  623. }
  624. ctx.HTML(200, tplCompareDiff)
  625. return
  626. }
  627. if util.IsEmptyString(form.Title) {
  628. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
  629. if ctx.Written() {
  630. return
  631. }
  632. ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
  633. return
  634. }
  635. patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch)
  636. if err != nil {
  637. ctx.ServerError("GetPatch", err)
  638. return
  639. }
  640. pullIssue := &models.Issue{
  641. RepoID: repo.ID,
  642. Title: form.Title,
  643. PosterID: ctx.User.ID,
  644. Poster: ctx.User,
  645. MilestoneID: milestoneID,
  646. IsPull: true,
  647. Content: form.Content,
  648. }
  649. pullRequest := &models.PullRequest{
  650. HeadRepoID: headRepo.ID,
  651. BaseRepoID: repo.ID,
  652. HeadBranch: headBranch,
  653. BaseBranch: baseBranch,
  654. HeadRepo: headRepo,
  655. BaseRepo: repo,
  656. MergeBase: prInfo.MergeBase,
  657. Type: models.PullRequestGitea,
  658. }
  659. // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
  660. // instead of 500.
  661. if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch, assigneeIDs); err != nil {
  662. if models.IsErrUserDoesNotHaveAccessToRepo(err) {
  663. ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error())
  664. return
  665. }
  666. ctx.ServerError("NewPullRequest", err)
  667. return
  668. } else if err := pullRequest.PushToBaseRepo(); err != nil {
  669. ctx.ServerError("PushToBaseRepo", err)
  670. return
  671. }
  672. notification.NotifyNewPullRequest(pullRequest)
  673. log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
  674. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index))
  675. }
  676. // TriggerTask response for a trigger task request
  677. func TriggerTask(ctx *context.Context) {
  678. pusherID := ctx.QueryInt64("pusher")
  679. branch := ctx.Query("branch")
  680. secret := ctx.Query("secret")
  681. if len(branch) == 0 || len(secret) == 0 || pusherID <= 0 {
  682. ctx.Error(404)
  683. log.Trace("TriggerTask: branch or secret is empty, or pusher ID is not valid")
  684. return
  685. }
  686. owner, repo := parseOwnerAndRepo(ctx)
  687. if ctx.Written() {
  688. return
  689. }
  690. got := []byte(base.EncodeMD5(owner.Salt))
  691. want := []byte(secret)
  692. if subtle.ConstantTimeCompare(got, want) != 1 {
  693. ctx.Error(404)
  694. log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name)
  695. return
  696. }
  697. pusher, err := models.GetUserByID(pusherID)
  698. if err != nil {
  699. if models.IsErrUserNotExist(err) {
  700. ctx.Error(404)
  701. } else {
  702. ctx.ServerError("GetUserByID", err)
  703. }
  704. return
  705. }
  706. log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
  707. go models.HookQueue.Add(repo.ID)
  708. go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true)
  709. ctx.Status(202)
  710. }
  711. // CleanUpPullRequest responses for delete merged branch when PR has been merged
  712. func CleanUpPullRequest(ctx *context.Context) {
  713. issue := checkPullInfo(ctx)
  714. if ctx.Written() {
  715. return
  716. }
  717. pr := issue.PullRequest
  718. // Don't cleanup unmerged and unclosed PRs
  719. if !pr.HasMerged && !issue.IsClosed {
  720. ctx.NotFound("CleanUpPullRequest", nil)
  721. return
  722. }
  723. if err := pr.GetHeadRepo(); err != nil {
  724. ctx.ServerError("GetHeadRepo", err)
  725. return
  726. } else if pr.HeadRepo == nil {
  727. // Forked repository has already been deleted
  728. ctx.NotFound("CleanUpPullRequest", nil)
  729. return
  730. } else if err = pr.GetBaseRepo(); err != nil {
  731. ctx.ServerError("GetBaseRepo", err)
  732. return
  733. } else if err = pr.HeadRepo.GetOwner(); err != nil {
  734. ctx.ServerError("HeadRepo.GetOwner", err)
  735. return
  736. }
  737. perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.User)
  738. if err != nil {
  739. ctx.ServerError("GetUserRepoPermission", err)
  740. return
  741. }
  742. if !perm.CanWrite(models.UnitTypeCode) {
  743. ctx.NotFound("CleanUpPullRequest", nil)
  744. return
  745. }
  746. fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch
  747. gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  748. if err != nil {
  749. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
  750. return
  751. }
  752. gitBaseRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  753. if err != nil {
  754. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err)
  755. return
  756. }
  757. defer func() {
  758. ctx.JSON(200, map[string]interface{}{
  759. "redirect": pr.BaseRepo.Link() + "/pulls/" + com.ToStr(issue.Index),
  760. })
  761. }()
  762. if pr.HeadBranch == pr.HeadRepo.DefaultBranch || !gitRepo.IsBranchExist(pr.HeadBranch) {
  763. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  764. return
  765. }
  766. // Check if branch is not protected
  767. if protected, err := pr.HeadRepo.IsProtectedBranch(pr.HeadBranch, ctx.User); err != nil || protected {
  768. if err != nil {
  769. log.Error("HeadRepo.IsProtectedBranch: %v", err)
  770. }
  771. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  772. return
  773. }
  774. // Check if branch has no new commits
  775. headCommitID, err := gitBaseRepo.GetRefCommitID(pr.GetGitRefName())
  776. if err != nil {
  777. log.Error("GetRefCommitID: %v", err)
  778. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  779. return
  780. }
  781. branchCommitID, err := gitRepo.GetBranchCommitID(pr.HeadBranch)
  782. if err != nil {
  783. log.Error("GetBranchCommitID: %v", err)
  784. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  785. return
  786. }
  787. if headCommitID != branchCommitID {
  788. ctx.Flash.Error(ctx.Tr("repo.branch.delete_branch_has_new_commits", fullBranchName))
  789. return
  790. }
  791. if err := gitRepo.DeleteBranch(pr.HeadBranch, git.DeleteBranchOptions{
  792. Force: true,
  793. }); err != nil {
  794. log.Error("DeleteBranch: %v", err)
  795. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  796. return
  797. }
  798. if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil {
  799. // Do not fail here as branch has already been deleted
  800. log.Error("DeleteBranch: %v", err)
  801. }
  802. ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
  803. }
  804. // DownloadPullDiff render a pull's raw diff
  805. func DownloadPullDiff(ctx *context.Context) {
  806. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  807. if err != nil {
  808. if models.IsErrIssueNotExist(err) {
  809. ctx.NotFound("GetIssueByIndex", err)
  810. } else {
  811. ctx.ServerError("GetIssueByIndex", err)
  812. }
  813. return
  814. }
  815. // Return not found if it's not a pull request
  816. if !issue.IsPull {
  817. ctx.NotFound("DownloadPullDiff",
  818. fmt.Errorf("Issue is not a pull request"))
  819. return
  820. }
  821. if err = issue.LoadPullRequest(); err != nil {
  822. ctx.ServerError("LoadPullRequest", err)
  823. return
  824. }
  825. pr := issue.PullRequest
  826. if err = pr.GetBaseRepo(); err != nil {
  827. ctx.ServerError("GetBaseRepo", err)
  828. return
  829. }
  830. patch, err := pr.BaseRepo.PatchPath(pr.Index)
  831. if err != nil {
  832. ctx.ServerError("PatchPath", err)
  833. return
  834. }
  835. ctx.ServeFileContent(patch)
  836. }
  837. // DownloadPullPatch render a pull's raw patch
  838. func DownloadPullPatch(ctx *context.Context) {
  839. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  840. if err != nil {
  841. if models.IsErrIssueNotExist(err) {
  842. ctx.NotFound("GetIssueByIndex", err)
  843. } else {
  844. ctx.ServerError("GetIssueByIndex", err)
  845. }
  846. return
  847. }
  848. // Return not found if it's not a pull request
  849. if !issue.IsPull {
  850. ctx.NotFound("DownloadPullDiff",
  851. fmt.Errorf("Issue is not a pull request"))
  852. return
  853. }
  854. if err = issue.LoadPullRequest(); err != nil {
  855. ctx.ServerError("LoadPullRequest", err)
  856. return
  857. }
  858. pr := issue.PullRequest
  859. if err = pr.GetHeadRepo(); err != nil {
  860. ctx.ServerError("GetHeadRepo", err)
  861. return
  862. }
  863. headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  864. if err != nil {
  865. ctx.ServerError("OpenRepository", err)
  866. return
  867. }
  868. patch, err := headGitRepo.GetFormatPatch(pr.MergeBase, pr.HeadBranch)
  869. if err != nil {
  870. ctx.ServerError("GetFormatPatch", err)
  871. return
  872. }
  873. _, err = io.Copy(ctx, patch)
  874. if err != nil {
  875. ctx.ServerError("io.Copy", err)
  876. return
  877. }
  878. }
上海开阖软件有限公司 沪ICP备12045867号-1