Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

pull.go 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341
  1. // Copyright 2018 The Gitea Authors.
  2. // Copyright 2014 The Gogs Authors.
  3. // All rights reserved.
  4. // Use of this source code is governed by a MIT-style
  5. // license that can be found in the LICENSE file.
  6. package repo
  7. import (
  8. "container/list"
  9. "crypto/subtle"
  10. "errors"
  11. "fmt"
  12. "net/http"
  13. "path"
  14. "strings"
  15. "time"
  16. "code.gitea.io/gitea/models"
  17. "code.gitea.io/gitea/modules/base"
  18. "code.gitea.io/gitea/modules/context"
  19. "code.gitea.io/gitea/modules/git"
  20. "code.gitea.io/gitea/modules/log"
  21. "code.gitea.io/gitea/modules/notification"
  22. "code.gitea.io/gitea/modules/setting"
  23. "code.gitea.io/gitea/modules/structs"
  24. "code.gitea.io/gitea/modules/upload"
  25. "code.gitea.io/gitea/modules/util"
  26. "code.gitea.io/gitea/modules/web"
  27. "code.gitea.io/gitea/modules/web/middleware"
  28. "code.gitea.io/gitea/routers/utils"
  29. "code.gitea.io/gitea/services/forms"
  30. "code.gitea.io/gitea/services/gitdiff"
  31. pull_service "code.gitea.io/gitea/services/pull"
  32. repo_service "code.gitea.io/gitea/services/repository"
  33. "github.com/unknwon/com"
  34. )
  35. const (
  36. tplFork base.TplName = "repo/pulls/fork"
  37. tplCompareDiff base.TplName = "repo/diff/compare"
  38. tplPullCommits base.TplName = "repo/pulls/commits"
  39. tplPullFiles base.TplName = "repo/pulls/files"
  40. pullRequestTemplateKey = "PullRequestTemplate"
  41. )
  42. var (
  43. pullRequestTemplateCandidates = []string{
  44. "PULL_REQUEST_TEMPLATE.md",
  45. "pull_request_template.md",
  46. ".gitea/PULL_REQUEST_TEMPLATE.md",
  47. ".gitea/pull_request_template.md",
  48. ".github/PULL_REQUEST_TEMPLATE.md",
  49. ".github/pull_request_template.md",
  50. }
  51. )
  52. func getRepository(ctx *context.Context, repoID int64) *models.Repository {
  53. repo, err := models.GetRepositoryByID(repoID)
  54. if err != nil {
  55. if models.IsErrRepoNotExist(err) {
  56. ctx.NotFound("GetRepositoryByID", nil)
  57. } else {
  58. ctx.ServerError("GetRepositoryByID", err)
  59. }
  60. return nil
  61. }
  62. perm, err := models.GetUserRepoPermission(repo, ctx.User)
  63. if err != nil {
  64. ctx.ServerError("GetUserRepoPermission", err)
  65. return nil
  66. }
  67. if !perm.CanRead(models.UnitTypeCode) {
  68. log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+
  69. "User in repo has Permissions: %-+v",
  70. ctx.User,
  71. models.UnitTypeCode,
  72. ctx.Repo,
  73. perm)
  74. ctx.NotFound("getRepository", nil)
  75. return nil
  76. }
  77. return repo
  78. }
  79. func getForkRepository(ctx *context.Context) *models.Repository {
  80. forkRepo := getRepository(ctx, ctx.ParamsInt64(":repoid"))
  81. if ctx.Written() {
  82. return nil
  83. }
  84. if forkRepo.IsEmpty {
  85. log.Trace("Empty repository %-v", forkRepo)
  86. ctx.NotFound("getForkRepository", nil)
  87. return nil
  88. }
  89. if err := forkRepo.GetOwner(); err != nil {
  90. ctx.ServerError("GetOwner", err)
  91. return nil
  92. }
  93. ctx.Data["repo_name"] = forkRepo.Name
  94. ctx.Data["description"] = forkRepo.Description
  95. ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate
  96. canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID)
  97. ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name
  98. ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID
  99. if err := ctx.User.GetOwnedOrganizations(); err != nil {
  100. ctx.ServerError("GetOwnedOrganizations", err)
  101. return nil
  102. }
  103. var orgs []*models.User
  104. for _, org := range ctx.User.OwnedOrgs {
  105. if forkRepo.OwnerID != org.ID && !org.HasForkedRepo(forkRepo.ID) {
  106. orgs = append(orgs, org)
  107. }
  108. }
  109. var traverseParentRepo = forkRepo
  110. var err error
  111. for {
  112. if ctx.User.ID == traverseParentRepo.OwnerID {
  113. canForkToUser = false
  114. } else {
  115. for i, org := range orgs {
  116. if org.ID == traverseParentRepo.OwnerID {
  117. orgs = append(orgs[:i], orgs[i+1:]...)
  118. break
  119. }
  120. }
  121. }
  122. if !traverseParentRepo.IsFork {
  123. break
  124. }
  125. traverseParentRepo, err = models.GetRepositoryByID(traverseParentRepo.ForkID)
  126. if err != nil {
  127. ctx.ServerError("GetRepositoryByID", err)
  128. return nil
  129. }
  130. }
  131. ctx.Data["CanForkToUser"] = canForkToUser
  132. ctx.Data["Orgs"] = orgs
  133. if canForkToUser {
  134. ctx.Data["ContextUser"] = ctx.User
  135. } else if len(orgs) > 0 {
  136. ctx.Data["ContextUser"] = orgs[0]
  137. }
  138. return forkRepo
  139. }
  140. // Fork render repository fork page
  141. func Fork(ctx *context.Context) {
  142. ctx.Data["Title"] = ctx.Tr("new_fork")
  143. getForkRepository(ctx)
  144. if ctx.Written() {
  145. return
  146. }
  147. ctx.HTML(http.StatusOK, tplFork)
  148. }
  149. // ForkPost response for forking a repository
  150. func ForkPost(ctx *context.Context) {
  151. form := web.GetForm(ctx).(*forms.CreateRepoForm)
  152. ctx.Data["Title"] = ctx.Tr("new_fork")
  153. ctxUser := checkContextUser(ctx, form.UID)
  154. if ctx.Written() {
  155. return
  156. }
  157. forkRepo := getForkRepository(ctx)
  158. if ctx.Written() {
  159. return
  160. }
  161. ctx.Data["ContextUser"] = ctxUser
  162. if ctx.HasError() {
  163. ctx.HTML(http.StatusOK, tplFork)
  164. return
  165. }
  166. var err error
  167. var traverseParentRepo = forkRepo
  168. for {
  169. if ctxUser.ID == traverseParentRepo.OwnerID {
  170. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
  171. return
  172. }
  173. repo, has := models.HasForkedRepo(ctxUser.ID, traverseParentRepo.ID)
  174. if has {
  175. ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name)
  176. return
  177. }
  178. if !traverseParentRepo.IsFork {
  179. break
  180. }
  181. traverseParentRepo, err = models.GetRepositoryByID(traverseParentRepo.ForkID)
  182. if err != nil {
  183. ctx.ServerError("GetRepositoryByID", err)
  184. return
  185. }
  186. }
  187. // Check ownership of organization.
  188. if ctxUser.IsOrganization() {
  189. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  190. if err != nil {
  191. ctx.ServerError("IsOwnedBy", err)
  192. return
  193. } else if !isOwner {
  194. ctx.Error(http.StatusForbidden)
  195. return
  196. }
  197. }
  198. repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description)
  199. if err != nil {
  200. ctx.Data["Err_RepoName"] = true
  201. switch {
  202. case models.IsErrRepoAlreadyExist(err):
  203. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
  204. case models.IsErrNameReserved(err):
  205. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplFork, &form)
  206. case models.IsErrNamePatternNotAllowed(err):
  207. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
  208. default:
  209. ctx.ServerError("ForkPost", err)
  210. }
  211. return
  212. }
  213. log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
  214. ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name)
  215. }
  216. func checkPullInfo(ctx *context.Context) *models.Issue {
  217. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  218. if err != nil {
  219. if models.IsErrIssueNotExist(err) {
  220. ctx.NotFound("GetIssueByIndex", err)
  221. } else {
  222. ctx.ServerError("GetIssueByIndex", err)
  223. }
  224. return nil
  225. }
  226. if err = issue.LoadPoster(); err != nil {
  227. ctx.ServerError("LoadPoster", err)
  228. return nil
  229. }
  230. if err := issue.LoadRepo(); err != nil {
  231. ctx.ServerError("LoadRepo", err)
  232. return nil
  233. }
  234. ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
  235. ctx.Data["Issue"] = issue
  236. if !issue.IsPull {
  237. ctx.NotFound("ViewPullCommits", nil)
  238. return nil
  239. }
  240. if err = issue.LoadPullRequest(); err != nil {
  241. ctx.ServerError("LoadPullRequest", err)
  242. return nil
  243. }
  244. if err = issue.PullRequest.LoadHeadRepo(); err != nil {
  245. ctx.ServerError("LoadHeadRepo", err)
  246. return nil
  247. }
  248. if ctx.IsSigned {
  249. // Update issue-user.
  250. if err = issue.ReadBy(ctx.User.ID); err != nil {
  251. ctx.ServerError("ReadBy", err)
  252. return nil
  253. }
  254. }
  255. return issue
  256. }
  257. func setMergeTarget(ctx *context.Context, pull *models.PullRequest) {
  258. if ctx.Repo.Owner.Name == pull.MustHeadUserName() {
  259. ctx.Data["HeadTarget"] = pull.HeadBranch
  260. } else if pull.HeadRepo == nil {
  261. ctx.Data["HeadTarget"] = pull.MustHeadUserName() + ":" + pull.HeadBranch
  262. } else {
  263. ctx.Data["HeadTarget"] = pull.MustHeadUserName() + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
  264. }
  265. ctx.Data["BaseTarget"] = pull.BaseBranch
  266. ctx.Data["HeadBranchHTMLURL"] = pull.GetHeadBranchHTMLURL()
  267. ctx.Data["BaseBranchHTMLURL"] = pull.GetBaseBranchHTMLURL()
  268. }
  269. // PrepareMergedViewPullInfo show meta information for a merged pull request view page
  270. func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
  271. pull := issue.PullRequest
  272. setMergeTarget(ctx, pull)
  273. ctx.Data["HasMerged"] = true
  274. compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(),
  275. pull.MergeBase, pull.GetGitRefName())
  276. if err != nil {
  277. if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
  278. ctx.Data["IsPullRequestBroken"] = true
  279. ctx.Data["BaseTarget"] = pull.BaseBranch
  280. ctx.Data["NumCommits"] = 0
  281. ctx.Data["NumFiles"] = 0
  282. return nil
  283. }
  284. ctx.ServerError("GetCompareInfo", err)
  285. return nil
  286. }
  287. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  288. ctx.Data["NumFiles"] = compareInfo.NumFiles
  289. if compareInfo.Commits.Len() != 0 {
  290. sha := compareInfo.Commits.Front().Value.(*git.Commit).ID.String()
  291. commitStatuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, sha, models.ListOptions{})
  292. if err != nil {
  293. ctx.ServerError("GetLatestCommitStatus", err)
  294. return nil
  295. }
  296. if len(commitStatuses) != 0 {
  297. ctx.Data["LatestCommitStatuses"] = commitStatuses
  298. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
  299. }
  300. }
  301. return compareInfo
  302. }
  303. // PrepareViewPullInfo show meta information for a pull request preview page
  304. func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
  305. repo := ctx.Repo.Repository
  306. pull := issue.PullRequest
  307. if err := pull.LoadHeadRepo(); err != nil {
  308. ctx.ServerError("LoadHeadRepo", err)
  309. return nil
  310. }
  311. if err := pull.LoadBaseRepo(); err != nil {
  312. ctx.ServerError("LoadBaseRepo", err)
  313. return nil
  314. }
  315. setMergeTarget(ctx, pull)
  316. if err := pull.LoadProtectedBranch(); err != nil {
  317. ctx.ServerError("LoadProtectedBranch", err)
  318. return nil
  319. }
  320. ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
  321. baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath())
  322. if err != nil {
  323. ctx.ServerError("OpenRepository", err)
  324. return nil
  325. }
  326. defer baseGitRepo.Close()
  327. if !baseGitRepo.IsBranchExist(pull.BaseBranch) {
  328. ctx.Data["IsPullRequestBroken"] = true
  329. ctx.Data["BaseTarget"] = pull.BaseBranch
  330. ctx.Data["HeadTarget"] = pull.HeadBranch
  331. sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
  332. if err != nil {
  333. ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
  334. return nil
  335. }
  336. commitStatuses, err := models.GetLatestCommitStatus(repo.ID, sha, models.ListOptions{})
  337. if err != nil {
  338. ctx.ServerError("GetLatestCommitStatus", err)
  339. return nil
  340. }
  341. if len(commitStatuses) > 0 {
  342. ctx.Data["LatestCommitStatuses"] = commitStatuses
  343. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
  344. }
  345. compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
  346. pull.MergeBase, pull.GetGitRefName())
  347. if err != nil {
  348. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  349. ctx.Data["IsPullRequestBroken"] = true
  350. ctx.Data["BaseTarget"] = pull.BaseBranch
  351. ctx.Data["NumCommits"] = 0
  352. ctx.Data["NumFiles"] = 0
  353. return nil
  354. }
  355. ctx.ServerError("GetCompareInfo", err)
  356. return nil
  357. }
  358. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  359. ctx.Data["NumFiles"] = compareInfo.NumFiles
  360. return compareInfo
  361. }
  362. var headBranchExist bool
  363. var headBranchSha string
  364. // HeadRepo may be missing
  365. if pull.HeadRepo != nil {
  366. headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath())
  367. if err != nil {
  368. ctx.ServerError("OpenRepository", err)
  369. return nil
  370. }
  371. defer headGitRepo.Close()
  372. headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
  373. if headBranchExist {
  374. headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
  375. if err != nil {
  376. ctx.ServerError("GetBranchCommitID", err)
  377. return nil
  378. }
  379. }
  380. }
  381. if headBranchExist {
  382. ctx.Data["UpdateAllowed"], err = pull_service.IsUserAllowedToUpdate(pull, ctx.User)
  383. if err != nil {
  384. ctx.ServerError("IsUserAllowedToUpdate", err)
  385. return nil
  386. }
  387. ctx.Data["GetCommitMessages"] = pull_service.GetSquashMergeCommitMessages(pull)
  388. }
  389. sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
  390. if err != nil {
  391. if git.IsErrNotExist(err) {
  392. ctx.Data["IsPullRequestBroken"] = true
  393. if pull.IsSameRepo() {
  394. ctx.Data["HeadTarget"] = pull.HeadBranch
  395. } else if pull.HeadRepo == nil {
  396. ctx.Data["HeadTarget"] = "<deleted>:" + pull.HeadBranch
  397. } else {
  398. ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch
  399. }
  400. ctx.Data["BaseTarget"] = pull.BaseBranch
  401. ctx.Data["NumCommits"] = 0
  402. ctx.Data["NumFiles"] = 0
  403. return nil
  404. }
  405. ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
  406. return nil
  407. }
  408. commitStatuses, err := models.GetLatestCommitStatus(repo.ID, sha, models.ListOptions{})
  409. if err != nil {
  410. ctx.ServerError("GetLatestCommitStatus", err)
  411. return nil
  412. }
  413. if len(commitStatuses) > 0 {
  414. ctx.Data["LatestCommitStatuses"] = commitStatuses
  415. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
  416. }
  417. if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
  418. ctx.Data["is_context_required"] = func(context string) bool {
  419. for _, c := range pull.ProtectedBranch.StatusCheckContexts {
  420. if c == context {
  421. return true
  422. }
  423. }
  424. return false
  425. }
  426. ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
  427. }
  428. ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
  429. ctx.Data["HeadBranchCommitID"] = headBranchSha
  430. ctx.Data["PullHeadCommitID"] = sha
  431. if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha {
  432. ctx.Data["IsPullRequestBroken"] = true
  433. if pull.IsSameRepo() {
  434. ctx.Data["HeadTarget"] = pull.HeadBranch
  435. } else if pull.HeadRepo == nil {
  436. ctx.Data["HeadTarget"] = "<deleted>:" + pull.HeadBranch
  437. } else {
  438. ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch
  439. }
  440. }
  441. compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
  442. git.BranchPrefix+pull.BaseBranch, pull.GetGitRefName())
  443. if err != nil {
  444. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  445. ctx.Data["IsPullRequestBroken"] = true
  446. ctx.Data["BaseTarget"] = pull.BaseBranch
  447. ctx.Data["NumCommits"] = 0
  448. ctx.Data["NumFiles"] = 0
  449. return nil
  450. }
  451. ctx.ServerError("GetCompareInfo", err)
  452. return nil
  453. }
  454. ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
  455. if pull.IsWorkInProgress() {
  456. ctx.Data["IsPullWorkInProgress"] = true
  457. ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix()
  458. }
  459. if pull.IsFilesConflicted() {
  460. ctx.Data["IsPullFilesConflicted"] = true
  461. ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
  462. }
  463. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  464. ctx.Data["NumFiles"] = compareInfo.NumFiles
  465. return compareInfo
  466. }
  467. // ViewPullCommits show commits for a pull request
  468. func ViewPullCommits(ctx *context.Context) {
  469. ctx.Data["PageIsPullList"] = true
  470. ctx.Data["PageIsPullCommits"] = true
  471. issue := checkPullInfo(ctx)
  472. if ctx.Written() {
  473. return
  474. }
  475. pull := issue.PullRequest
  476. var commits *list.List
  477. var prInfo *git.CompareInfo
  478. if pull.HasMerged {
  479. prInfo = PrepareMergedViewPullInfo(ctx, issue)
  480. } else {
  481. prInfo = PrepareViewPullInfo(ctx, issue)
  482. }
  483. if ctx.Written() {
  484. return
  485. } else if prInfo == nil {
  486. ctx.NotFound("ViewPullCommits", nil)
  487. return
  488. }
  489. ctx.Data["Username"] = ctx.Repo.Owner.Name
  490. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  491. commits = prInfo.Commits
  492. commits = models.ValidateCommitsWithEmails(commits)
  493. commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository)
  494. commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
  495. ctx.Data["Commits"] = commits
  496. ctx.Data["CommitCount"] = commits.Len()
  497. getBranchData(ctx, issue)
  498. ctx.HTML(http.StatusOK, tplPullCommits)
  499. }
  500. // ViewPullFiles render pull request changed files list page
  501. func ViewPullFiles(ctx *context.Context) {
  502. ctx.Data["PageIsPullList"] = true
  503. ctx.Data["PageIsPullFiles"] = true
  504. issue := checkPullInfo(ctx)
  505. if ctx.Written() {
  506. return
  507. }
  508. pull := issue.PullRequest
  509. var (
  510. diffRepoPath string
  511. startCommitID string
  512. endCommitID string
  513. gitRepo *git.Repository
  514. )
  515. var prInfo *git.CompareInfo
  516. if pull.HasMerged {
  517. prInfo = PrepareMergedViewPullInfo(ctx, issue)
  518. } else {
  519. prInfo = PrepareViewPullInfo(ctx, issue)
  520. }
  521. if ctx.Written() {
  522. return
  523. } else if prInfo == nil {
  524. ctx.NotFound("ViewPullFiles", nil)
  525. return
  526. }
  527. diffRepoPath = ctx.Repo.GitRepo.Path
  528. gitRepo = ctx.Repo.GitRepo
  529. headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
  530. if err != nil {
  531. ctx.ServerError("GetRefCommitID", err)
  532. return
  533. }
  534. startCommitID = prInfo.MergeBase
  535. endCommitID = headCommitID
  536. ctx.Data["Username"] = ctx.Repo.Owner.Name
  537. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  538. ctx.Data["AfterCommitID"] = endCommitID
  539. diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(diffRepoPath,
  540. startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
  541. setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles,
  542. gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
  543. if err != nil {
  544. ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
  545. return
  546. }
  547. if err = diff.LoadComments(issue, ctx.User); err != nil {
  548. ctx.ServerError("LoadComments", err)
  549. return
  550. }
  551. if err = pull.LoadProtectedBranch(); err != nil {
  552. ctx.ServerError("LoadProtectedBranch", err)
  553. return
  554. }
  555. if pull.ProtectedBranch != nil {
  556. glob := pull.ProtectedBranch.GetProtectedFilePatterns()
  557. if len(glob) != 0 {
  558. for _, file := range diff.Files {
  559. file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name)
  560. }
  561. }
  562. }
  563. ctx.Data["Diff"] = diff
  564. ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
  565. baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID)
  566. if err != nil {
  567. ctx.ServerError("GetCommit", err)
  568. return
  569. }
  570. commit, err := gitRepo.GetCommit(endCommitID)
  571. if err != nil {
  572. ctx.ServerError("GetCommit", err)
  573. return
  574. }
  575. if ctx.IsSigned && ctx.User != nil {
  576. if ctx.Data["CanMarkConversation"], err = models.CanMarkConversation(issue, ctx.User); err != nil {
  577. ctx.ServerError("CanMarkConversation", err)
  578. return
  579. }
  580. }
  581. headTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  582. setCompareContext(ctx, baseCommit, commit, headTarget)
  583. ctx.Data["RequireHighlightJS"] = true
  584. ctx.Data["RequireSimpleMDE"] = true
  585. ctx.Data["RequireTribute"] = true
  586. if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil {
  587. ctx.ServerError("GetAssignees", err)
  588. return
  589. }
  590. handleTeamMentions(ctx)
  591. if ctx.Written() {
  592. return
  593. }
  594. ctx.Data["CurrentReview"], err = models.GetCurrentReview(ctx.User, issue)
  595. if err != nil && !models.IsErrReviewNotExist(err) {
  596. ctx.ServerError("GetCurrentReview", err)
  597. return
  598. }
  599. getBranchData(ctx, issue)
  600. ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
  601. ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
  602. ctx.HTML(http.StatusOK, tplPullFiles)
  603. }
  604. // UpdatePullRequest merge PR's baseBranch into headBranch
  605. func UpdatePullRequest(ctx *context.Context) {
  606. issue := checkPullInfo(ctx)
  607. if ctx.Written() {
  608. return
  609. }
  610. if issue.IsClosed {
  611. ctx.NotFound("MergePullRequest", nil)
  612. return
  613. }
  614. if issue.PullRequest.HasMerged {
  615. ctx.NotFound("MergePullRequest", nil)
  616. return
  617. }
  618. if err := issue.PullRequest.LoadBaseRepo(); err != nil {
  619. ctx.ServerError("LoadBaseRepo", err)
  620. return
  621. }
  622. if err := issue.PullRequest.LoadHeadRepo(); err != nil {
  623. ctx.ServerError("LoadHeadRepo", err)
  624. return
  625. }
  626. allowedUpdate, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User)
  627. if err != nil {
  628. ctx.ServerError("IsUserAllowedToMerge", err)
  629. return
  630. }
  631. // ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
  632. if !allowedUpdate {
  633. ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
  634. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  635. return
  636. }
  637. // default merge commit message
  638. message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
  639. if err = pull_service.Update(issue.PullRequest, ctx.User, message); err != nil {
  640. if models.IsErrMergeConflicts(err) {
  641. conflictError := err.(models.ErrMergeConflicts)
  642. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  643. "Message": ctx.Tr("repo.pulls.merge_conflict"),
  644. "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"),
  645. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  646. })
  647. if err != nil {
  648. ctx.ServerError("UpdatePullRequest.HTMLString", err)
  649. return
  650. }
  651. ctx.Flash.Error(flashError)
  652. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  653. return
  654. }
  655. ctx.Flash.Error(err.Error())
  656. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  657. return
  658. }
  659. time.Sleep(1 * time.Second)
  660. ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success"))
  661. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  662. }
  663. // MergePullRequest response for merging pull request
  664. func MergePullRequest(ctx *context.Context) {
  665. form := web.GetForm(ctx).(*forms.MergePullRequestForm)
  666. issue := checkPullInfo(ctx)
  667. if ctx.Written() {
  668. return
  669. }
  670. if issue.IsClosed {
  671. if issue.IsPull {
  672. ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
  673. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  674. return
  675. }
  676. ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
  677. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index))
  678. return
  679. }
  680. pr := issue.PullRequest
  681. allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.User)
  682. if err != nil {
  683. ctx.ServerError("IsUserAllowedToMerge", err)
  684. return
  685. }
  686. if !allowedMerge {
  687. ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
  688. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  689. return
  690. }
  691. if pr.HasMerged {
  692. ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
  693. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  694. return
  695. }
  696. // handle manually-merged mark
  697. if models.MergeStyle(form.Do) == models.MergeStyleManuallyMerged {
  698. if err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
  699. if models.IsErrInvalidMergeStyle(err) {
  700. ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
  701. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  702. return
  703. } else if strings.Contains(err.Error(), "Wrong commit ID") {
  704. ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
  705. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  706. return
  707. }
  708. ctx.ServerError("MergedManually", err)
  709. return
  710. }
  711. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  712. return
  713. }
  714. if !pr.CanAutoMerge() {
  715. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
  716. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  717. return
  718. }
  719. if pr.IsWorkInProgress() {
  720. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
  721. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  722. return
  723. }
  724. if err := pull_service.CheckPRReadyToMerge(pr, false); err != nil {
  725. if !models.IsErrNotAllowedToMerge(err) {
  726. ctx.ServerError("Merge PR status", err)
  727. return
  728. }
  729. if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.User); err != nil {
  730. ctx.ServerError("IsUserRepoAdmin", err)
  731. return
  732. } else if !isRepoAdmin {
  733. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
  734. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  735. return
  736. }
  737. }
  738. if ctx.HasError() {
  739. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  740. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  741. return
  742. }
  743. message := strings.TrimSpace(form.MergeTitleField)
  744. if len(message) == 0 {
  745. if models.MergeStyle(form.Do) == models.MergeStyleMerge {
  746. message = pr.GetDefaultMergeMessage()
  747. }
  748. if models.MergeStyle(form.Do) == models.MergeStyleRebaseMerge {
  749. message = pr.GetDefaultMergeMessage()
  750. }
  751. if models.MergeStyle(form.Do) == models.MergeStyleSquash {
  752. message = pr.GetDefaultSquashMessage()
  753. }
  754. }
  755. form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
  756. if len(form.MergeMessageField) > 0 {
  757. message += "\n\n" + form.MergeMessageField
  758. }
  759. pr.Issue = issue
  760. pr.Issue.Repo = ctx.Repo.Repository
  761. noDeps, err := models.IssueNoDependenciesLeft(issue)
  762. if err != nil {
  763. return
  764. }
  765. if !noDeps {
  766. ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
  767. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  768. return
  769. }
  770. if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
  771. if models.IsErrInvalidMergeStyle(err) {
  772. ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
  773. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  774. return
  775. } else if models.IsErrMergeConflicts(err) {
  776. conflictError := err.(models.ErrMergeConflicts)
  777. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  778. "Message": ctx.Tr("repo.editor.merge_conflict"),
  779. "Summary": ctx.Tr("repo.editor.merge_conflict_summary"),
  780. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  781. })
  782. if err != nil {
  783. ctx.ServerError("MergePullRequest.HTMLString", err)
  784. return
  785. }
  786. ctx.Flash.Error(flashError)
  787. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  788. return
  789. } else if models.IsErrRebaseConflicts(err) {
  790. conflictError := err.(models.ErrRebaseConflicts)
  791. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  792. "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
  793. "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
  794. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  795. })
  796. if err != nil {
  797. ctx.ServerError("MergePullRequest.HTMLString", err)
  798. return
  799. }
  800. ctx.Flash.Error(flashError)
  801. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  802. return
  803. } else if models.IsErrMergeUnrelatedHistories(err) {
  804. log.Debug("MergeUnrelatedHistories error: %v", err)
  805. ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
  806. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  807. return
  808. } else if git.IsErrPushOutOfDate(err) {
  809. log.Debug("MergePushOutOfDate error: %v", err)
  810. ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date"))
  811. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  812. return
  813. } else if git.IsErrPushRejected(err) {
  814. log.Debug("MergePushRejected error: %v", err)
  815. pushrejErr := err.(*git.ErrPushRejected)
  816. message := pushrejErr.Message
  817. if len(message) == 0 {
  818. ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
  819. } else {
  820. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  821. "Message": ctx.Tr("repo.pulls.push_rejected"),
  822. "Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
  823. "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
  824. })
  825. if err != nil {
  826. ctx.ServerError("MergePullRequest.HTMLString", err)
  827. return
  828. }
  829. ctx.Flash.Error(flashError)
  830. }
  831. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  832. return
  833. }
  834. ctx.ServerError("Merge", err)
  835. return
  836. }
  837. if err := stopTimerIfAvailable(ctx.User, issue); err != nil {
  838. ctx.ServerError("CreateOrStopIssueStopwatch", err)
  839. return
  840. }
  841. log.Trace("Pull request merged: %d", pr.ID)
  842. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  843. }
  844. func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
  845. if models.StopwatchExists(user.ID, issue.ID) {
  846. if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
  847. return err
  848. }
  849. }
  850. return nil
  851. }
  852. // CompareAndPullRequestPost response for creating pull request
  853. func CompareAndPullRequestPost(ctx *context.Context) {
  854. form := web.GetForm(ctx).(*forms.CreateIssueForm)
  855. ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
  856. ctx.Data["PageIsComparePull"] = true
  857. ctx.Data["IsDiffCompare"] = true
  858. ctx.Data["RequireHighlightJS"] = true
  859. ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
  860. ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
  861. upload.AddUploadContext(ctx, "comment")
  862. var (
  863. repo = ctx.Repo.Repository
  864. attachments []string
  865. )
  866. headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
  867. if ctx.Written() {
  868. return
  869. }
  870. defer headGitRepo.Close()
  871. labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
  872. if ctx.Written() {
  873. return
  874. }
  875. if setting.Attachment.Enabled {
  876. attachments = form.Files
  877. }
  878. if ctx.HasError() {
  879. middleware.AssignForm(form, ctx.Data)
  880. // This stage is already stop creating new pull request, so it does not matter if it has
  881. // something to compare or not.
  882. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch,
  883. gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
  884. if ctx.Written() {
  885. return
  886. }
  887. ctx.HTML(http.StatusOK, tplCompareDiff)
  888. return
  889. }
  890. if util.IsEmptyString(form.Title) {
  891. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch,
  892. gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
  893. if ctx.Written() {
  894. return
  895. }
  896. ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
  897. return
  898. }
  899. pullIssue := &models.Issue{
  900. RepoID: repo.ID,
  901. Title: form.Title,
  902. PosterID: ctx.User.ID,
  903. Poster: ctx.User,
  904. MilestoneID: milestoneID,
  905. IsPull: true,
  906. Content: form.Content,
  907. }
  908. pullRequest := &models.PullRequest{
  909. HeadRepoID: headRepo.ID,
  910. BaseRepoID: repo.ID,
  911. HeadBranch: headBranch,
  912. BaseBranch: baseBranch,
  913. HeadRepo: headRepo,
  914. BaseRepo: repo,
  915. MergeBase: prInfo.MergeBase,
  916. Type: models.PullRequestGitea,
  917. }
  918. // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
  919. // instead of 500.
  920. if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil {
  921. if models.IsErrUserDoesNotHaveAccessToRepo(err) {
  922. ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
  923. return
  924. } else if git.IsErrPushRejected(err) {
  925. pushrejErr := err.(*git.ErrPushRejected)
  926. message := pushrejErr.Message
  927. if len(message) == 0 {
  928. ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
  929. } else {
  930. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  931. "Message": ctx.Tr("repo.pulls.push_rejected"),
  932. "Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
  933. "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
  934. })
  935. if err != nil {
  936. ctx.ServerError("CompareAndPullRequest.HTMLString", err)
  937. return
  938. }
  939. ctx.Flash.Error(flashError)
  940. }
  941. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pullIssue.Index))
  942. return
  943. }
  944. ctx.ServerError("NewPullRequest", err)
  945. return
  946. }
  947. log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
  948. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pullIssue.Index))
  949. }
  950. // TriggerTask response for a trigger task request
  951. func TriggerTask(ctx *context.Context) {
  952. pusherID := ctx.QueryInt64("pusher")
  953. branch := ctx.Query("branch")
  954. secret := ctx.Query("secret")
  955. if len(branch) == 0 || len(secret) == 0 || pusherID <= 0 {
  956. ctx.Error(http.StatusNotFound)
  957. log.Trace("TriggerTask: branch or secret is empty, or pusher ID is not valid")
  958. return
  959. }
  960. owner, repo := parseOwnerAndRepo(ctx)
  961. if ctx.Written() {
  962. return
  963. }
  964. got := []byte(base.EncodeMD5(owner.Salt))
  965. want := []byte(secret)
  966. if subtle.ConstantTimeCompare(got, want) != 1 {
  967. ctx.Error(http.StatusNotFound)
  968. log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name)
  969. return
  970. }
  971. pusher, err := models.GetUserByID(pusherID)
  972. if err != nil {
  973. if models.IsErrUserNotExist(err) {
  974. ctx.Error(http.StatusNotFound)
  975. } else {
  976. ctx.ServerError("GetUserByID", err)
  977. }
  978. return
  979. }
  980. log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
  981. go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, "", "")
  982. ctx.Status(202)
  983. }
  984. // CleanUpPullRequest responses for delete merged branch when PR has been merged
  985. func CleanUpPullRequest(ctx *context.Context) {
  986. issue := checkPullInfo(ctx)
  987. if ctx.Written() {
  988. return
  989. }
  990. pr := issue.PullRequest
  991. // Don't cleanup unmerged and unclosed PRs
  992. if !pr.HasMerged && !issue.IsClosed {
  993. ctx.NotFound("CleanUpPullRequest", nil)
  994. return
  995. }
  996. if err := pr.LoadHeadRepo(); err != nil {
  997. ctx.ServerError("LoadHeadRepo", err)
  998. return
  999. } else if pr.HeadRepo == nil {
  1000. // Forked repository has already been deleted
  1001. ctx.NotFound("CleanUpPullRequest", nil)
  1002. return
  1003. } else if err = pr.LoadBaseRepo(); err != nil {
  1004. ctx.ServerError("LoadBaseRepo", err)
  1005. return
  1006. } else if err = pr.HeadRepo.GetOwner(); err != nil {
  1007. ctx.ServerError("HeadRepo.GetOwner", err)
  1008. return
  1009. }
  1010. perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.User)
  1011. if err != nil {
  1012. ctx.ServerError("GetUserRepoPermission", err)
  1013. return
  1014. }
  1015. if !perm.CanWrite(models.UnitTypeCode) {
  1016. ctx.NotFound("CleanUpPullRequest", nil)
  1017. return
  1018. }
  1019. fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch
  1020. gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  1021. if err != nil {
  1022. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
  1023. return
  1024. }
  1025. defer gitRepo.Close()
  1026. gitBaseRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  1027. if err != nil {
  1028. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err)
  1029. return
  1030. }
  1031. defer gitBaseRepo.Close()
  1032. defer func() {
  1033. ctx.JSON(http.StatusOK, map[string]interface{}{
  1034. "redirect": pr.BaseRepo.Link() + "/pulls/" + fmt.Sprint(issue.Index),
  1035. })
  1036. }()
  1037. // Check if branch has no new commits
  1038. headCommitID, err := gitBaseRepo.GetRefCommitID(pr.GetGitRefName())
  1039. if err != nil {
  1040. log.Error("GetRefCommitID: %v", err)
  1041. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1042. return
  1043. }
  1044. branchCommitID, err := gitRepo.GetBranchCommitID(pr.HeadBranch)
  1045. if err != nil {
  1046. log.Error("GetBranchCommitID: %v", err)
  1047. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1048. return
  1049. }
  1050. if headCommitID != branchCommitID {
  1051. ctx.Flash.Error(ctx.Tr("repo.branch.delete_branch_has_new_commits", fullBranchName))
  1052. return
  1053. }
  1054. if err := repo_service.DeleteBranch(ctx.User, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil {
  1055. switch {
  1056. case git.IsErrBranchNotExist(err):
  1057. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1058. case errors.Is(err, repo_service.ErrBranchIsDefault):
  1059. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1060. case errors.Is(err, repo_service.ErrBranchIsProtected):
  1061. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1062. default:
  1063. log.Error("DeleteBranch: %v", err)
  1064. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1065. }
  1066. return
  1067. }
  1068. if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil {
  1069. // Do not fail here as branch has already been deleted
  1070. log.Error("DeleteBranch: %v", err)
  1071. }
  1072. ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
  1073. }
  1074. // DownloadPullDiff render a pull's raw diff
  1075. func DownloadPullDiff(ctx *context.Context) {
  1076. DownloadPullDiffOrPatch(ctx, false)
  1077. }
  1078. // DownloadPullPatch render a pull's raw patch
  1079. func DownloadPullPatch(ctx *context.Context) {
  1080. DownloadPullDiffOrPatch(ctx, true)
  1081. }
  1082. // DownloadPullDiffOrPatch render a pull's raw diff or patch
  1083. func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) {
  1084. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  1085. if err != nil {
  1086. if models.IsErrIssueNotExist(err) {
  1087. ctx.NotFound("GetIssueByIndex", err)
  1088. } else {
  1089. ctx.ServerError("GetIssueByIndex", err)
  1090. }
  1091. return
  1092. }
  1093. // Return not found if it's not a pull request
  1094. if !issue.IsPull {
  1095. ctx.NotFound("DownloadPullDiff",
  1096. fmt.Errorf("Issue is not a pull request"))
  1097. return
  1098. }
  1099. if err = issue.LoadPullRequest(); err != nil {
  1100. ctx.ServerError("LoadPullRequest", err)
  1101. return
  1102. }
  1103. pr := issue.PullRequest
  1104. if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch); err != nil {
  1105. ctx.ServerError("DownloadDiffOrPatch", err)
  1106. return
  1107. }
  1108. }
  1109. // UpdatePullRequestTarget change pull request's target branch
  1110. func UpdatePullRequestTarget(ctx *context.Context) {
  1111. issue := GetActionIssue(ctx)
  1112. pr := issue.PullRequest
  1113. if ctx.Written() {
  1114. return
  1115. }
  1116. if !issue.IsPull {
  1117. ctx.Error(http.StatusNotFound)
  1118. return
  1119. }
  1120. if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) {
  1121. ctx.Error(http.StatusForbidden)
  1122. return
  1123. }
  1124. targetBranch := ctx.QueryTrim("target_branch")
  1125. if len(targetBranch) == 0 {
  1126. ctx.Error(http.StatusNoContent)
  1127. return
  1128. }
  1129. if err := pull_service.ChangeTargetBranch(pr, ctx.User, targetBranch); err != nil {
  1130. if models.IsErrPullRequestAlreadyExists(err) {
  1131. err := err.(models.ErrPullRequestAlreadyExists)
  1132. RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
  1133. errorMessage := ctx.Tr("repo.pulls.has_pull_request", ctx.Repo.RepoLink, RepoRelPath, err.IssueID)
  1134. ctx.Flash.Error(errorMessage)
  1135. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1136. "error": err.Error(),
  1137. "user_error": errorMessage,
  1138. })
  1139. } else if models.IsErrIssueIsClosed(err) {
  1140. errorMessage := ctx.Tr("repo.pulls.is_closed")
  1141. ctx.Flash.Error(errorMessage)
  1142. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1143. "error": err.Error(),
  1144. "user_error": errorMessage,
  1145. })
  1146. } else if models.IsErrPullRequestHasMerged(err) {
  1147. errorMessage := ctx.Tr("repo.pulls.has_merged")
  1148. ctx.Flash.Error(errorMessage)
  1149. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1150. "error": err.Error(),
  1151. "user_error": errorMessage,
  1152. })
  1153. } else if models.IsErrBranchesEqual(err) {
  1154. errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
  1155. ctx.Flash.Error(errorMessage)
  1156. ctx.JSON(http.StatusBadRequest, map[string]interface{}{
  1157. "error": err.Error(),
  1158. "user_error": errorMessage,
  1159. })
  1160. } else {
  1161. ctx.ServerError("UpdatePullRequestTarget", err)
  1162. }
  1163. return
  1164. }
  1165. notification.NotifyPullRequestChangeTargetBranch(ctx.User, pr, targetBranch)
  1166. ctx.JSON(http.StatusOK, map[string]interface{}{
  1167. "base_branch": pr.BaseBranch,
  1168. })
  1169. }