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.

agit.go 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package agit
  4. import (
  5. "context"
  6. "fmt"
  7. "os"
  8. "strings"
  9. issues_model "code.gitea.io/gitea/models/issues"
  10. repo_model "code.gitea.io/gitea/models/repo"
  11. user_model "code.gitea.io/gitea/models/user"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/private"
  15. notify_service "code.gitea.io/gitea/services/notify"
  16. pull_service "code.gitea.io/gitea/services/pull"
  17. )
  18. // ProcReceive handle proc receive work
  19. func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) {
  20. // TODO: Add more options?
  21. var (
  22. topicBranch string
  23. title string
  24. description string
  25. forcePush bool
  26. )
  27. results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs))
  28. ownerName := repo.OwnerName
  29. repoName := repo.Name
  30. topicBranch = opts.GitPushOptions["topic"]
  31. _, forcePush = opts.GitPushOptions["force-push"]
  32. for i := range opts.OldCommitIDs {
  33. if opts.NewCommitIDs[i] == git.EmptySHA {
  34. results = append(results, private.HookProcReceiveRefResult{
  35. OriginalRef: opts.RefFullNames[i],
  36. OldOID: opts.OldCommitIDs[i],
  37. NewOID: opts.NewCommitIDs[i],
  38. Err: "Can't delete not exist branch",
  39. })
  40. continue
  41. }
  42. if !opts.RefFullNames[i].IsFor() {
  43. results = append(results, private.HookProcReceiveRefResult{
  44. IsNotMatched: true,
  45. OriginalRef: opts.RefFullNames[i],
  46. })
  47. continue
  48. }
  49. baseBranchName := opts.RefFullNames[i].ForBranchName()
  50. curentTopicBranch := ""
  51. if !gitRepo.IsBranchExist(baseBranchName) {
  52. // try match refs/for/<target-branch>/<topic-branch>
  53. for p, v := range baseBranchName {
  54. if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 {
  55. curentTopicBranch = baseBranchName[p+1:]
  56. baseBranchName = baseBranchName[:p]
  57. break
  58. }
  59. }
  60. }
  61. if len(topicBranch) == 0 && len(curentTopicBranch) == 0 {
  62. results = append(results, private.HookProcReceiveRefResult{
  63. OriginalRef: opts.RefFullNames[i],
  64. OldOID: opts.OldCommitIDs[i],
  65. NewOID: opts.NewCommitIDs[i],
  66. Err: "topic-branch is not set",
  67. })
  68. continue
  69. }
  70. var headBranch string
  71. userName := strings.ToLower(opts.UserName)
  72. if len(curentTopicBranch) == 0 {
  73. curentTopicBranch = topicBranch
  74. }
  75. // because different user maybe want to use same topic,
  76. // So it's better to make sure the topic branch name
  77. // has user name prefix
  78. if !strings.HasPrefix(curentTopicBranch, userName+"/") {
  79. headBranch = userName + "/" + curentTopicBranch
  80. } else {
  81. headBranch = curentTopicBranch
  82. }
  83. pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, repo.ID, headBranch, baseBranchName, issues_model.PullRequestFlowAGit)
  84. if err != nil {
  85. if !issues_model.IsErrPullRequestNotExist(err) {
  86. return nil, fmt.Errorf("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %w", ownerName, repoName, err)
  87. }
  88. // create a new pull request
  89. if len(title) == 0 {
  90. var has bool
  91. title, has = opts.GitPushOptions["title"]
  92. if !has || len(title) == 0 {
  93. commit, err := gitRepo.GetCommit(opts.NewCommitIDs[i])
  94. if err != nil {
  95. return nil, fmt.Errorf("Failed to get commit %s in repository: %s/%s Error: %w", opts.NewCommitIDs[i], ownerName, repoName, err)
  96. }
  97. title = strings.Split(commit.CommitMessage, "\n")[0]
  98. }
  99. description = opts.GitPushOptions["description"]
  100. }
  101. pusher, err := user_model.GetUserByID(ctx, opts.UserID)
  102. if err != nil {
  103. return nil, fmt.Errorf("Failed to get user. Error: %w", err)
  104. }
  105. prIssue := &issues_model.Issue{
  106. RepoID: repo.ID,
  107. Title: title,
  108. PosterID: pusher.ID,
  109. Poster: pusher,
  110. IsPull: true,
  111. Content: description,
  112. }
  113. pr := &issues_model.PullRequest{
  114. HeadRepoID: repo.ID,
  115. BaseRepoID: repo.ID,
  116. HeadBranch: headBranch,
  117. HeadCommitID: opts.NewCommitIDs[i],
  118. BaseBranch: baseBranchName,
  119. HeadRepo: repo,
  120. BaseRepo: repo,
  121. MergeBase: "",
  122. Type: issues_model.PullRequestGitea,
  123. Flow: issues_model.PullRequestFlowAGit,
  124. }
  125. if err := pull_service.NewPullRequest(ctx, repo, prIssue, []int64{}, []string{}, pr, []int64{}); err != nil {
  126. return nil, err
  127. }
  128. log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
  129. results = append(results, private.HookProcReceiveRefResult{
  130. Ref: pr.GetGitRefName(),
  131. OriginalRef: opts.RefFullNames[i],
  132. OldOID: git.EmptySHA,
  133. NewOID: opts.NewCommitIDs[i],
  134. })
  135. continue
  136. }
  137. // update exist pull request
  138. if err := pr.LoadBaseRepo(ctx); err != nil {
  139. return nil, fmt.Errorf("Unable to load base repository for PR[%d] Error: %w", pr.ID, err)
  140. }
  141. oldCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
  142. if err != nil {
  143. return nil, fmt.Errorf("Unable to get ref commit id in base repository for PR[%d] Error: %w", pr.ID, err)
  144. }
  145. if oldCommitID == opts.NewCommitIDs[i] {
  146. results = append(results, private.HookProcReceiveRefResult{
  147. OriginalRef: opts.RefFullNames[i],
  148. OldOID: opts.OldCommitIDs[i],
  149. NewOID: opts.NewCommitIDs[i],
  150. Err: "new commit is same with old commit",
  151. })
  152. continue
  153. }
  154. if !forcePush {
  155. output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()})
  156. if err != nil {
  157. return nil, fmt.Errorf("Fail to detect force push: %w", err)
  158. } else if len(output) > 0 {
  159. results = append(results, private.HookProcReceiveRefResult{
  160. OriginalRef: opts.RefFullNames[i],
  161. OldOID: opts.OldCommitIDs[i],
  162. NewOID: opts.NewCommitIDs[i],
  163. Err: "request `force-push` push option",
  164. })
  165. continue
  166. }
  167. }
  168. pr.HeadCommitID = opts.NewCommitIDs[i]
  169. if err = pull_service.UpdateRef(ctx, pr); err != nil {
  170. return nil, fmt.Errorf("Failed to update pull ref. Error: %w", err)
  171. }
  172. pull_service.AddToTaskQueue(ctx, pr)
  173. pusher, err := user_model.GetUserByID(ctx, opts.UserID)
  174. if err != nil {
  175. return nil, fmt.Errorf("Failed to get user. Error: %w", err)
  176. }
  177. err = pr.LoadIssue(ctx)
  178. if err != nil {
  179. return nil, fmt.Errorf("Failed to load pull issue. Error: %w", err)
  180. }
  181. comment, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i])
  182. if err == nil && comment != nil {
  183. notify_service.PullRequestPushCommits(ctx, pusher, pr, comment)
  184. }
  185. notify_service.PullRequestSynchronized(ctx, pusher, pr)
  186. isForcePush := comment != nil && comment.IsForcePush
  187. results = append(results, private.HookProcReceiveRefResult{
  188. OldOID: oldCommitID,
  189. NewOID: opts.NewCommitIDs[i],
  190. Ref: pr.GetGitRefName(),
  191. OriginalRef: opts.RefFullNames[i],
  192. IsForcePush: isForcePush,
  193. })
  194. }
  195. return results, nil
  196. }
  197. // UserNameChanged handle user name change for agit flow pull
  198. func UserNameChanged(ctx context.Context, user *user_model.User, newName string) error {
  199. pulls, err := issues_model.GetAllUnmergedAgitPullRequestByPoster(ctx, user.ID)
  200. if err != nil {
  201. return err
  202. }
  203. newName = strings.ToLower(newName)
  204. for _, pull := range pulls {
  205. pull.HeadBranch = strings.TrimPrefix(pull.HeadBranch, user.LowerName+"/")
  206. pull.HeadBranch = newName + "/" + pull.HeadBranch
  207. if err = pull.UpdateCols("head_branch"); err != nil {
  208. return err
  209. }
  210. }
  211. return nil
  212. }