gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repository
  4. import (
  5. "errors"
  6. "fmt"
  7. issue_model "code.gitea.io/gitea/models/issues"
  8. repo_model "code.gitea.io/gitea/models/repo"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "code.gitea.io/gitea/modules/git"
  11. repo_module "code.gitea.io/gitea/modules/repository"
  12. "code.gitea.io/gitea/modules/reqctx"
  13. "code.gitea.io/gitea/modules/util"
  14. "code.gitea.io/gitea/services/pull"
  15. )
  16. // MergeUpstream merges the base repository's default branch into the fork repository's current branch.
  17. func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, branch string, ffOnly bool) (mergeStyle string, err error) {
  18. if err = repo.MustNotBeArchived(); err != nil {
  19. return "", err
  20. }
  21. if err = repo.GetBaseRepo(ctx); err != nil {
  22. return "", err
  23. }
  24. divergingInfo, err := GetUpstreamDivergingInfo(ctx, repo, branch)
  25. if err != nil {
  26. return "", err
  27. }
  28. if !divergingInfo.BaseBranchHasNewCommits {
  29. return "up-to-date", nil
  30. }
  31. err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
  32. Remote: repo.RepoPath(),
  33. Branch: fmt.Sprintf("%s:%s", divergingInfo.BaseBranchName, branch),
  34. Env: repo_module.PushingEnvironment(doer, repo),
  35. })
  36. if err == nil {
  37. return "fast-forward", nil
  38. }
  39. if !git.IsErrPushOutOfDate(err) && !git.IsErrPushRejected(err) {
  40. return "", err
  41. }
  42. // If ff_only is requested and fast-forward failed, return error
  43. if ffOnly {
  44. return "", util.NewInvalidArgumentErrorf("fast-forward merge not possible: branch has diverged")
  45. }
  46. // TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
  47. // ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
  48. fakeIssue := &issue_model.Issue{
  49. ID: -1,
  50. RepoID: repo.ID,
  51. Repo: repo,
  52. Index: -1,
  53. PosterID: doer.ID,
  54. Poster: doer,
  55. IsPull: true,
  56. }
  57. fakePR := &issue_model.PullRequest{
  58. ID: -1,
  59. Status: issue_model.PullRequestStatusMergeable,
  60. IssueID: -1,
  61. Issue: fakeIssue,
  62. Index: -1,
  63. HeadRepoID: repo.ID,
  64. HeadRepo: repo,
  65. BaseRepoID: repo.BaseRepo.ID,
  66. BaseRepo: repo.BaseRepo,
  67. HeadBranch: branch, // maybe HeadCommitID is not needed
  68. BaseBranch: divergingInfo.BaseBranchName,
  69. }
  70. fakeIssue.PullRequest = fakePR
  71. err = pull.Update(ctx, fakePR, doer, "merge upstream", false)
  72. if err != nil {
  73. return "", err
  74. }
  75. return "merge", nil
  76. }
  77. // UpstreamDivergingInfo is also used in templates, so it needs to search for all references before changing it.
  78. type UpstreamDivergingInfo struct {
  79. BaseBranchName string
  80. BaseBranchHasNewCommits bool
  81. HeadBranchCommitsBehind int
  82. }
  83. // GetUpstreamDivergingInfo returns the information about the divergence between the fork repository's branch and the base repository's default branch.
  84. func GetUpstreamDivergingInfo(ctx reqctx.RequestContext, forkRepo *repo_model.Repository, forkBranch string) (*UpstreamDivergingInfo, error) {
  85. if !forkRepo.IsFork {
  86. return nil, util.NewInvalidArgumentErrorf("repo is not a fork")
  87. }
  88. if forkRepo.IsArchived {
  89. return nil, util.NewInvalidArgumentErrorf("repo is archived")
  90. }
  91. if err := forkRepo.GetBaseRepo(ctx); err != nil {
  92. return nil, err
  93. }
  94. // Do the best to follow the GitHub's behavior, suppose there is a `branch-a` in fork repo:
  95. // * if `branch-a` exists in base repo: try to sync `base:branch-a` to `fork:branch-a`
  96. // * if `branch-a` doesn't exist in base repo: try to sync `base:main` to `fork:branch-a`
  97. info, err := GetBranchDivergingInfo(ctx, forkRepo.BaseRepo, forkBranch, forkRepo, forkBranch)
  98. if err == nil {
  99. return &UpstreamDivergingInfo{
  100. BaseBranchName: forkBranch,
  101. BaseBranchHasNewCommits: info.BaseHasNewCommits,
  102. HeadBranchCommitsBehind: info.HeadCommitsBehind,
  103. }, nil
  104. }
  105. if errors.Is(err, util.ErrNotExist) {
  106. info, err = GetBranchDivergingInfo(ctx, forkRepo.BaseRepo, forkRepo.BaseRepo.DefaultBranch, forkRepo, forkBranch)
  107. if err == nil {
  108. return &UpstreamDivergingInfo{
  109. BaseBranchName: forkRepo.BaseRepo.DefaultBranch,
  110. BaseBranchHasNewCommits: info.BaseHasNewCommits,
  111. HeadBranchCommitsBehind: info.HeadCommitsBehind,
  112. }, nil
  113. }
  114. }
  115. return nil, err
  116. }