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.

pull.go 46KB

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