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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359
  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. "fmt"
  11. "net/http"
  12. "path"
  13. "strings"
  14. "time"
  15. "code.gitea.io/gitea/models"
  16. "code.gitea.io/gitea/modules/base"
  17. "code.gitea.io/gitea/modules/context"
  18. auth "code.gitea.io/gitea/modules/forms"
  19. "code.gitea.io/gitea/modules/git"
  20. "code.gitea.io/gitea/modules/log"
  21. "code.gitea.io/gitea/modules/notification"
  22. repo_module "code.gitea.io/gitea/modules/repository"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/structs"
  25. "code.gitea.io/gitea/modules/upload"
  26. "code.gitea.io/gitea/modules/util"
  27. "code.gitea.io/gitea/modules/web"
  28. "code.gitea.io/gitea/modules/web/middleware"
  29. "code.gitea.io/gitea/routers/utils"
  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).(*auth.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(setting.AppSubURL + "/" + ctxUser.Name + "/" + 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(setting.AppSubURL + "/" + ctxUser.Name + "/" + 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. if pull.IsWorkInProgress() {
  455. ctx.Data["IsPullWorkInProgress"] = true
  456. ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix()
  457. }
  458. if pull.IsFilesConflicted() {
  459. ctx.Data["IsPullFilesConflicted"] = true
  460. ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
  461. }
  462. ctx.Data["NumCommits"] = compareInfo.Commits.Len()
  463. ctx.Data["NumFiles"] = compareInfo.NumFiles
  464. return compareInfo
  465. }
  466. // ViewPullCommits show commits for a pull request
  467. func ViewPullCommits(ctx *context.Context) {
  468. ctx.Data["PageIsPullList"] = true
  469. ctx.Data["PageIsPullCommits"] = true
  470. issue := checkPullInfo(ctx)
  471. if ctx.Written() {
  472. return
  473. }
  474. pull := issue.PullRequest
  475. var commits *list.List
  476. var prInfo *git.CompareInfo
  477. if pull.HasMerged {
  478. prInfo = PrepareMergedViewPullInfo(ctx, issue)
  479. } else {
  480. prInfo = PrepareViewPullInfo(ctx, issue)
  481. }
  482. if ctx.Written() {
  483. return
  484. } else if prInfo == nil {
  485. ctx.NotFound("ViewPullCommits", nil)
  486. return
  487. }
  488. ctx.Data["Username"] = ctx.Repo.Owner.Name
  489. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  490. commits = prInfo.Commits
  491. commits = models.ValidateCommitsWithEmails(commits)
  492. commits = models.ParseCommitsWithSignature(commits, ctx.Repo.Repository)
  493. commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
  494. ctx.Data["Commits"] = commits
  495. ctx.Data["CommitCount"] = commits.Len()
  496. getBranchData(ctx, issue)
  497. ctx.HTML(http.StatusOK, tplPullCommits)
  498. }
  499. // ViewPullFiles render pull request changed files list page
  500. func ViewPullFiles(ctx *context.Context) {
  501. ctx.Data["PageIsPullList"] = true
  502. ctx.Data["PageIsPullFiles"] = true
  503. issue := checkPullInfo(ctx)
  504. if ctx.Written() {
  505. return
  506. }
  507. pull := issue.PullRequest
  508. var (
  509. diffRepoPath string
  510. startCommitID string
  511. endCommitID string
  512. gitRepo *git.Repository
  513. )
  514. var prInfo *git.CompareInfo
  515. if pull.HasMerged {
  516. prInfo = PrepareMergedViewPullInfo(ctx, issue)
  517. } else {
  518. prInfo = PrepareViewPullInfo(ctx, issue)
  519. }
  520. if ctx.Written() {
  521. return
  522. } else if prInfo == nil {
  523. ctx.NotFound("ViewPullFiles", nil)
  524. return
  525. }
  526. diffRepoPath = ctx.Repo.GitRepo.Path
  527. gitRepo = ctx.Repo.GitRepo
  528. headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
  529. if err != nil {
  530. ctx.ServerError("GetRefCommitID", err)
  531. return
  532. }
  533. startCommitID = prInfo.MergeBase
  534. endCommitID = headCommitID
  535. ctx.Data["Username"] = ctx.Repo.Owner.Name
  536. ctx.Data["Reponame"] = ctx.Repo.Repository.Name
  537. ctx.Data["AfterCommitID"] = endCommitID
  538. diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(diffRepoPath,
  539. startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
  540. setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles,
  541. gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
  542. if err != nil {
  543. ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
  544. return
  545. }
  546. if err = diff.LoadComments(issue, ctx.User); err != nil {
  547. ctx.ServerError("LoadComments", err)
  548. return
  549. }
  550. if err = pull.LoadProtectedBranch(); err != nil {
  551. ctx.ServerError("LoadProtectedBranch", err)
  552. return
  553. }
  554. if pull.ProtectedBranch != nil {
  555. glob := pull.ProtectedBranch.GetProtectedFilePatterns()
  556. if len(glob) != 0 {
  557. for _, file := range diff.Files {
  558. file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name)
  559. }
  560. }
  561. }
  562. ctx.Data["Diff"] = diff
  563. ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
  564. baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID)
  565. if err != nil {
  566. ctx.ServerError("GetCommit", err)
  567. return
  568. }
  569. commit, err := gitRepo.GetCommit(endCommitID)
  570. if err != nil {
  571. ctx.ServerError("GetCommit", err)
  572. return
  573. }
  574. if ctx.IsSigned && ctx.User != nil {
  575. if ctx.Data["CanMarkConversation"], err = models.CanMarkConversation(issue, ctx.User); err != nil {
  576. ctx.ServerError("CanMarkConversation", err)
  577. return
  578. }
  579. }
  580. headTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  581. setCompareContext(ctx, baseCommit, commit, headTarget)
  582. ctx.Data["RequireHighlightJS"] = true
  583. ctx.Data["RequireSimpleMDE"] = true
  584. ctx.Data["RequireTribute"] = true
  585. if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil {
  586. ctx.ServerError("GetAssignees", err)
  587. return
  588. }
  589. handleTeamMentions(ctx)
  590. if ctx.Written() {
  591. return
  592. }
  593. ctx.Data["CurrentReview"], err = models.GetCurrentReview(ctx.User, issue)
  594. if err != nil && !models.IsErrReviewNotExist(err) {
  595. ctx.ServerError("GetCurrentReview", err)
  596. return
  597. }
  598. getBranchData(ctx, issue)
  599. ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
  600. ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
  601. ctx.HTML(http.StatusOK, tplPullFiles)
  602. }
  603. // UpdatePullRequest merge PR's baseBranch into headBranch
  604. func UpdatePullRequest(ctx *context.Context) {
  605. issue := checkPullInfo(ctx)
  606. if ctx.Written() {
  607. return
  608. }
  609. if issue.IsClosed {
  610. ctx.NotFound("MergePullRequest", nil)
  611. return
  612. }
  613. if issue.PullRequest.HasMerged {
  614. ctx.NotFound("MergePullRequest", nil)
  615. return
  616. }
  617. if err := issue.PullRequest.LoadBaseRepo(); err != nil {
  618. ctx.ServerError("LoadBaseRepo", err)
  619. return
  620. }
  621. if err := issue.PullRequest.LoadHeadRepo(); err != nil {
  622. ctx.ServerError("LoadHeadRepo", err)
  623. return
  624. }
  625. allowedUpdate, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User)
  626. if err != nil {
  627. ctx.ServerError("IsUserAllowedToMerge", err)
  628. return
  629. }
  630. // ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
  631. if !allowedUpdate {
  632. ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
  633. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  634. return
  635. }
  636. // default merge commit message
  637. message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
  638. if err = pull_service.Update(issue.PullRequest, ctx.User, message); err != nil {
  639. if models.IsErrMergeConflicts(err) {
  640. conflictError := err.(models.ErrMergeConflicts)
  641. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  642. "Message": ctx.Tr("repo.pulls.merge_conflict"),
  643. "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"),
  644. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  645. })
  646. if err != nil {
  647. ctx.ServerError("UpdatePullRequest.HTMLString", err)
  648. return
  649. }
  650. ctx.Flash.Error(flashError)
  651. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  652. return
  653. }
  654. ctx.Flash.Error(err.Error())
  655. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  656. return
  657. }
  658. time.Sleep(1 * time.Second)
  659. ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success"))
  660. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  661. }
  662. // MergePullRequest response for merging pull request
  663. func MergePullRequest(ctx *context.Context) {
  664. form := web.GetForm(ctx).(*auth.MergePullRequestForm)
  665. issue := checkPullInfo(ctx)
  666. if ctx.Written() {
  667. return
  668. }
  669. if issue.IsClosed {
  670. if issue.IsPull {
  671. ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
  672. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  673. return
  674. }
  675. ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
  676. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index))
  677. return
  678. }
  679. pr := issue.PullRequest
  680. allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.User)
  681. if err != nil {
  682. ctx.ServerError("IsUserAllowedToMerge", err)
  683. return
  684. }
  685. if !allowedMerge {
  686. ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
  687. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index))
  688. return
  689. }
  690. if pr.HasMerged {
  691. ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
  692. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  693. return
  694. }
  695. // handle manually-merged mark
  696. if models.MergeStyle(form.Do) == models.MergeStyleManuallyMerged {
  697. if err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
  698. if models.IsErrInvalidMergeStyle(err) {
  699. ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
  700. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  701. return
  702. } else if strings.Contains(err.Error(), "Wrong commit ID") {
  703. ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
  704. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  705. return
  706. }
  707. ctx.ServerError("MergedManually", err)
  708. return
  709. }
  710. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  711. return
  712. }
  713. if !pr.CanAutoMerge() {
  714. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
  715. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  716. return
  717. }
  718. if pr.IsWorkInProgress() {
  719. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
  720. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  721. return
  722. }
  723. if err := pull_service.CheckPRReadyToMerge(pr, false); err != nil {
  724. if !models.IsErrNotAllowedToMerge(err) {
  725. ctx.ServerError("Merge PR status", err)
  726. return
  727. }
  728. if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.User); err != nil {
  729. ctx.ServerError("IsUserRepoAdmin", err)
  730. return
  731. } else if !isRepoAdmin {
  732. ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
  733. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  734. return
  735. }
  736. }
  737. if ctx.HasError() {
  738. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  739. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  740. return
  741. }
  742. message := strings.TrimSpace(form.MergeTitleField)
  743. if len(message) == 0 {
  744. if models.MergeStyle(form.Do) == models.MergeStyleMerge {
  745. message = pr.GetDefaultMergeMessage()
  746. }
  747. if models.MergeStyle(form.Do) == models.MergeStyleRebaseMerge {
  748. message = pr.GetDefaultMergeMessage()
  749. }
  750. if models.MergeStyle(form.Do) == models.MergeStyleSquash {
  751. message = pr.GetDefaultSquashMessage()
  752. }
  753. }
  754. form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
  755. if len(form.MergeMessageField) > 0 {
  756. message += "\n\n" + form.MergeMessageField
  757. }
  758. pr.Issue = issue
  759. pr.Issue.Repo = ctx.Repo.Repository
  760. noDeps, err := models.IssueNoDependenciesLeft(issue)
  761. if err != nil {
  762. return
  763. }
  764. if !noDeps {
  765. ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
  766. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  767. return
  768. }
  769. if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
  770. if models.IsErrInvalidMergeStyle(err) {
  771. ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
  772. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  773. return
  774. } else if models.IsErrMergeConflicts(err) {
  775. conflictError := err.(models.ErrMergeConflicts)
  776. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  777. "Message": ctx.Tr("repo.editor.merge_conflict"),
  778. "Summary": ctx.Tr("repo.editor.merge_conflict_summary"),
  779. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  780. })
  781. if err != nil {
  782. ctx.ServerError("MergePullRequest.HTMLString", err)
  783. return
  784. }
  785. ctx.Flash.Error(flashError)
  786. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  787. return
  788. } else if models.IsErrRebaseConflicts(err) {
  789. conflictError := err.(models.ErrRebaseConflicts)
  790. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  791. "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
  792. "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
  793. "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
  794. })
  795. if err != nil {
  796. ctx.ServerError("MergePullRequest.HTMLString", err)
  797. return
  798. }
  799. ctx.Flash.Error(flashError)
  800. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  801. return
  802. } else if models.IsErrMergeUnrelatedHistories(err) {
  803. log.Debug("MergeUnrelatedHistories error: %v", err)
  804. ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
  805. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  806. return
  807. } else if git.IsErrPushOutOfDate(err) {
  808. log.Debug("MergePushOutOfDate error: %v", err)
  809. ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date"))
  810. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  811. return
  812. } else if git.IsErrPushRejected(err) {
  813. log.Debug("MergePushRejected error: %v", err)
  814. pushrejErr := err.(*git.ErrPushRejected)
  815. message := pushrejErr.Message
  816. if len(message) == 0 {
  817. ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
  818. } else {
  819. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  820. "Message": ctx.Tr("repo.pulls.push_rejected"),
  821. "Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
  822. "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
  823. })
  824. if err != nil {
  825. ctx.ServerError("MergePullRequest.HTMLString", err)
  826. return
  827. }
  828. ctx.Flash.Error(flashError)
  829. }
  830. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  831. return
  832. }
  833. ctx.ServerError("Merge", err)
  834. return
  835. }
  836. if err := stopTimerIfAvailable(ctx.User, issue); err != nil {
  837. ctx.ServerError("CreateOrStopIssueStopwatch", err)
  838. return
  839. }
  840. log.Trace("Pull request merged: %d", pr.ID)
  841. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index))
  842. }
  843. func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
  844. if models.StopwatchExists(user.ID, issue.ID) {
  845. if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
  846. return err
  847. }
  848. }
  849. return nil
  850. }
  851. // CompareAndPullRequestPost response for creating pull request
  852. func CompareAndPullRequestPost(ctx *context.Context) {
  853. form := web.GetForm(ctx).(*auth.CreateIssueForm)
  854. ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
  855. ctx.Data["PageIsComparePull"] = true
  856. ctx.Data["IsDiffCompare"] = true
  857. ctx.Data["RequireHighlightJS"] = true
  858. ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
  859. ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
  860. upload.AddUploadContext(ctx, "comment")
  861. var (
  862. repo = ctx.Repo.Repository
  863. attachments []string
  864. )
  865. headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
  866. if ctx.Written() {
  867. return
  868. }
  869. defer headGitRepo.Close()
  870. labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
  871. if ctx.Written() {
  872. return
  873. }
  874. if setting.Attachment.Enabled {
  875. attachments = form.Files
  876. }
  877. if ctx.HasError() {
  878. middleware.AssignForm(form, ctx.Data)
  879. // This stage is already stop creating new pull request, so it does not matter if it has
  880. // something to compare or not.
  881. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch,
  882. gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
  883. if ctx.Written() {
  884. return
  885. }
  886. ctx.HTML(http.StatusOK, tplCompareDiff)
  887. return
  888. }
  889. if util.IsEmptyString(form.Title) {
  890. PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch,
  891. gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
  892. if ctx.Written() {
  893. return
  894. }
  895. ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
  896. return
  897. }
  898. pullIssue := &models.Issue{
  899. RepoID: repo.ID,
  900. Title: form.Title,
  901. PosterID: ctx.User.ID,
  902. Poster: ctx.User,
  903. MilestoneID: milestoneID,
  904. IsPull: true,
  905. Content: form.Content,
  906. }
  907. pullRequest := &models.PullRequest{
  908. HeadRepoID: headRepo.ID,
  909. BaseRepoID: repo.ID,
  910. HeadBranch: headBranch,
  911. BaseBranch: baseBranch,
  912. HeadRepo: headRepo,
  913. BaseRepo: repo,
  914. MergeBase: prInfo.MergeBase,
  915. Type: models.PullRequestGitea,
  916. }
  917. // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
  918. // instead of 500.
  919. if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil {
  920. if models.IsErrUserDoesNotHaveAccessToRepo(err) {
  921. ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
  922. return
  923. } else if git.IsErrPushRejected(err) {
  924. pushrejErr := err.(*git.ErrPushRejected)
  925. message := pushrejErr.Message
  926. if len(message) == 0 {
  927. ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
  928. } else {
  929. flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{
  930. "Message": ctx.Tr("repo.pulls.push_rejected"),
  931. "Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
  932. "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
  933. })
  934. if err != nil {
  935. ctx.ServerError("CompareAndPullRequest.HTMLString", err)
  936. return
  937. }
  938. ctx.Flash.Error(flashError)
  939. }
  940. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pullIssue.Index))
  941. return
  942. }
  943. ctx.ServerError("NewPullRequest", err)
  944. return
  945. }
  946. log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
  947. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pullIssue.Index))
  948. }
  949. // TriggerTask response for a trigger task request
  950. func TriggerTask(ctx *context.Context) {
  951. pusherID := ctx.QueryInt64("pusher")
  952. branch := ctx.Query("branch")
  953. secret := ctx.Query("secret")
  954. if len(branch) == 0 || len(secret) == 0 || pusherID <= 0 {
  955. ctx.Error(http.StatusNotFound)
  956. log.Trace("TriggerTask: branch or secret is empty, or pusher ID is not valid")
  957. return
  958. }
  959. owner, repo := parseOwnerAndRepo(ctx)
  960. if ctx.Written() {
  961. return
  962. }
  963. got := []byte(base.EncodeMD5(owner.Salt))
  964. want := []byte(secret)
  965. if subtle.ConstantTimeCompare(got, want) != 1 {
  966. ctx.Error(http.StatusNotFound)
  967. log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name)
  968. return
  969. }
  970. pusher, err := models.GetUserByID(pusherID)
  971. if err != nil {
  972. if models.IsErrUserNotExist(err) {
  973. ctx.Error(http.StatusNotFound)
  974. } else {
  975. ctx.ServerError("GetUserByID", err)
  976. }
  977. return
  978. }
  979. log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
  980. go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, "", "")
  981. ctx.Status(202)
  982. }
  983. // CleanUpPullRequest responses for delete merged branch when PR has been merged
  984. func CleanUpPullRequest(ctx *context.Context) {
  985. issue := checkPullInfo(ctx)
  986. if ctx.Written() {
  987. return
  988. }
  989. pr := issue.PullRequest
  990. // Don't cleanup unmerged and unclosed PRs
  991. if !pr.HasMerged && !issue.IsClosed {
  992. ctx.NotFound("CleanUpPullRequest", nil)
  993. return
  994. }
  995. if err := pr.LoadHeadRepo(); err != nil {
  996. ctx.ServerError("LoadHeadRepo", err)
  997. return
  998. } else if pr.HeadRepo == nil {
  999. // Forked repository has already been deleted
  1000. ctx.NotFound("CleanUpPullRequest", nil)
  1001. return
  1002. } else if err = pr.LoadBaseRepo(); err != nil {
  1003. ctx.ServerError("LoadBaseRepo", err)
  1004. return
  1005. } else if err = pr.HeadRepo.GetOwner(); err != nil {
  1006. ctx.ServerError("HeadRepo.GetOwner", err)
  1007. return
  1008. }
  1009. perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.User)
  1010. if err != nil {
  1011. ctx.ServerError("GetUserRepoPermission", err)
  1012. return
  1013. }
  1014. if !perm.CanWrite(models.UnitTypeCode) {
  1015. ctx.NotFound("CleanUpPullRequest", nil)
  1016. return
  1017. }
  1018. fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch
  1019. gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  1020. if err != nil {
  1021. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
  1022. return
  1023. }
  1024. defer gitRepo.Close()
  1025. gitBaseRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  1026. if err != nil {
  1027. ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err)
  1028. return
  1029. }
  1030. defer gitBaseRepo.Close()
  1031. defer func() {
  1032. ctx.JSON(http.StatusOK, map[string]interface{}{
  1033. "redirect": pr.BaseRepo.Link() + "/pulls/" + fmt.Sprint(issue.Index),
  1034. })
  1035. }()
  1036. if pr.HeadBranch == pr.HeadRepo.DefaultBranch || !gitRepo.IsBranchExist(pr.HeadBranch) {
  1037. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1038. return
  1039. }
  1040. // Check if branch is not protected
  1041. if protected, err := pr.HeadRepo.IsProtectedBranch(pr.HeadBranch, ctx.User); err != nil || protected {
  1042. if err != nil {
  1043. log.Error("HeadRepo.IsProtectedBranch: %v", err)
  1044. }
  1045. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1046. return
  1047. }
  1048. // Check if branch has no new commits
  1049. headCommitID, err := gitBaseRepo.GetRefCommitID(pr.GetGitRefName())
  1050. if err != nil {
  1051. log.Error("GetRefCommitID: %v", err)
  1052. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1053. return
  1054. }
  1055. branchCommitID, err := gitRepo.GetBranchCommitID(pr.HeadBranch)
  1056. if err != nil {
  1057. log.Error("GetBranchCommitID: %v", err)
  1058. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1059. return
  1060. }
  1061. if headCommitID != branchCommitID {
  1062. ctx.Flash.Error(ctx.Tr("repo.branch.delete_branch_has_new_commits", fullBranchName))
  1063. return
  1064. }
  1065. if err := gitRepo.DeleteBranch(pr.HeadBranch, git.DeleteBranchOptions{
  1066. Force: true,
  1067. }); err != nil {
  1068. log.Error("DeleteBranch: %v", err)
  1069. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
  1070. return
  1071. }
  1072. if err := repo_service.PushUpdate(
  1073. &repo_module.PushUpdateOptions{
  1074. RefFullName: git.BranchPrefix + pr.HeadBranch,
  1075. OldCommitID: branchCommitID,
  1076. NewCommitID: git.EmptySHA,
  1077. PusherID: ctx.User.ID,
  1078. PusherName: ctx.User.Name,
  1079. RepoUserName: pr.HeadRepo.Owner.Name,
  1080. RepoName: pr.HeadRepo.Name,
  1081. }); err != nil {
  1082. log.Error("Update: %v", err)
  1083. }
  1084. if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil {
  1085. // Do not fail here as branch has already been deleted
  1086. log.Error("DeleteBranch: %v", err)
  1087. }
  1088. ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
  1089. }
  1090. // DownloadPullDiff render a pull's raw diff
  1091. func DownloadPullDiff(ctx *context.Context) {
  1092. DownloadPullDiffOrPatch(ctx, false)
  1093. }
  1094. // DownloadPullPatch render a pull's raw patch
  1095. func DownloadPullPatch(ctx *context.Context) {
  1096. DownloadPullDiffOrPatch(ctx, true)
  1097. }
  1098. // DownloadPullDiffOrPatch render a pull's raw diff or patch
  1099. func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) {
  1100. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  1101. if err != nil {
  1102. if models.IsErrIssueNotExist(err) {
  1103. ctx.NotFound("GetIssueByIndex", err)
  1104. } else {
  1105. ctx.ServerError("GetIssueByIndex", err)
  1106. }
  1107. return
  1108. }
  1109. // Return not found if it's not a pull request
  1110. if !issue.IsPull {
  1111. ctx.NotFound("DownloadPullDiff",
  1112. fmt.Errorf("Issue is not a pull request"))
  1113. return
  1114. }
  1115. if err = issue.LoadPullRequest(); err != nil {
  1116. ctx.ServerError("LoadPullRequest", err)
  1117. return
  1118. }
  1119. pr := issue.PullRequest
  1120. if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch); err != nil {
  1121. ctx.ServerError("DownloadDiffOrPatch", err)
  1122. return
  1123. }
  1124. }
  1125. // UpdatePullRequestTarget change pull request's target branch
  1126. func UpdatePullRequestTarget(ctx *context.Context) {
  1127. issue := GetActionIssue(ctx)
  1128. pr := issue.PullRequest
  1129. if ctx.Written() {
  1130. return
  1131. }
  1132. if !issue.IsPull {
  1133. ctx.Error(http.StatusNotFound)
  1134. return
  1135. }
  1136. if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) {
  1137. ctx.Error(http.StatusForbidden)
  1138. return
  1139. }
  1140. targetBranch := ctx.QueryTrim("target_branch")
  1141. if len(targetBranch) == 0 {
  1142. ctx.Error(http.StatusNoContent)
  1143. return
  1144. }
  1145. if err := pull_service.ChangeTargetBranch(pr, ctx.User, targetBranch); err != nil {
  1146. if models.IsErrPullRequestAlreadyExists(err) {
  1147. err := err.(models.ErrPullRequestAlreadyExists)
  1148. RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
  1149. errorMessage := ctx.Tr("repo.pulls.has_pull_request", ctx.Repo.RepoLink, RepoRelPath, err.IssueID)
  1150. ctx.Flash.Error(errorMessage)
  1151. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1152. "error": err.Error(),
  1153. "user_error": errorMessage,
  1154. })
  1155. } else if models.IsErrIssueIsClosed(err) {
  1156. errorMessage := ctx.Tr("repo.pulls.is_closed")
  1157. ctx.Flash.Error(errorMessage)
  1158. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1159. "error": err.Error(),
  1160. "user_error": errorMessage,
  1161. })
  1162. } else if models.IsErrPullRequestHasMerged(err) {
  1163. errorMessage := ctx.Tr("repo.pulls.has_merged")
  1164. ctx.Flash.Error(errorMessage)
  1165. ctx.JSON(http.StatusConflict, map[string]interface{}{
  1166. "error": err.Error(),
  1167. "user_error": errorMessage,
  1168. })
  1169. } else if models.IsErrBranchesEqual(err) {
  1170. errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
  1171. ctx.Flash.Error(errorMessage)
  1172. ctx.JSON(http.StatusBadRequest, map[string]interface{}{
  1173. "error": err.Error(),
  1174. "user_error": errorMessage,
  1175. })
  1176. } else {
  1177. ctx.ServerError("UpdatePullRequestTarget", err)
  1178. }
  1179. return
  1180. }
  1181. notification.NotifyPullRequestChangeTargetBranch(ctx.User, pr, targetBranch)
  1182. ctx.JSON(http.StatusOK, map[string]interface{}{
  1183. "base_branch": pr.BaseBranch,
  1184. })
  1185. }