You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

pull.go 37 kB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  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. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "code.gitea.io/git"
  15. "code.gitea.io/gitea/modules/base"
  16. "code.gitea.io/gitea/modules/cache"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/process"
  19. "code.gitea.io/gitea/modules/setting"
  20. "code.gitea.io/gitea/modules/sync"
  21. "code.gitea.io/gitea/modules/util"
  22. api "code.gitea.io/sdk/gitea"
  23. "github.com/Unknwon/com"
  24. "github.com/go-xorm/xorm"
  25. )
  26. var pullRequestQueue = sync.NewUniqueQueue(setting.Repository.PullRequestQueueLength)
  27. // PullRequestType defines pull request type
  28. type PullRequestType int
  29. // Enumerate all the pull request types
  30. const (
  31. PullRequestGitea PullRequestType = iota
  32. PullRequestGit
  33. )
  34. // PullRequestStatus defines pull request status
  35. type PullRequestStatus int
  36. // Enumerate all the pull request status
  37. const (
  38. PullRequestStatusConflict PullRequestStatus = iota
  39. PullRequestStatusChecking
  40. PullRequestStatusMergeable
  41. PullRequestStatusManuallyMerged
  42. )
  43. // PullRequest represents relation between pull request and repositories.
  44. type PullRequest struct {
  45. ID int64 `xorm:"pk autoincr"`
  46. Type PullRequestType
  47. Status PullRequestStatus
  48. IssueID int64 `xorm:"INDEX"`
  49. Issue *Issue `xorm:"-"`
  50. Index int64
  51. HeadRepoID int64 `xorm:"INDEX"`
  52. HeadRepo *Repository `xorm:"-"`
  53. BaseRepoID int64 `xorm:"INDEX"`
  54. BaseRepo *Repository `xorm:"-"`
  55. HeadUserName string
  56. HeadBranch string
  57. BaseBranch string
  58. MergeBase string `xorm:"VARCHAR(40)"`
  59. HasMerged bool `xorm:"INDEX"`
  60. MergedCommitID string `xorm:"VARCHAR(40)"`
  61. MergerID int64 `xorm:"INDEX"`
  62. Merger *User `xorm:"-"`
  63. MergedUnix util.TimeStamp `xorm:"updated INDEX"`
  64. }
  65. // Note: don't try to get Issue because will end up recursive querying.
  66. func (pr *PullRequest) loadAttributes(e Engine) (err error) {
  67. if pr.HasMerged && pr.Merger == nil {
  68. pr.Merger, err = getUserByID(e, pr.MergerID)
  69. if IsErrUserNotExist(err) {
  70. pr.MergerID = -1
  71. pr.Merger = NewGhostUser()
  72. } else if err != nil {
  73. return fmt.Errorf("getUserByID [%d]: %v", pr.MergerID, err)
  74. }
  75. }
  76. return nil
  77. }
  78. // LoadAttributes loads pull request attributes from database
  79. func (pr *PullRequest) LoadAttributes() error {
  80. return pr.loadAttributes(x)
  81. }
  82. // LoadIssue loads issue information from database
  83. func (pr *PullRequest) LoadIssue() (err error) {
  84. return pr.loadIssue(x)
  85. }
  86. func (pr *PullRequest) loadIssue(e Engine) (err error) {
  87. if pr.Issue != nil {
  88. return nil
  89. }
  90. pr.Issue, err = getIssueByID(e, pr.IssueID)
  91. return err
  92. }
  93. // GetDefaultMergeMessage returns default message used when merging pull request
  94. func (pr *PullRequest) GetDefaultMergeMessage() string {
  95. if pr.HeadRepo == nil {
  96. var err error
  97. pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
  98. if err != nil {
  99. log.Error(4, "GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
  100. return ""
  101. }
  102. }
  103. return fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch)
  104. }
  105. // GetDefaultSquashMessage returns default message used when squash and merging pull request
  106. func (pr *PullRequest) GetDefaultSquashMessage() string {
  107. if err := pr.LoadIssue(); err != nil {
  108. log.Error(4, "LoadIssue: %v", err)
  109. return ""
  110. }
  111. return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index)
  112. }
  113. // APIFormat assumes following fields have been assigned with valid values:
  114. // Required - Issue
  115. // Optional - Merger
  116. func (pr *PullRequest) APIFormat() *api.PullRequest {
  117. var (
  118. baseBranch *Branch
  119. headBranch *Branch
  120. baseCommit *git.Commit
  121. headCommit *git.Commit
  122. err error
  123. )
  124. apiIssue := pr.Issue.APIFormat()
  125. if pr.BaseRepo == nil {
  126. pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
  127. if err != nil {
  128. log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err)
  129. return nil
  130. }
  131. }
  132. if pr.HeadRepo == nil {
  133. pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
  134. if err != nil {
  135. log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err)
  136. return nil
  137. }
  138. }
  139. if baseBranch, err = pr.BaseRepo.GetBranch(pr.BaseBranch); err != nil {
  140. return nil
  141. }
  142. if baseCommit, err = baseBranch.GetCommit(); err != nil {
  143. return nil
  144. }
  145. if headBranch, err = pr.HeadRepo.GetBranch(pr.HeadBranch); err != nil {
  146. return nil
  147. }
  148. if headCommit, err = headBranch.GetCommit(); err != nil {
  149. return nil
  150. }
  151. apiBaseBranchInfo := &api.PRBranchInfo{
  152. Name: pr.BaseBranch,
  153. Ref: pr.BaseBranch,
  154. Sha: baseCommit.ID.String(),
  155. RepoID: pr.BaseRepoID,
  156. Repository: pr.BaseRepo.APIFormat(AccessModeNone),
  157. }
  158. apiHeadBranchInfo := &api.PRBranchInfo{
  159. Name: pr.HeadBranch,
  160. Ref: pr.HeadBranch,
  161. Sha: headCommit.ID.String(),
  162. RepoID: pr.HeadRepoID,
  163. Repository: pr.HeadRepo.APIFormat(AccessModeNone),
  164. }
  165. apiPullRequest := &api.PullRequest{
  166. ID: pr.ID,
  167. Index: pr.Index,
  168. Poster: apiIssue.Poster,
  169. Title: apiIssue.Title,
  170. Body: apiIssue.Body,
  171. Labels: apiIssue.Labels,
  172. Milestone: apiIssue.Milestone,
  173. Assignee: apiIssue.Assignee,
  174. State: apiIssue.State,
  175. Comments: apiIssue.Comments,
  176. HTMLURL: pr.Issue.HTMLURL(),
  177. DiffURL: pr.Issue.DiffURL(),
  178. PatchURL: pr.Issue.PatchURL(),
  179. HasMerged: pr.HasMerged,
  180. Base: apiBaseBranchInfo,
  181. Head: apiHeadBranchInfo,
  182. MergeBase: pr.MergeBase,
  183. Created: pr.Issue.CreatedUnix.AsTimePtr(),
  184. Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
  185. }
  186. if pr.Status != PullRequestStatusChecking {
  187. mergeable := pr.Status != PullRequestStatusConflict
  188. apiPullRequest.Mergeable = mergeable
  189. }
  190. if pr.HasMerged {
  191. apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
  192. apiPullRequest.MergedCommitID = &pr.MergedCommitID
  193. apiPullRequest.MergedBy = pr.Merger.APIFormat()
  194. }
  195. return apiPullRequest
  196. }
  197. func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
  198. pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
  199. if err != nil && !IsErrRepoNotExist(err) {
  200. return fmt.Errorf("getRepositoryByID(head): %v", err)
  201. }
  202. return nil
  203. }
  204. // GetHeadRepo loads the head repository
  205. func (pr *PullRequest) GetHeadRepo() error {
  206. return pr.getHeadRepo(x)
  207. }
  208. // GetBaseRepo loads the target repository
  209. func (pr *PullRequest) GetBaseRepo() (err error) {
  210. if pr.BaseRepo != nil {
  211. return nil
  212. }
  213. pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
  214. if err != nil {
  215. return fmt.Errorf("GetRepositoryByID(base): %v", err)
  216. }
  217. return nil
  218. }
  219. // IsChecking returns true if this pull request is still checking conflict.
  220. func (pr *PullRequest) IsChecking() bool {
  221. return pr.Status == PullRequestStatusChecking
  222. }
  223. // CanAutoMerge returns true if this pull request can be merged automatically.
  224. func (pr *PullRequest) CanAutoMerge() bool {
  225. return pr.Status == PullRequestStatusMergeable
  226. }
  227. // MergeStyle represents the approach to merge commits into base branch.
  228. type MergeStyle string
  229. const (
  230. // MergeStyleMerge create merge commit
  231. MergeStyleMerge MergeStyle = "merge"
  232. // MergeStyleRebase rebase before merging
  233. MergeStyleRebase MergeStyle = "rebase"
  234. // MergeStyleSquash squash commits into single commit before merging
  235. MergeStyleSquash MergeStyle = "squash"
  236. )
  237. // Merge merges pull request to base repository.
  238. // FIXME: add repoWorkingPull make sure two merges does not happen at same time.
  239. func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle MergeStyle, message string) (err error) {
  240. if err = pr.GetHeadRepo(); err != nil {
  241. return fmt.Errorf("GetHeadRepo: %v", err)
  242. } else if err = pr.GetBaseRepo(); err != nil {
  243. return fmt.Errorf("GetBaseRepo: %v", err)
  244. }
  245. prUnit, err := pr.BaseRepo.GetUnit(UnitTypePullRequests)
  246. if err != nil {
  247. return err
  248. }
  249. prConfig := prUnit.PullRequestsConfig()
  250. // Check if merge style is correct and allowed
  251. if !prConfig.IsMergeStyleAllowed(mergeStyle) {
  252. return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle}
  253. }
  254. defer func() {
  255. go HookQueue.Add(pr.BaseRepo.ID)
  256. go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
  257. }()
  258. headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
  259. headGitRepo, err := git.OpenRepository(headRepoPath)
  260. if err != nil {
  261. return fmt.Errorf("OpenRepository: %v", err)
  262. }
  263. // Clone base repo.
  264. tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
  265. if err := os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm); err != nil {
  266. return fmt.Errorf("Failed to create dir %s: %v", tmpBasePath, err)
  267. }
  268. defer os.RemoveAll(path.Dir(tmpBasePath))
  269. var stderr string
  270. if _, stderr, err = process.GetManager().ExecTimeout(5*time.Minute,
  271. fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
  272. "git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
  273. return fmt.Errorf("git clone: %s", stderr)
  274. }
  275. // Check out base branch.
  276. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  277. fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
  278. "git", "checkout", pr.BaseBranch); err != nil {
  279. return fmt.Errorf("git checkout: %s", stderr)
  280. }
  281. // Add head repo remote.
  282. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  283. fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
  284. "git", "remote", "add", "head_repo", headRepoPath); err != nil {
  285. return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
  286. }
  287. // Merge commits.
  288. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  289. fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
  290. "git", "fetch", "head_repo"); err != nil {
  291. return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
  292. }
  293. switch mergeStyle {
  294. case MergeStyleMerge:
  295. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  296. fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
  297. "git", "merge", "--no-ff", "--no-commit", "head_repo/"+pr.HeadBranch); err != nil {
  298. return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
  299. }
  300. sig := doer.NewGitSig()
  301. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  302. fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
  303. "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
  304. "-m", message); err != nil {
  305. return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
  306. }
  307. case MergeStyleRebase:
  308. // Checkout head branch
  309. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  310. fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
  311. "git", "checkout", "-b", "head_repo_"+pr.HeadBranch, "head_repo/"+pr.HeadBranch); err != nil {
  312. return fmt.Errorf("git checkout: %s", stderr)
  313. }
  314. // Rebase before merging
  315. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  316. fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
  317. "git", "rebase", "-q", pr.BaseBranch); err != nil {
  318. return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
  319. }
  320. // Checkout base branch again
  321. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  322. fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
  323. "git", "checkout", pr.BaseBranch); err != nil {
  324. return fmt.Errorf("git checkout: %s", stderr)
  325. }
  326. // Merge fast forward
  327. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  328. fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
  329. "git", "merge", "--ff-only", "-q", "head_repo_"+pr.HeadBranch); err != nil {
  330. return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
  331. }
  332. case MergeStyleSquash:
  333. // Merge with squash
  334. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  335. fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath),
  336. "git", "merge", "-q", "--squash", "head_repo/"+pr.HeadBranch); err != nil {
  337. return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
  338. }
  339. sig := pr.Issue.Poster.NewGitSig()
  340. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  341. fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath),
  342. "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
  343. "-m", message); err != nil {
  344. return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
  345. }
  346. default:
  347. return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle}
  348. }
  349. // Push back to upstream.
  350. if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath,
  351. fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath),
  352. "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
  353. return fmt.Errorf("git push: %s", stderr)
  354. }
  355. pr.MergedCommitID, err = baseGitRepo.GetBranchCommitID(pr.BaseBranch)
  356. if err != nil {
  357. return fmt.Errorf("GetBranchCommit: %v", err)
  358. }
  359. pr.MergedUnix = util.TimeStampNow()
  360. pr.Merger = doer
  361. pr.MergerID = doer.ID
  362. if err = pr.setMerged(); err != nil {
  363. log.Error(4, "setMerged [%d]: %v", pr.ID, err)
  364. }
  365. if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil {
  366. log.Error(4, "MergePullRequestAction [%d]: %v", pr.ID, err)
  367. }
  368. // Reset cached commit count
  369. cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
  370. // Reload pull request information.
  371. if err = pr.LoadAttributes(); err != nil {
  372. log.Error(4, "LoadAttributes: %v", err)
  373. return nil
  374. }
  375. if err = PrepareWebhooks(pr.Issue.Repo, HookEventPullRequest, &api.PullRequestPayload{
  376. Action: api.HookIssueClosed,
  377. Index: pr.Index,
  378. PullRequest: pr.APIFormat(),
  379. Repository: pr.Issue.Repo.APIFormat(AccessModeNone),
  380. Sender: doer.APIFormat(),
  381. }); err != nil {
  382. log.Error(4, "PrepareWebhooks: %v", err)
  383. return nil
  384. }
  385. l, err := headGitRepo.CommitsBetweenIDs(pr.MergedCommitID, pr.MergeBase)
  386. if err != nil {
  387. log.Error(4, "CommitsBetweenIDs: %v", err)
  388. return nil
  389. }
  390. // It is possible that head branch is not fully sync with base branch for merge commits,
  391. // so we need to get latest head commit and append merge commit manually
  392. // to avoid strange diff commits produced.
  393. mergeCommit, err := baseGitRepo.GetBranchCommit(pr.BaseBranch)
  394. if err != nil {
  395. log.Error(4, "GetBranchCommit: %v", err)
  396. return nil
  397. }
  398. if mergeStyle == MergeStyleMerge {
  399. l.PushFront(mergeCommit)
  400. }
  401. p := &api.PushPayload{
  402. Ref: git.BranchPrefix + pr.BaseBranch,
  403. Before: pr.MergeBase,
  404. After: mergeCommit.ID.String(),
  405. CompareURL: setting.AppURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
  406. Commits: ListToPushCommits(l).ToAPIPayloadCommits(pr.BaseRepo.HTMLURL()),
  407. Repo: pr.BaseRepo.APIFormat(AccessModeNone),
  408. Pusher: pr.HeadRepo.MustOwner().APIFormat(),
  409. Sender: doer.APIFormat(),
  410. }
  411. if err = PrepareWebhooks(pr.BaseRepo, HookEventPush, p); err != nil {
  412. return fmt.Errorf("PrepareWebhooks: %v", err)
  413. }
  414. return nil
  415. }
  416. // setMerged sets a pull request to merged and closes the corresponding issue
  417. func (pr *PullRequest) setMerged() (err error) {
  418. if pr.HasMerged {
  419. return fmt.Errorf("PullRequest[%d] already merged", pr.Index)
  420. }
  421. if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
  422. return fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
  423. }
  424. pr.HasMerged = true
  425. sess := x.NewSession()
  426. defer sess.Close()
  427. if err = sess.Begin(); err != nil {
  428. return err
  429. }
  430. if err = pr.loadIssue(sess); err != nil {
  431. return err
  432. }
  433. if err = pr.Issue.loadRepo(sess); err != nil {
  434. return err
  435. }
  436. if err = pr.Issue.Repo.getOwner(sess); err != nil {
  437. return err
  438. }
  439. if err = pr.Issue.changeStatus(sess, pr.Merger, pr.Issue.Repo, true); err != nil {
  440. return fmt.Errorf("Issue.changeStatus: %v", err)
  441. }
  442. if _, err = sess.ID(pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
  443. return fmt.Errorf("update pull request: %v", err)
  444. }
  445. if err = sess.Commit(); err != nil {
  446. return fmt.Errorf("Commit: %v", err)
  447. }
  448. return nil
  449. }
  450. // manuallyMerged checks if a pull request got manually merged
  451. // When a pull request got manually merged mark the pull request as merged
  452. func (pr *PullRequest) manuallyMerged() bool {
  453. commit, err := pr.getMergeCommit()
  454. if err != nil {
  455. log.Error(4, "PullRequest[%d].getMergeCommit: %v", pr.ID, err)
  456. return false
  457. }
  458. if commit != nil {
  459. pr.MergedCommitID = commit.ID.String()
  460. pr.MergedUnix = util.TimeStamp(commit.Author.When.Unix())
  461. pr.Status = PullRequestStatusManuallyMerged
  462. merger, _ := GetUserByEmail(commit.Author.Email)
  463. // When the commit author is unknown set the BaseRepo owner as merger
  464. if merger == nil {
  465. if pr.BaseRepo.Owner == nil {
  466. if err = pr.BaseRepo.getOwner(x); err != nil {
  467. log.Error(4, "BaseRepo.getOwner[%d]: %v", pr.ID, err)
  468. return false
  469. }
  470. }
  471. merger = pr.BaseRepo.Owner
  472. }
  473. pr.Merger = merger
  474. pr.MergerID = merger.ID
  475. if err = pr.setMerged(); err != nil {
  476. log.Error(4, "PullRequest[%d].setMerged : %v", pr.ID, err)
  477. return false
  478. }
  479. log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String())
  480. return true
  481. }
  482. return false
  483. }
  484. // getMergeCommit checks if a pull request got merged
  485. // Returns the git.Commit of the pull request if merged
  486. func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
  487. if pr.BaseRepo == nil {
  488. var err error
  489. pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
  490. if err != nil {
  491. return nil, fmt.Errorf("GetRepositoryByID: %v", err)
  492. }
  493. }
  494. indexTmpPath := filepath.Join(os.TempDir(), "gitea-"+pr.BaseRepo.Name+"-"+strconv.Itoa(time.Now().Nanosecond()))
  495. defer os.Remove(indexTmpPath)
  496. headFile := fmt.Sprintf("refs/pull/%d/head", pr.Index)
  497. // Check if a pull request is merged into BaseBranch
  498. _, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git merge-base --is-ancestor): %d", pr.BaseRepo.ID),
  499. []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
  500. "git", "merge-base", "--is-ancestor", headFile, pr.BaseBranch)
  501. if err != nil {
  502. // Errors are signaled by a non-zero status that is not 1
  503. if strings.Contains(err.Error(), "exit status 1") {
  504. return nil, nil
  505. }
  506. return nil, fmt.Errorf("git merge-base --is-ancestor: %v %v", stderr, err)
  507. }
  508. commitIDBytes, err := ioutil.ReadFile(pr.BaseRepo.RepoPath() + "/" + headFile)
  509. if err != nil {
  510. return nil, fmt.Errorf("ReadFile(%s): %v", headFile, err)
  511. }
  512. commitID := string(commitIDBytes)
  513. if len(commitID) < 40 {
  514. return nil, fmt.Errorf(`ReadFile(%s): invalid commit-ID "%s"`, headFile, commitID)
  515. }
  516. cmd := commitID[:40] + ".." + pr.BaseBranch
  517. // Get the commit from BaseBranch where the pull request got merged
  518. mergeCommit, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git rev-list --ancestry-path --merges --reverse): %d", pr.BaseRepo.ID),
  519. []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
  520. "git", "rev-list", "--ancestry-path", "--merges", "--reverse", cmd)
  521. if err != nil {
  522. return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v %v", stderr, err)
  523. } else if len(mergeCommit) < 40 {
  524. // PR was fast-forwarded, so just use last commit of PR
  525. mergeCommit = commitID[:40]
  526. }
  527. gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  528. if err != nil {
  529. return nil, fmt.Errorf("OpenRepository: %v", err)
  530. }
  531. commit, err := gitRepo.GetCommit(mergeCommit[:40])
  532. if err != nil {
  533. return nil, fmt.Errorf("GetCommit: %v", err)
  534. }
  535. return commit, nil
  536. }
  537. // patchConflicts is a list of conflict description from Git.
  538. var patchConflicts = []string{
  539. "patch does not apply",
  540. "already exists in working directory",
  541. "unrecognized input",
  542. "error:",
  543. }
  544. // testPatch checks if patch can be merged to base repository without conflict.
  545. func (pr *PullRequest) testPatch() (err error) {
  546. if pr.BaseRepo == nil {
  547. pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
  548. if err != nil {
  549. return fmt.Errorf("GetRepositoryByID: %v", err)
  550. }
  551. }
  552. patchPath, err := pr.BaseRepo.PatchPath(pr.Index)
  553. if err != nil {
  554. return fmt.Errorf("BaseRepo.PatchPath: %v", err)
  555. }
  556. // Fast fail if patch does not exist, this assumes data is corrupted.
  557. if !com.IsFile(patchPath) {
  558. log.Trace("PullRequest[%d].testPatch: ignored corrupted data", pr.ID)
  559. return nil
  560. }
  561. repoWorkingPool.CheckIn(com.ToStr(pr.BaseRepoID))
  562. defer repoWorkingPool.CheckOut(com.ToStr(pr.BaseRepoID))
  563. log.Trace("PullRequest[%d].testPatch (patchPath): %s", pr.ID, patchPath)
  564. pr.Status = PullRequestStatusChecking
  565. indexTmpPath := filepath.Join(os.TempDir(), "gitea-"+pr.BaseRepo.Name+"-"+strconv.Itoa(time.Now().Nanosecond()))
  566. defer os.Remove(indexTmpPath)
  567. var stderr string
  568. _, stderr, err = process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("testPatch (git read-tree): %d", pr.BaseRepo.ID),
  569. []string{"GIT_DIR=" + pr.BaseRepo.RepoPath(), "GIT_INDEX_FILE=" + indexTmpPath},
  570. "git", "read-tree", pr.BaseBranch)
  571. if err != nil {
  572. return fmt.Errorf("git read-tree --index-output=%s %s: %v - %s", indexTmpPath, pr.BaseBranch, err, stderr)
  573. }
  574. prUnit, err := pr.BaseRepo.GetUnit(UnitTypePullRequests)
  575. if err != nil {
  576. return err
  577. }
  578. prConfig := prUnit.PullRequestsConfig()
  579. args := []string{"apply", "--check", "--cached"}
  580. if prConfig.IgnoreWhitespaceConflicts {
  581. args = append(args, "--ignore-whitespace")
  582. }
  583. args = append(args, patchPath)
  584. _, stderr, err = process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("testPatch (git apply --check): %d", pr.BaseRepo.ID),
  585. []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
  586. "git", args...)
  587. if err != nil {
  588. for i := range patchConflicts {
  589. if strings.Contains(stderr, patchConflicts[i]) {
  590. log.Trace("PullRequest[%d].testPatch (apply): has conflict", pr.ID)
  591. fmt.Println(stderr)
  592. pr.Status = PullRequestStatusConflict
  593. return nil
  594. }
  595. }
  596. return fmt.Errorf("git apply --check: %v - %s", err, stderr)
  597. }
  598. return nil
  599. }
  600. // NewPullRequest creates new pull request with labels for repository.
  601. func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
  602. sess := x.NewSession()
  603. defer sess.Close()
  604. if err = sess.Begin(); err != nil {
  605. return err
  606. }
  607. if err = newIssue(sess, pull.Poster, NewIssueOptions{
  608. Repo: repo,
  609. Issue: pull,
  610. LabelIDs: labelIDs,
  611. Attachments: uuids,
  612. IsPull: true,
  613. }); err != nil {
  614. return fmt.Errorf("newIssue: %v", err)
  615. }
  616. pr.Index = pull.Index
  617. if err = repo.SavePatch(pr.Index, patch); err != nil {
  618. return fmt.Errorf("SavePatch: %v", err)
  619. }
  620. pr.BaseRepo = repo
  621. if err = pr.testPatch(); err != nil {
  622. return fmt.Errorf("testPatch: %v", err)
  623. }
  624. // No conflict appears after test means mergeable.
  625. if pr.Status == PullRequestStatusChecking {
  626. pr.Status = PullRequestStatusMergeable
  627. }
  628. pr.IssueID = pull.ID
  629. if _, err = sess.Insert(pr); err != nil {
  630. return fmt.Errorf("insert pull repo: %v", err)
  631. }
  632. if err = sess.Commit(); err != nil {
  633. return fmt.Errorf("Commit: %v", err)
  634. }
  635. UpdateIssueIndexer(pull.ID)
  636. if err = NotifyWatchers(&Action{
  637. ActUserID: pull.Poster.ID,
  638. ActUser: pull.Poster,
  639. OpType: ActionCreatePullRequest,
  640. Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
  641. RepoID: repo.ID,
  642. Repo: repo,
  643. IsPrivate: repo.IsPrivate,
  644. }); err != nil {
  645. log.Error(4, "NotifyWatchers: %v", err)
  646. } else if err = pull.MailParticipants(); err != nil {
  647. log.Error(4, "MailParticipants: %v", err)
  648. }
  649. pr.Issue = pull
  650. pull.PullRequest = pr
  651. if err = PrepareWebhooks(repo, HookEventPullRequest, &api.PullRequestPayload{
  652. Action: api.HookIssueOpened,
  653. Index: pull.Index,
  654. PullRequest: pr.APIFormat(),
  655. Repository: repo.APIFormat(AccessModeNone),
  656. Sender: pull.Poster.APIFormat(),
  657. }); err != nil {
  658. log.Error(4, "PrepareWebhooks: %v", err)
  659. }
  660. go HookQueue.Add(repo.ID)
  661. return nil
  662. }
  663. // PullRequestsOptions holds the options for PRs
  664. type PullRequestsOptions struct {
  665. Page int
  666. State string
  667. SortType string
  668. Labels []string
  669. MilestoneID int64
  670. }
  671. func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xorm.Session, error) {
  672. sess := x.Where("pull_request.base_repo_id=?", baseRepoID)
  673. sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
  674. switch opts.State {
  675. case "closed", "open":
  676. sess.And("issue.is_closed=?", opts.State == "closed")
  677. }
  678. if labelIDs, err := base.StringsToInt64s(opts.Labels); err != nil {
  679. return nil, err
  680. } else if len(labelIDs) > 0 {
  681. sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
  682. In("issue_label.label_id", labelIDs)
  683. }
  684. if opts.MilestoneID > 0 {
  685. sess.And("issue.milestone_id=?", opts.MilestoneID)
  686. }
  687. return sess, nil
  688. }
  689. // PullRequests returns all pull requests for a base Repo by the given conditions
  690. func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, int64, error) {
  691. if opts.Page <= 0 {
  692. opts.Page = 1
  693. }
  694. countSession, err := listPullRequestStatement(baseRepoID, opts)
  695. if err != nil {
  696. log.Error(4, "listPullRequestStatement", err)
  697. return nil, 0, err
  698. }
  699. maxResults, err := countSession.Count(new(PullRequest))
  700. if err != nil {
  701. log.Error(4, "Count PRs", err)
  702. return nil, maxResults, err
  703. }
  704. prs := make([]*PullRequest, 0, ItemsPerPage)
  705. findSession, err := listPullRequestStatement(baseRepoID, opts)
  706. sortIssuesSession(findSession, opts.SortType)
  707. if err != nil {
  708. log.Error(4, "listPullRequestStatement", err)
  709. return nil, maxResults, err
  710. }
  711. findSession.Limit(ItemsPerPage, (opts.Page-1)*ItemsPerPage)
  712. return prs, maxResults, findSession.Find(&prs)
  713. }
  714. // GetUnmergedPullRequest returns a pull request that is open and has not been merged
  715. // by given head/base and repo/branch.
  716. func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
  717. pr := new(PullRequest)
  718. has, err := x.
  719. Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
  720. headRepoID, headBranch, baseRepoID, baseBranch, false, false).
  721. Join("INNER", "issue", "issue.id=pull_request.issue_id").
  722. Get(pr)
  723. if err != nil {
  724. return nil, err
  725. } else if !has {
  726. return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
  727. }
  728. return pr, nil
  729. }
  730. // GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
  731. // by given head information (repo and branch).
  732. func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
  733. prs := make([]*PullRequest, 0, 2)
  734. return prs, x.
  735. Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?",
  736. repoID, branch, false, false).
  737. Join("INNER", "issue", "issue.id = pull_request.issue_id").
  738. Find(&prs)
  739. }
  740. // GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
  741. // by given base information (repo and branch).
  742. func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
  743. prs := make([]*PullRequest, 0, 2)
  744. return prs, x.
  745. Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
  746. repoID, branch, false, false).
  747. Join("INNER", "issue", "issue.id=pull_request.issue_id").
  748. Find(&prs)
  749. }
  750. // GetPullRequestByIndex returns a pull request by the given index
  751. func GetPullRequestByIndex(repoID int64, index int64) (*PullRequest, error) {
  752. pr := &PullRequest{
  753. BaseRepoID: repoID,
  754. Index: index,
  755. }
  756. has, err := x.Get(pr)
  757. if err != nil {
  758. return nil, err
  759. } else if !has {
  760. return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""}
  761. }
  762. if err = pr.LoadAttributes(); err != nil {
  763. return nil, err
  764. }
  765. if err = pr.LoadIssue(); err != nil {
  766. return nil, err
  767. }
  768. return pr, nil
  769. }
  770. func getPullRequestByID(e Engine, id int64) (*PullRequest, error) {
  771. pr := new(PullRequest)
  772. has, err := e.ID(id).Get(pr)
  773. if err != nil {
  774. return nil, err
  775. } else if !has {
  776. return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
  777. }
  778. return pr, pr.loadAttributes(e)
  779. }
  780. // GetPullRequestByID returns a pull request by given ID.
  781. func GetPullRequestByID(id int64) (*PullRequest, error) {
  782. return getPullRequestByID(x, id)
  783. }
  784. func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) {
  785. pr := &PullRequest{
  786. IssueID: issueID,
  787. }
  788. has, err := e.Get(pr)
  789. if err != nil {
  790. return nil, err
  791. } else if !has {
  792. return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
  793. }
  794. return pr, pr.loadAttributes(e)
  795. }
  796. // GetPullRequestByIssueID returns pull request by given issue ID.
  797. func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
  798. return getPullRequestByIssueID(x, issueID)
  799. }
  800. // Update updates all fields of pull request.
  801. func (pr *PullRequest) Update() error {
  802. _, err := x.ID(pr.ID).AllCols().Update(pr)
  803. return err
  804. }
  805. // UpdateCols updates specific fields of pull request.
  806. func (pr *PullRequest) UpdateCols(cols ...string) error {
  807. _, err := x.ID(pr.ID).Cols(cols...).Update(pr)
  808. return err
  809. }
  810. // UpdatePatch generates and saves a new patch.
  811. func (pr *PullRequest) UpdatePatch() (err error) {
  812. if err = pr.GetHeadRepo(); err != nil {
  813. return fmt.Errorf("GetHeadRepo: %v", err)
  814. } else if pr.HeadRepo == nil {
  815. log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
  816. return nil
  817. }
  818. if err = pr.GetBaseRepo(); err != nil {
  819. return fmt.Errorf("GetBaseRepo: %v", err)
  820. }
  821. headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  822. if err != nil {
  823. return fmt.Errorf("OpenRepository: %v", err)
  824. }
  825. // Add a temporary remote.
  826. tmpRemote := com.ToStr(time.Now().UnixNano())
  827. if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name), true); err != nil {
  828. return fmt.Errorf("AddRemote: %v", err)
  829. }
  830. defer func() {
  831. headGitRepo.RemoveRemote(tmpRemote)
  832. }()
  833. remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
  834. pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
  835. if err != nil {
  836. return fmt.Errorf("GetMergeBase: %v", err)
  837. } else if err = pr.Update(); err != nil {
  838. return fmt.Errorf("Update: %v", err)
  839. }
  840. patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch)
  841. if err != nil {
  842. return fmt.Errorf("GetPatch: %v", err)
  843. }
  844. if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil {
  845. return fmt.Errorf("BaseRepo.SavePatch: %v", err)
  846. }
  847. return nil
  848. }
  849. // PushToBaseRepo pushes commits from branches of head repository to
  850. // corresponding branches of base repository.
  851. // FIXME: Only push branches that are actually updates?
  852. func (pr *PullRequest) PushToBaseRepo() (err error) {
  853. log.Trace("PushToBaseRepo[%d]: pushing commits to base repo 'refs/pull/%d/head'", pr.BaseRepoID, pr.Index)
  854. headRepoPath := pr.HeadRepo.RepoPath()
  855. headGitRepo, err := git.OpenRepository(headRepoPath)
  856. if err != nil {
  857. return fmt.Errorf("OpenRepository: %v", err)
  858. }
  859. tmpRemoteName := fmt.Sprintf("tmp-pull-%d", pr.ID)
  860. if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), false); err != nil {
  861. return fmt.Errorf("headGitRepo.AddRemote: %v", err)
  862. }
  863. // Make sure to remove the remote even if the push fails
  864. defer headGitRepo.RemoveRemote(tmpRemoteName)
  865. headFile := fmt.Sprintf("refs/pull/%d/head", pr.Index)
  866. // Remove head in case there is a conflict.
  867. file := path.Join(pr.BaseRepo.RepoPath(), headFile)
  868. _ = os.Remove(file)
  869. if err = git.Push(headRepoPath, git.PushOptions{
  870. Remote: tmpRemoteName,
  871. Branch: fmt.Sprintf("%s:%s", pr.HeadBranch, headFile),
  872. }); err != nil {
  873. return fmt.Errorf("Push: %v", err)
  874. }
  875. return nil
  876. }
  877. // AddToTaskQueue adds itself to pull request test task queue.
  878. func (pr *PullRequest) AddToTaskQueue() {
  879. go pullRequestQueue.AddFunc(pr.ID, func() {
  880. pr.Status = PullRequestStatusChecking
  881. if err := pr.UpdateCols("status"); err != nil {
  882. log.Error(5, "AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err)
  883. }
  884. })
  885. }
  886. // PullRequestList defines a list of pull requests
  887. type PullRequestList []*PullRequest
  888. func (prs PullRequestList) loadAttributes(e Engine) error {
  889. if len(prs) == 0 {
  890. return nil
  891. }
  892. // Load issues.
  893. issueIDs := make([]int64, 0, len(prs))
  894. for i := range prs {
  895. issueIDs = append(issueIDs, prs[i].IssueID)
  896. }
  897. issues := make([]*Issue, 0, len(issueIDs))
  898. if err := e.
  899. Where("id > 0").
  900. In("id", issueIDs).
  901. Find(&issues); err != nil {
  902. return fmt.Errorf("find issues: %v", err)
  903. }
  904. set := make(map[int64]*Issue)
  905. for i := range issues {
  906. set[issues[i].ID] = issues[i]
  907. }
  908. for i := range prs {
  909. prs[i].Issue = set[prs[i].IssueID]
  910. }
  911. return nil
  912. }
  913. // LoadAttributes load all the prs attributes
  914. func (prs PullRequestList) LoadAttributes() error {
  915. return prs.loadAttributes(x)
  916. }
  917. func addHeadRepoTasks(prs []*PullRequest) {
  918. for _, pr := range prs {
  919. log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
  920. if err := pr.UpdatePatch(); err != nil {
  921. log.Error(4, "UpdatePatch: %v", err)
  922. continue
  923. } else if err := pr.PushToBaseRepo(); err != nil {
  924. log.Error(4, "PushToBaseRepo: %v", err)
  925. continue
  926. }
  927. pr.AddToTaskQueue()
  928. }
  929. }
  930. // AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
  931. // and generate new patch for testing as needed.
  932. func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool) {
  933. log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
  934. prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch)
  935. if err != nil {
  936. log.Error(4, "Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
  937. return
  938. }
  939. if isSync {
  940. if err = PullRequestList(prs).LoadAttributes(); err != nil {
  941. log.Error(4, "PullRequestList.LoadAttributes: %v", err)
  942. }
  943. if err == nil {
  944. for _, pr := range prs {
  945. pr.Issue.PullRequest = pr
  946. if err = pr.Issue.LoadAttributes(); err != nil {
  947. log.Error(4, "LoadAttributes: %v", err)
  948. continue
  949. }
  950. if err = PrepareWebhooks(pr.Issue.Repo, HookEventPullRequest, &api.PullRequestPayload{
  951. Action: api.HookIssueSynchronized,
  952. Index: pr.Issue.Index,
  953. PullRequest: pr.Issue.PullRequest.APIFormat(),
  954. Repository: pr.Issue.Repo.APIFormat(AccessModeNone),
  955. Sender: doer.APIFormat(),
  956. }); err != nil {
  957. log.Error(4, "PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
  958. continue
  959. }
  960. go HookQueue.Add(pr.Issue.Repo.ID)
  961. }
  962. }
  963. }
  964. addHeadRepoTasks(prs)
  965. log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
  966. prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch)
  967. if err != nil {
  968. log.Error(4, "Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
  969. return
  970. }
  971. for _, pr := range prs {
  972. pr.AddToTaskQueue()
  973. }
  974. }
  975. // ChangeUsernameInPullRequests changes the name of head_user_name
  976. func ChangeUsernameInPullRequests(oldUserName, newUserName string) error {
  977. pr := PullRequest{
  978. HeadUserName: strings.ToLower(newUserName),
  979. }
  980. _, err := x.
  981. Cols("head_user_name").
  982. Where("head_user_name = ?", strings.ToLower(oldUserName)).
  983. Update(pr)
  984. return err
  985. }
  986. // checkAndUpdateStatus checks if pull request is possible to leaving checking status,
  987. // and set to be either conflict or mergeable.
  988. func (pr *PullRequest) checkAndUpdateStatus() {
  989. // Status is not changed to conflict means mergeable.
  990. if pr.Status == PullRequestStatusChecking {
  991. pr.Status = PullRequestStatusMergeable
  992. }
  993. // Make sure there is no waiting test to process before leaving the checking status.
  994. if !pullRequestQueue.Exist(pr.ID) {
  995. if err := pr.UpdateCols("status"); err != nil {
  996. log.Error(4, "Update[%d]: %v", pr.ID, err)
  997. }
  998. }
  999. }
  1000. // TestPullRequests checks and tests untested patches of pull requests.
  1001. // TODO: test more pull requests at same time.
  1002. func TestPullRequests() {
  1003. prs := make([]*PullRequest, 0, 10)
  1004. err := x.Where("status = ?", PullRequestStatusChecking).Find(&prs)
  1005. if err != nil {
  1006. log.Error(3, "Find Checking PRs", err)
  1007. return
  1008. }
  1009. var checkedPRs = make(map[int64]struct{})
  1010. // Update pull request status.
  1011. for _, pr := range prs {
  1012. checkedPRs[pr.ID] = struct{}{}
  1013. if err := pr.GetBaseRepo(); err != nil {
  1014. log.Error(3, "GetBaseRepo: %v", err)
  1015. continue
  1016. }
  1017. if pr.manuallyMerged() {
  1018. continue
  1019. }
  1020. if err := pr.testPatch(); err != nil {
  1021. log.Error(3, "testPatch: %v", err)
  1022. continue
  1023. }
  1024. pr.checkAndUpdateStatus()
  1025. }
  1026. // Start listening on new test requests.
  1027. for prID := range pullRequestQueue.Queue() {
  1028. log.Trace("TestPullRequests[%v]: processing test task", prID)
  1029. pullRequestQueue.Remove(prID)
  1030. id := com.StrTo(prID).MustInt64()
  1031. if _, ok := checkedPRs[id]; ok {
  1032. continue
  1033. }
  1034. pr, err := GetPullRequestByID(id)
  1035. if err != nil {
  1036. log.Error(4, "GetPullRequestByID[%s]: %v", prID, err)
  1037. continue
  1038. } else if pr.manuallyMerged() {
  1039. continue
  1040. } else if err = pr.testPatch(); err != nil {
  1041. log.Error(4, "testPatch[%d]: %v", pr.ID, err)
  1042. continue
  1043. }
  1044. pr.checkAndUpdateStatus()
  1045. }
  1046. }
  1047. // InitTestPullRequests runs the task to test all the checking status pull requests
  1048. func InitTestPullRequests() {
  1049. go TestPullRequests()
  1050. }