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.

262 lines
7.2 KiB

  1. // Copyright 2015 The Gogs 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 models
  5. import (
  6. "fmt"
  7. "io/ioutil"
  8. "os"
  9. "path"
  10. "strings"
  11. "time"
  12. "github.com/Unknwon/com"
  13. "github.com/go-xorm/xorm"
  14. "github.com/gogits/gogs/modules/git"
  15. "github.com/gogits/gogs/modules/log"
  16. "github.com/gogits/gogs/modules/process"
  17. )
  18. type PullRequestType int
  19. const (
  20. PULL_REQUEST_GOGS PullRequestType = iota
  21. PLLL_ERQUEST_GIT
  22. )
  23. type PullRequestStatus int
  24. const (
  25. PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
  26. PULL_REQUEST_STATUS_CHECKING
  27. PULL_REQUEST_STATUS_MERGEABLE
  28. )
  29. // PullRequest represents relation between pull request and repositories.
  30. type PullRequest struct {
  31. ID int64 `xorm:"pk autoincr"`
  32. Type PullRequestType
  33. Status PullRequestStatus
  34. IssueID int64 `xorm:"INDEX"`
  35. Issue *Issue `xorm:"-"`
  36. Index int64
  37. HeadRepoID int64
  38. HeadRepo *Repository `xorm:"-"`
  39. BaseRepoID int64
  40. HeadUserName string
  41. HeadBranch string
  42. BaseBranch string
  43. MergeBase string `xorm:"VARCHAR(40)"`
  44. MergedCommitID string `xorm:"VARCHAR(40)"`
  45. HasMerged bool
  46. Merged time.Time
  47. MergerID int64
  48. Merger *User `xorm:"-"`
  49. }
  50. // Note: don't try to get Pull because will end up recursive querying.
  51. func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
  52. var err error
  53. switch colName {
  54. case "head_repo_id":
  55. // FIXME: shouldn't show error if it's known that head repository has been removed.
  56. pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
  57. if err != nil {
  58. log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err)
  59. }
  60. case "merger_id":
  61. if !pr.HasMerged {
  62. return
  63. }
  64. pr.Merger, err = GetUserByID(pr.MergerID)
  65. if err != nil {
  66. if IsErrUserNotExist(err) {
  67. pr.MergerID = -1
  68. pr.Merger = NewFakeUser()
  69. } else {
  70. log.Error(3, "GetUserByID[%d]: %v", pr.ID, err)
  71. }
  72. }
  73. case "merged":
  74. if !pr.HasMerged {
  75. return
  76. }
  77. pr.Merged = regulateTimeZone(pr.Merged)
  78. }
  79. }
  80. // CanAutoMerge returns true if this pull request can be merged automatically.
  81. func (pr *PullRequest) CanAutoMerge() bool {
  82. return pr.Status == PULL_REQUEST_STATUS_MERGEABLE
  83. }
  84. // Merge merges pull request to base repository.
  85. func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
  86. sess := x.NewSession()
  87. defer sessionRelease(sess)
  88. if err = sess.Begin(); err != nil {
  89. return err
  90. }
  91. if err = pr.Issue.changeStatus(sess, doer, true); err != nil {
  92. return fmt.Errorf("Pull.changeStatus: %v", err)
  93. }
  94. headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
  95. headGitRepo, err := git.OpenRepository(headRepoPath)
  96. if err != nil {
  97. return fmt.Errorf("OpenRepository: %v", err)
  98. }
  99. pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBranch)
  100. if err != nil {
  101. return fmt.Errorf("GetCommitIdOfBranch: %v", err)
  102. }
  103. if err = mergePullRequestAction(sess, doer, pr.Issue.Repo, pr.Issue); err != nil {
  104. return fmt.Errorf("mergePullRequestAction: %v", err)
  105. }
  106. pr.HasMerged = true
  107. pr.Merged = time.Now()
  108. pr.MergerID = doer.Id
  109. if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
  110. return fmt.Errorf("update pull request: %v", err)
  111. }
  112. // Clone base repo.
  113. tmpBasePath := path.Join("data/tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
  114. os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
  115. defer os.RemoveAll(path.Dir(tmpBasePath))
  116. var stderr string
  117. if _, stderr, err = process.ExecTimeout(5*time.Minute,
  118. fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
  119. "git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
  120. return fmt.Errorf("git clone: %s", stderr)
  121. }
  122. // Check out base branch.
  123. if _, stderr, err = process.ExecDir(-1, tmpBasePath,
  124. fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
  125. "git", "checkout", pr.BaseBranch); err != nil {
  126. return fmt.Errorf("git checkout: %s", stderr)
  127. }
  128. // Pull commits.
  129. if _, stderr, err = process.ExecDir(-1, tmpBasePath,
  130. fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath),
  131. "git", "pull", headRepoPath, pr.HeadBranch); err != nil {
  132. return fmt.Errorf("git pull[%s / %s -> %s]: %s", headRepoPath, pr.HeadBranch, tmpBasePath, stderr)
  133. }
  134. // Push back to upstream.
  135. if _, stderr, err = process.ExecDir(-1, tmpBasePath,
  136. fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
  137. "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
  138. return fmt.Errorf("git push: %s", stderr)
  139. }
  140. return sess.Commit()
  141. }
  142. // NewPullRequest creates new pull request with labels for repository.
  143. func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
  144. sess := x.NewSession()
  145. defer sessionRelease(sess)
  146. if err = sess.Begin(); err != nil {
  147. return err
  148. }
  149. if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
  150. return fmt.Errorf("newIssue: %v", err)
  151. }
  152. // Notify watchers.
  153. act := &Action{
  154. ActUserID: pull.Poster.Id,
  155. ActUserName: pull.Poster.Name,
  156. ActEmail: pull.Poster.Email,
  157. OpType: CREATE_PULL_REQUEST,
  158. Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
  159. RepoID: repo.ID,
  160. RepoUserName: repo.Owner.Name,
  161. RepoName: repo.Name,
  162. IsPrivate: repo.IsPrivate,
  163. }
  164. if err = notifyWatchers(sess, act); err != nil {
  165. return err
  166. }
  167. // Test apply patch.
  168. if err = repo.UpdateLocalCopy(); err != nil {
  169. return fmt.Errorf("UpdateLocalCopy: %v", err)
  170. }
  171. repoPath, err := repo.RepoPath()
  172. if err != nil {
  173. return fmt.Errorf("RepoPath: %v", err)
  174. }
  175. patchPath := path.Join(repoPath, "pulls", com.ToStr(pull.ID)+".patch")
  176. os.MkdirAll(path.Dir(patchPath), os.ModePerm)
  177. if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
  178. return fmt.Errorf("save patch: %v", err)
  179. }
  180. pr.Status = PULL_REQUEST_STATUS_MERGEABLE
  181. _, stderr, err := process.ExecDir(-1, repo.LocalCopyPath(),
  182. fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID),
  183. "git", "apply", "--check", patchPath)
  184. if err != nil {
  185. if strings.Contains(stderr, "patch does not apply") {
  186. pr.Status = PULL_REQUEST_STATUS_CONFLICT
  187. } else {
  188. return fmt.Errorf("git apply --check: %v - %s", err, stderr)
  189. }
  190. }
  191. pr.IssueID = pull.ID
  192. pr.Index = pull.Index
  193. if _, err = sess.Insert(pr); err != nil {
  194. return fmt.Errorf("insert pull repo: %v", err)
  195. }
  196. return sess.Commit()
  197. }
  198. // GetUnmergedPullRequest returnss a pull request that is open and has not been merged
  199. // by given head/base and repo/branch.
  200. func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
  201. pr := new(PullRequest)
  202. has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
  203. headRepoID, headBranch, baseRepoID, baseBranch, false, false).
  204. Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr)
  205. if err != nil {
  206. return nil, err
  207. } else if !has {
  208. return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
  209. }
  210. return pr, nil
  211. }
  212. // GetPullRequestByIssueID returns pull request by given issue ID.
  213. func GetPullRequestByIssueID(pullID int64) (*PullRequest, error) {
  214. pr := new(PullRequest)
  215. has, err := x.Where("pull_id=?", pullID).Get(pr)
  216. if err != nil {
  217. return nil, err
  218. } else if !has {
  219. return nil, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""}
  220. }
  221. return pr, nil
  222. }