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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package agit
  5. import (
  6. "fmt"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "code.gitea.io/gitea/models"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/notification"
  15. "code.gitea.io/gitea/modules/private"
  16. pull_service "code.gitea.io/gitea/services/pull"
  17. )
  18. // ProcRecive handle proc receive work
  19. func ProcRecive(ctx *context.PrivateContext, opts *private.HookOptions) []private.HookProcReceiveRefResult {
  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. repo := ctx.Repo.Repository
  29. gitRepo := ctx.Repo.GitRepo
  30. ownerName := ctx.Repo.Repository.OwnerName
  31. repoName := ctx.Repo.Repository.Name
  32. topicBranch = opts.GitPushOptions["topic"]
  33. _, forcePush = opts.GitPushOptions["force-push"]
  34. for i := range opts.OldCommitIDs {
  35. if opts.NewCommitIDs[i] == git.EmptySHA {
  36. results = append(results, private.HookProcReceiveRefResult{
  37. OriginalRef: opts.RefFullNames[i],
  38. OldOID: opts.OldCommitIDs[i],
  39. NewOID: opts.NewCommitIDs[i],
  40. Err: "Can't delete not exist branch",
  41. })
  42. continue
  43. }
  44. if !strings.HasPrefix(opts.RefFullNames[i], git.PullRequestPrefix) {
  45. results = append(results, private.HookProcReceiveRefResult{
  46. IsNotMatched: true,
  47. OriginalRef: opts.RefFullNames[i],
  48. })
  49. continue
  50. }
  51. baseBranchName := opts.RefFullNames[i][len(git.PullRequestPrefix):]
  52. curentTopicBranch := ""
  53. if !gitRepo.IsBranchExist(baseBranchName) {
  54. // try match refs/for/<target-branch>/<topic-branch>
  55. for p, v := range baseBranchName {
  56. if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 {
  57. curentTopicBranch = baseBranchName[p+1:]
  58. baseBranchName = baseBranchName[:p]
  59. break
  60. }
  61. }
  62. }
  63. if len(topicBranch) == 0 && len(curentTopicBranch) == 0 {
  64. results = append(results, private.HookProcReceiveRefResult{
  65. OriginalRef: opts.RefFullNames[i],
  66. OldOID: opts.OldCommitIDs[i],
  67. NewOID: opts.NewCommitIDs[i],
  68. Err: "topic-branch is not set",
  69. })
  70. continue
  71. }
  72. headBranch := ""
  73. userName := strings.ToLower(opts.UserName)
  74. if len(curentTopicBranch) == 0 {
  75. curentTopicBranch = topicBranch
  76. }
  77. // because different user maybe want to use same topic,
  78. // So it's better to make sure the topic branch name
  79. // has user name prefix
  80. if !strings.HasPrefix(curentTopicBranch, userName+"/") {
  81. headBranch = userName + "/" + curentTopicBranch
  82. } else {
  83. headBranch = curentTopicBranch
  84. }
  85. pr, err := models.GetUnmergedPullRequest(repo.ID, repo.ID, headBranch, baseBranchName, models.PullRequestFlowAGit)
  86. if err != nil {
  87. if !models.IsErrPullRequestNotExist(err) {
  88. log.Error("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %v", ownerName, repoName, err)
  89. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  90. "Err": fmt.Sprintf("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %v", ownerName, repoName, err),
  91. })
  92. return nil
  93. }
  94. // create a new pull request
  95. if len(title) == 0 {
  96. has := false
  97. title, has = opts.GitPushOptions["title"]
  98. if !has || len(title) == 0 {
  99. commit, err := gitRepo.GetCommit(opts.NewCommitIDs[i])
  100. if err != nil {
  101. log.Error("Failed to get commit %s in repository: %s/%s Error: %v", opts.NewCommitIDs[i], ownerName, repoName, err)
  102. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  103. "Err": fmt.Sprintf("Failed to get commit %s in repository: %s/%s Error: %v", opts.NewCommitIDs[i], ownerName, repoName, err),
  104. })
  105. return nil
  106. }
  107. title = strings.Split(commit.CommitMessage, "\n")[0]
  108. }
  109. description = opts.GitPushOptions["description"]
  110. }
  111. pusher, err := models.GetUserByID(opts.UserID)
  112. if err != nil {
  113. log.Error("Failed to get user. Error: %v", err)
  114. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  115. "Err": fmt.Sprintf("Failed to get user. Error: %v", err),
  116. })
  117. return nil
  118. }
  119. prIssue := &models.Issue{
  120. RepoID: repo.ID,
  121. Title: title,
  122. PosterID: pusher.ID,
  123. Poster: pusher,
  124. IsPull: true,
  125. Content: description,
  126. }
  127. pr := &models.PullRequest{
  128. HeadRepoID: repo.ID,
  129. BaseRepoID: repo.ID,
  130. HeadBranch: headBranch,
  131. HeadCommitID: opts.NewCommitIDs[i],
  132. BaseBranch: baseBranchName,
  133. HeadRepo: repo,
  134. BaseRepo: repo,
  135. MergeBase: "",
  136. Type: models.PullRequestGitea,
  137. Flow: models.PullRequestFlowAGit,
  138. }
  139. if err := pull_service.NewPullRequest(repo, prIssue, []int64{}, []string{}, pr, []int64{}); err != nil {
  140. if models.IsErrUserDoesNotHaveAccessToRepo(err) {
  141. ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
  142. return nil
  143. }
  144. ctx.Error(http.StatusInternalServerError, "NewPullRequest", err.Error())
  145. return nil
  146. }
  147. log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
  148. results = append(results, private.HookProcReceiveRefResult{
  149. Ref: pr.GetGitRefName(),
  150. OriginalRef: opts.RefFullNames[i],
  151. OldOID: git.EmptySHA,
  152. NewOID: opts.NewCommitIDs[i],
  153. })
  154. continue
  155. }
  156. // update exist pull request
  157. if err := pr.LoadBaseRepo(); err != nil {
  158. log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
  159. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  160. "Err": fmt.Sprintf("Unable to load base repository for PR[%d] Error: %v", pr.ID, err),
  161. })
  162. return nil
  163. }
  164. oldCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
  165. if err != nil {
  166. log.Error("Unable to get ref commit id in base repository for PR[%d] Error: %v", pr.ID, err)
  167. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  168. "Err": fmt.Sprintf("Unable to get ref commit id in base repository for PR[%d] Error: %v", pr.ID, err),
  169. })
  170. return nil
  171. }
  172. if oldCommitID == opts.NewCommitIDs[i] {
  173. results = append(results, private.HookProcReceiveRefResult{
  174. OriginalRef: opts.RefFullNames[i],
  175. OldOID: opts.OldCommitIDs[i],
  176. NewOID: opts.NewCommitIDs[i],
  177. Err: "new commit is same with old commit",
  178. })
  179. continue
  180. }
  181. if !forcePush {
  182. output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+opts.NewCommitIDs[i]).RunInDirWithEnv(repo.RepoPath(), os.Environ())
  183. if err != nil {
  184. log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, opts.NewCommitIDs[i], repo, err)
  185. ctx.JSON(http.StatusInternalServerError, private.Response{
  186. Err: fmt.Sprintf("Fail to detect force push: %v", err),
  187. })
  188. return nil
  189. } else if len(output) > 0 {
  190. results = append(results, private.HookProcReceiveRefResult{
  191. OriginalRef: opts.RefFullNames[i],
  192. OldOID: opts.OldCommitIDs[i],
  193. NewOID: opts.NewCommitIDs[i],
  194. Err: "request `force-push` push option",
  195. })
  196. continue
  197. }
  198. }
  199. pr.HeadCommitID = opts.NewCommitIDs[i]
  200. if err = pull_service.UpdateRef(pr); err != nil {
  201. log.Error("Failed to update pull ref. Error: %v", err)
  202. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  203. "Err": fmt.Sprintf("Failed to update pull ref. Error: %v", err),
  204. })
  205. return nil
  206. }
  207. pull_service.AddToTaskQueue(pr)
  208. pusher, err := models.GetUserByID(opts.UserID)
  209. if err != nil {
  210. log.Error("Failed to get user. Error: %v", err)
  211. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  212. "Err": fmt.Sprintf("Failed to get user. Error: %v", err),
  213. })
  214. return nil
  215. }
  216. err = pr.LoadIssue()
  217. if err != nil {
  218. log.Error("Failed to load pull issue. Error: %v", err)
  219. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  220. "Err": fmt.Sprintf("Failed to load pull issue. Error: %v", err),
  221. })
  222. return nil
  223. }
  224. comment, err := models.CreatePushPullComment(pusher, pr, oldCommitID, opts.NewCommitIDs[i])
  225. if err == nil && comment != nil {
  226. notification.NotifyPullRequestPushCommits(pusher, pr, comment)
  227. }
  228. notification.NotifyPullRequestSynchronized(pusher, pr)
  229. isForcePush := comment != nil && comment.IsForcePush
  230. results = append(results, private.HookProcReceiveRefResult{
  231. OldOID: oldCommitID,
  232. NewOID: opts.NewCommitIDs[i],
  233. Ref: pr.GetGitRefName(),
  234. OriginalRef: opts.RefFullNames[i],
  235. IsForcePush: isForcePush,
  236. })
  237. }
  238. return results
  239. }
  240. // UserNameChanged hanle user name change for agit flow pull
  241. func UserNameChanged(user *models.User, newName string) error {
  242. pulls, err := models.GetAllUnmergedAgitPullRequestByPoster(user.ID)
  243. if err != nil {
  244. return err
  245. }
  246. newName = strings.ToLower(newName)
  247. for _, pull := range pulls {
  248. pull.HeadBranch = strings.TrimPrefix(pull.HeadBranch, user.LowerName+"/")
  249. pull.HeadBranch = newName + "/" + pull.HeadBranch
  250. if err = pull.UpdateCols("head_branch"); err != nil {
  251. return err
  252. }
  253. }
  254. return nil
  255. }