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 7package repo 8 9import ( 10 "errors" 11 "fmt" 12 "html" 13 "net/http" 14 "net/url" 15 "strconv" 16 "strings" 17 "time" 18 19 "code.gitea.io/gitea/models" 20 "code.gitea.io/gitea/models/db" 21 repo_model "code.gitea.io/gitea/models/repo" 22 "code.gitea.io/gitea/models/unit" 23 user_model "code.gitea.io/gitea/models/user" 24 "code.gitea.io/gitea/modules/base" 25 "code.gitea.io/gitea/modules/context" 26 "code.gitea.io/gitea/modules/git" 27 "code.gitea.io/gitea/modules/log" 28 "code.gitea.io/gitea/modules/notification" 29 "code.gitea.io/gitea/modules/setting" 30 "code.gitea.io/gitea/modules/structs" 31 "code.gitea.io/gitea/modules/upload" 32 "code.gitea.io/gitea/modules/util" 33 "code.gitea.io/gitea/modules/web" 34 "code.gitea.io/gitea/modules/web/middleware" 35 "code.gitea.io/gitea/routers/utils" 36 "code.gitea.io/gitea/services/forms" 37 "code.gitea.io/gitea/services/gitdiff" 38 pull_service "code.gitea.io/gitea/services/pull" 39 repo_service "code.gitea.io/gitea/services/repository" 40) 41 42const ( 43 tplFork base.TplName = "repo/pulls/fork" 44 tplCompareDiff base.TplName = "repo/diff/compare" 45 tplPullCommits base.TplName = "repo/pulls/commits" 46 tplPullFiles base.TplName = "repo/pulls/files" 47 48 pullRequestTemplateKey = "PullRequestTemplate" 49) 50 51var ( 52 pullRequestTemplateCandidates = []string{ 53 "PULL_REQUEST_TEMPLATE.md", 54 "pull_request_template.md", 55 ".gitea/PULL_REQUEST_TEMPLATE.md", 56 ".gitea/pull_request_template.md", 57 ".github/PULL_REQUEST_TEMPLATE.md", 58 ".github/pull_request_template.md", 59 } 60) 61 62func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository { 63 repo, err := repo_model.GetRepositoryByID(repoID) 64 if err != nil { 65 if repo_model.IsErrRepoNotExist(err) { 66 ctx.NotFound("GetRepositoryByID", nil) 67 } else { 68 ctx.ServerError("GetRepositoryByID", err) 69 } 70 return nil 71 } 72 73 perm, err := models.GetUserRepoPermission(repo, ctx.User) 74 if err != nil { 75 ctx.ServerError("GetUserRepoPermission", err) 76 return nil 77 } 78 79 if !perm.CanRead(unit.TypeCode) { 80 log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+ 81 "User in repo has Permissions: %-+v", 82 ctx.User, 83 unit.TypeCode, 84 ctx.Repo, 85 perm) 86 ctx.NotFound("getRepository", nil) 87 return nil 88 } 89 return repo 90} 91 92func getForkRepository(ctx *context.Context) *repo_model.Repository { 93 forkRepo := getRepository(ctx, ctx.ParamsInt64(":repoid")) 94 if ctx.Written() { 95 return nil 96 } 97 98 if forkRepo.IsEmpty { 99 log.Trace("Empty repository %-v", forkRepo) 100 ctx.NotFound("getForkRepository", nil) 101 return nil 102 } 103 104 if err := forkRepo.GetOwner(db.DefaultContext); err != nil { 105 ctx.ServerError("GetOwner", err) 106 return nil 107 } 108 109 ctx.Data["repo_name"] = forkRepo.Name 110 ctx.Data["description"] = forkRepo.Description 111 ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate 112 canForkToUser := forkRepo.OwnerID != ctx.User.ID && !repo_model.HasForkedRepo(ctx.User.ID, forkRepo.ID) 113 114 ctx.Data["ForkRepo"] = forkRepo 115 116 ownedOrgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.User.ID) 117 if err != nil { 118 ctx.ServerError("GetOrgsCanCreateRepoByUserID", err) 119 return nil 120 } 121 var orgs []*models.Organization 122 for _, org := range ownedOrgs { 123 if forkRepo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, forkRepo.ID) { 124 orgs = append(orgs, org) 125 } 126 } 127 128 var traverseParentRepo = forkRepo 129 for { 130 if ctx.User.ID == traverseParentRepo.OwnerID { 131 canForkToUser = false 132 } else { 133 for i, org := range orgs { 134 if org.ID == traverseParentRepo.OwnerID { 135 orgs = append(orgs[:i], orgs[i+1:]...) 136 break 137 } 138 } 139 } 140 141 if !traverseParentRepo.IsFork { 142 break 143 } 144 traverseParentRepo, err = repo_model.GetRepositoryByID(traverseParentRepo.ForkID) 145 if err != nil { 146 ctx.ServerError("GetRepositoryByID", err) 147 return nil 148 } 149 } 150 151 ctx.Data["CanForkToUser"] = canForkToUser 152 ctx.Data["Orgs"] = orgs 153 154 if canForkToUser { 155 ctx.Data["ContextUser"] = ctx.User 156 } else if len(orgs) > 0 { 157 ctx.Data["ContextUser"] = orgs[0] 158 } 159 160 return forkRepo 161} 162 163// Fork render repository fork page 164func Fork(ctx *context.Context) { 165 ctx.Data["Title"] = ctx.Tr("new_fork") 166 167 getForkRepository(ctx) 168 if ctx.Written() { 169 return 170 } 171 172 ctx.HTML(http.StatusOK, tplFork) 173} 174 175// ForkPost response for forking a repository 176func ForkPost(ctx *context.Context) { 177 form := web.GetForm(ctx).(*forms.CreateRepoForm) 178 ctx.Data["Title"] = ctx.Tr("new_fork") 179 180 ctxUser := checkContextUser(ctx, form.UID) 181 if ctx.Written() { 182 return 183 } 184 185 forkRepo := getForkRepository(ctx) 186 if ctx.Written() { 187 return 188 } 189 190 ctx.Data["ContextUser"] = ctxUser 191 192 if ctx.HasError() { 193 ctx.HTML(http.StatusOK, tplFork) 194 return 195 } 196 197 var err error 198 var traverseParentRepo = forkRepo 199 for { 200 if ctxUser.ID == traverseParentRepo.OwnerID { 201 ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form) 202 return 203 } 204 repo := repo_model.GetForkedRepo(ctxUser.ID, traverseParentRepo.ID) 205 if repo != nil { 206 ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name)) 207 return 208 } 209 if !traverseParentRepo.IsFork { 210 break 211 } 212 traverseParentRepo, err = repo_model.GetRepositoryByID(traverseParentRepo.ForkID) 213 if err != nil { 214 ctx.ServerError("GetRepositoryByID", err) 215 return 216 } 217 } 218 219 // Check if user is allowed to create repo's on the organization. 220 if ctxUser.IsOrganization() { 221 isAllowedToFork, err := models.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx.User.ID) 222 if err != nil { 223 ctx.ServerError("CanCreateOrgRepo", err) 224 return 225 } else if !isAllowedToFork { 226 ctx.Error(http.StatusForbidden) 227 return 228 } 229 } 230 231 repo, err := repo_service.ForkRepository(ctx.User, ctxUser, repo_service.ForkRepoOptions{ 232 BaseRepo: forkRepo, 233 Name: form.RepoName, 234 Description: form.Description, 235 }) 236 if err != nil { 237 ctx.Data["Err_RepoName"] = true 238 switch { 239 case repo_model.IsErrRepoAlreadyExist(err): 240 ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form) 241 case db.IsErrNameReserved(err): 242 ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplFork, &form) 243 case db.IsErrNamePatternNotAllowed(err): 244 ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplFork, &form) 245 default: 246 ctx.ServerError("ForkPost", err) 247 } 248 return 249 } 250 251 log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name) 252 ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name)) 253} 254 255func checkPullInfo(ctx *context.Context) *models.Issue { 256 issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) 257 if err != nil { 258 if models.IsErrIssueNotExist(err) { 259 ctx.NotFound("GetIssueByIndex", err) 260 } else { 261 ctx.ServerError("GetIssueByIndex", err) 262 } 263 return nil 264 } 265 if err = issue.LoadPoster(); err != nil { 266 ctx.ServerError("LoadPoster", err) 267 return nil 268 } 269 if err := issue.LoadRepo(); err != nil { 270 ctx.ServerError("LoadRepo", err) 271 return nil 272 } 273 ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) 274 ctx.Data["Issue"] = issue 275 276 if !issue.IsPull { 277 ctx.NotFound("ViewPullCommits", nil) 278 return nil 279 } 280 281 if err = issue.LoadPullRequest(); err != nil { 282 ctx.ServerError("LoadPullRequest", err) 283 return nil 284 } 285 286 if err = issue.PullRequest.LoadHeadRepo(); err != nil { 287 ctx.ServerError("LoadHeadRepo", err) 288 return nil 289 } 290 291 if ctx.IsSigned { 292 // Update issue-user. 293 if err = issue.ReadBy(ctx.User.ID); err != nil { 294 ctx.ServerError("ReadBy", err) 295 return nil 296 } 297 } 298 299 return issue 300} 301 302func setMergeTarget(ctx *context.Context, pull *models.PullRequest) { 303 if ctx.Repo.Owner.Name == pull.MustHeadUserName() { 304 ctx.Data["HeadTarget"] = pull.HeadBranch 305 } else if pull.HeadRepo == nil { 306 ctx.Data["HeadTarget"] = pull.MustHeadUserName() + ":" + pull.HeadBranch 307 } else { 308 ctx.Data["HeadTarget"] = pull.MustHeadUserName() + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch 309 } 310 ctx.Data["BaseTarget"] = pull.BaseBranch 311 ctx.Data["HeadBranchHTMLURL"] = pull.GetHeadBranchHTMLURL() 312 ctx.Data["BaseBranchHTMLURL"] = pull.GetBaseBranchHTMLURL() 313} 314 315// PrepareMergedViewPullInfo show meta information for a merged pull request view page 316func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo { 317 pull := issue.PullRequest 318 319 setMergeTarget(ctx, pull) 320 ctx.Data["HasMerged"] = true 321 322 var baseCommit string 323 // Some migrated PR won't have any Base SHA and lose history, try to get one 324 if pull.MergeBase == "" { 325 var commitSHA, parentCommit string 326 // If there is a head or a patch file, and it is readable, grab info 327 commitSHA, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitRefName()) 328 if err != nil { 329 // Head File does not exist, try the patch 330 commitSHA, err = ctx.Repo.GitRepo.ReadPatchCommit(pull.Index) 331 if err == nil { 332 // Recreate pull head in files for next time 333 if err := ctx.Repo.GitRepo.SetReference(pull.GetGitRefName(), commitSHA); err != nil { 334 log.Error("Could not write head file", err) 335 } 336 } else { 337 // There is no history available 338 log.Trace("No history file available for PR %d", pull.Index) 339 } 340 } 341 if commitSHA != "" { 342 // Get immediate parent of the first commit in the patch, grab history back 343 parentCommit, err = git.NewCommandContext(ctx, "rev-list", "-1", "--skip=1", commitSHA).RunInDir(ctx.Repo.GitRepo.Path) 344 if err == nil { 345 parentCommit = strings.TrimSpace(parentCommit) 346 } 347 // Special case on Git < 2.25 that doesn't fail on immediate empty history 348 if err != nil || parentCommit == "" { 349 log.Info("No known parent commit for PR %d, error: %v", pull.Index, err) 350 // bring at least partial history if it can work 351 parentCommit = commitSHA 352 } 353 } 354 baseCommit = parentCommit 355 } else { 356 // Keep an empty history or original commit 357 baseCommit = pull.MergeBase 358 } 359 360 compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(), 361 baseCommit, pull.GetGitRefName(), false, false) 362 if err != nil { 363 if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") { 364 ctx.Data["IsPullRequestBroken"] = true 365 ctx.Data["BaseTarget"] = pull.BaseBranch 366 ctx.Data["NumCommits"] = 0 367 ctx.Data["NumFiles"] = 0 368 return nil 369 } 370 371 ctx.ServerError("GetCompareInfo", err) 372 return nil 373 } 374 ctx.Data["NumCommits"] = len(compareInfo.Commits) 375 ctx.Data["NumFiles"] = compareInfo.NumFiles 376 377 if len(compareInfo.Commits) != 0 { 378 sha := compareInfo.Commits[0].ID.String() 379 commitStatuses, _, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, sha, db.ListOptions{}) 380 if err != nil { 381 ctx.ServerError("GetLatestCommitStatus", err) 382 return nil 383 } 384 if len(commitStatuses) != 0 { 385 ctx.Data["LatestCommitStatuses"] = commitStatuses 386 ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) 387 } 388 } 389 390 return compareInfo 391} 392 393// PrepareViewPullInfo show meta information for a pull request preview page 394func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo { 395 ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes 396 397 repo := ctx.Repo.Repository 398 pull := issue.PullRequest 399 400 if err := pull.LoadHeadRepo(); err != nil { 401 ctx.ServerError("LoadHeadRepo", err) 402 return nil 403 } 404 405 if err := pull.LoadBaseRepo(); err != nil { 406 ctx.ServerError("LoadBaseRepo", err) 407 return nil 408 } 409 410 setMergeTarget(ctx, pull) 411 412 if err := pull.LoadProtectedBranch(); err != nil { 413 ctx.ServerError("LoadProtectedBranch", err) 414 return nil 415 } 416 ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck 417 418 baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath()) 419 if err != nil { 420 ctx.ServerError("OpenRepository", err) 421 return nil 422 } 423 defer baseGitRepo.Close() 424 425 if !baseGitRepo.IsBranchExist(pull.BaseBranch) { 426 ctx.Data["IsPullRequestBroken"] = true 427 ctx.Data["BaseTarget"] = pull.BaseBranch 428 ctx.Data["HeadTarget"] = pull.HeadBranch 429 430 sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName()) 431 if err != nil { 432 ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err) 433 return nil 434 } 435 commitStatuses, _, err := models.GetLatestCommitStatus(repo.ID, sha, db.ListOptions{}) 436 if err != nil { 437 ctx.ServerError("GetLatestCommitStatus", err) 438 return nil 439 } 440 if len(commitStatuses) > 0 { 441 ctx.Data["LatestCommitStatuses"] = commitStatuses 442 ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) 443 } 444 445 compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), 446 pull.MergeBase, pull.GetGitRefName(), false, false) 447 if err != nil { 448 if strings.Contains(err.Error(), "fatal: Not a valid object name") { 449 ctx.Data["IsPullRequestBroken"] = true 450 ctx.Data["BaseTarget"] = pull.BaseBranch 451 ctx.Data["NumCommits"] = 0 452 ctx.Data["NumFiles"] = 0 453 return nil 454 } 455 456 ctx.ServerError("GetCompareInfo", err) 457 return nil 458 } 459 460 ctx.Data["NumCommits"] = len(compareInfo.Commits) 461 ctx.Data["NumFiles"] = compareInfo.NumFiles 462 return compareInfo 463 } 464 465 var headBranchExist bool 466 var headBranchSha string 467 // HeadRepo may be missing 468 if pull.HeadRepo != nil { 469 headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath()) 470 if err != nil { 471 ctx.ServerError("OpenRepository", err) 472 return nil 473 } 474 defer headGitRepo.Close() 475 476 if pull.Flow == models.PullRequestFlowGithub { 477 headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch) 478 } else { 479 headBranchExist = git.IsReferenceExist(ctx, baseGitRepo.Path, pull.GetGitRefName()) 480 } 481 482 if headBranchExist { 483 if pull.Flow != models.PullRequestFlowGithub { 484 headBranchSha, err = baseGitRepo.GetRefCommitID(pull.GetGitRefName()) 485 } else { 486 headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch) 487 } 488 if err != nil { 489 ctx.ServerError("GetBranchCommitID", err) 490 return nil 491 } 492 } 493 } 494 495 if headBranchExist { 496 ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(pull, ctx.User) 497 if err != nil { 498 ctx.ServerError("IsUserAllowedToUpdate", err) 499 return nil 500 } 501 ctx.Data["GetCommitMessages"] = pull_service.GetSquashMergeCommitMessages(pull) 502 } 503 504 sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName()) 505 if err != nil { 506 if git.IsErrNotExist(err) { 507 ctx.Data["IsPullRequestBroken"] = true 508 if pull.IsSameRepo() { 509 ctx.Data["HeadTarget"] = pull.HeadBranch 510 } else if pull.HeadRepo == nil { 511 ctx.Data["HeadTarget"] = "<deleted>:" + pull.HeadBranch 512 } else { 513 ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch 514 } 515 ctx.Data["BaseTarget"] = pull.BaseBranch 516 ctx.Data["NumCommits"] = 0 517 ctx.Data["NumFiles"] = 0 518 return nil 519 } 520 ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err) 521 return nil 522 } 523 524 commitStatuses, _, err := models.GetLatestCommitStatus(repo.ID, sha, db.ListOptions{}) 525 if err != nil { 526 ctx.ServerError("GetLatestCommitStatus", err) 527 return nil 528 } 529 if len(commitStatuses) > 0 { 530 ctx.Data["LatestCommitStatuses"] = commitStatuses 531 ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) 532 } 533 534 if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck { 535 ctx.Data["is_context_required"] = func(context string) bool { 536 for _, c := range pull.ProtectedBranch.StatusCheckContexts { 537 if c == context { 538 return true 539 } 540 } 541 return false 542 } 543 ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts) 544 } 545 546 ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha 547 ctx.Data["HeadBranchCommitID"] = headBranchSha 548 ctx.Data["PullHeadCommitID"] = sha 549 550 if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha { 551 ctx.Data["IsPullRequestBroken"] = true 552 if pull.IsSameRepo() { 553 ctx.Data["HeadTarget"] = pull.HeadBranch 554 } else if pull.HeadRepo == nil { 555 ctx.Data["HeadTarget"] = "<deleted>:" + pull.HeadBranch 556 } else { 557 ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch 558 } 559 } 560 561 compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), 562 git.BranchPrefix+pull.BaseBranch, pull.GetGitRefName(), false, false) 563 if err != nil { 564 if strings.Contains(err.Error(), "fatal: Not a valid object name") { 565 ctx.Data["IsPullRequestBroken"] = true 566 ctx.Data["BaseTarget"] = pull.BaseBranch 567 ctx.Data["NumCommits"] = 0 568 ctx.Data["NumFiles"] = 0 569 return nil 570 } 571 572 ctx.ServerError("GetCompareInfo", err) 573 return nil 574 } 575 576 if compareInfo.HeadCommitID == compareInfo.MergeBase { 577 ctx.Data["IsNothingToCompare"] = true 578 } 579 580 if pull.IsWorkInProgress() { 581 ctx.Data["IsPullWorkInProgress"] = true 582 ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix() 583 } 584 585 if pull.IsFilesConflicted() { 586 ctx.Data["IsPullFilesConflicted"] = true 587 ctx.Data["ConflictedFiles"] = pull.ConflictedFiles 588 } 589 590 ctx.Data["NumCommits"] = len(compareInfo.Commits) 591 ctx.Data["NumFiles"] = compareInfo.NumFiles 592 return compareInfo 593} 594 595// ViewPullCommits show commits for a pull request 596func ViewPullCommits(ctx *context.Context) { 597 ctx.Data["PageIsPullList"] = true 598 ctx.Data["PageIsPullCommits"] = true 599 600 issue := checkPullInfo(ctx) 601 if ctx.Written() { 602 return 603 } 604 pull := issue.PullRequest 605 606 var prInfo *git.CompareInfo 607 if pull.HasMerged { 608 prInfo = PrepareMergedViewPullInfo(ctx, issue) 609 } else { 610 prInfo = PrepareViewPullInfo(ctx, issue) 611 } 612 613 if ctx.Written() { 614 return 615 } else if prInfo == nil { 616 ctx.NotFound("ViewPullCommits", nil) 617 return 618 } 619 620 ctx.Data["Username"] = ctx.Repo.Owner.Name 621 ctx.Data["Reponame"] = ctx.Repo.Repository.Name 622 623 commits := models.ConvertFromGitCommit(prInfo.Commits, ctx.Repo.Repository) 624 ctx.Data["Commits"] = commits 625 ctx.Data["CommitCount"] = len(commits) 626 627 getBranchData(ctx, issue) 628 ctx.HTML(http.StatusOK, tplPullCommits) 629} 630 631// ViewPullFiles render pull request changed files list page 632func ViewPullFiles(ctx *context.Context) { 633 ctx.Data["PageIsPullList"] = true 634 ctx.Data["PageIsPullFiles"] = true 635 636 issue := checkPullInfo(ctx) 637 if ctx.Written() { 638 return 639 } 640 pull := issue.PullRequest 641 642 var ( 643 startCommitID string 644 endCommitID string 645 gitRepo = ctx.Repo.GitRepo 646 ) 647 648 var prInfo *git.CompareInfo 649 if pull.HasMerged { 650 prInfo = PrepareMergedViewPullInfo(ctx, issue) 651 } else { 652 prInfo = PrepareViewPullInfo(ctx, issue) 653 } 654 655 if ctx.Written() { 656 return 657 } else if prInfo == nil { 658 ctx.NotFound("ViewPullFiles", nil) 659 return 660 } 661 662 headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) 663 if err != nil { 664 ctx.ServerError("GetRefCommitID", err) 665 return 666 } 667 668 startCommitID = prInfo.MergeBase 669 endCommitID = headCommitID 670 671 ctx.Data["Username"] = ctx.Repo.Owner.Name 672 ctx.Data["Reponame"] = ctx.Repo.Repository.Name 673 ctx.Data["AfterCommitID"] = endCommitID 674 675 fileOnly := ctx.FormBool("file-only") 676 677 maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles 678 files := ctx.FormStrings("files") 679 if fileOnly && (len(files) == 2 || len(files) == 1) { 680 maxLines, maxFiles = -1, -1 681 } 682 683 diff, err := gitdiff.GetDiff(gitRepo, 684 &gitdiff.DiffOptions{ 685 BeforeCommitID: startCommitID, 686 AfterCommitID: endCommitID, 687 SkipTo: ctx.FormString("skip-to"), 688 MaxLines: maxLines, 689 MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, 690 MaxFiles: maxFiles, 691 WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), 692 }, ctx.FormStrings("files")...) 693 if err != nil { 694 ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err) 695 return 696 } 697 698 if err = diff.LoadComments(issue, ctx.User); err != nil { 699 ctx.ServerError("LoadComments", err) 700 return 701 } 702 703 if err = pull.LoadProtectedBranch(); err != nil { 704 ctx.ServerError("LoadProtectedBranch", err) 705 return 706 } 707 708 if pull.ProtectedBranch != nil { 709 glob := pull.ProtectedBranch.GetProtectedFilePatterns() 710 if len(glob) != 0 { 711 for _, file := range diff.Files { 712 file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name) 713 } 714 } 715 } 716 717 ctx.Data["Diff"] = diff 718 ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 719 720 baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID) 721 if err != nil { 722 ctx.ServerError("GetCommit", err) 723 return 724 } 725 commit, err := gitRepo.GetCommit(endCommitID) 726 if err != nil { 727 ctx.ServerError("GetCommit", err) 728 return 729 } 730 731 if ctx.IsSigned && ctx.User != nil { 732 if ctx.Data["CanMarkConversation"], err = models.CanMarkConversation(issue, ctx.User); err != nil { 733 ctx.ServerError("CanMarkConversation", err) 734 return 735 } 736 } 737 738 setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) 739 740 ctx.Data["RequireHighlightJS"] = true 741 ctx.Data["RequireTribute"] = true 742 if ctx.Data["Assignees"], err = models.GetRepoAssignees(ctx.Repo.Repository); err != nil { 743 ctx.ServerError("GetAssignees", err) 744 return 745 } 746 handleTeamMentions(ctx) 747 if ctx.Written() { 748 return 749 } 750 ctx.Data["CurrentReview"], err = models.GetCurrentReview(ctx.User, issue) 751 if err != nil && !models.IsErrReviewNotExist(err) { 752 ctx.ServerError("GetCurrentReview", err) 753 return 754 } 755 getBranchData(ctx, issue) 756 ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) 757 ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) 758 759 ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled 760 upload.AddUploadContext(ctx, "comment") 761 762 ctx.HTML(http.StatusOK, tplPullFiles) 763} 764 765// UpdatePullRequest merge PR's baseBranch into headBranch 766func UpdatePullRequest(ctx *context.Context) { 767 issue := checkPullInfo(ctx) 768 if ctx.Written() { 769 return 770 } 771 if issue.IsClosed { 772 ctx.NotFound("MergePullRequest", nil) 773 return 774 } 775 if issue.PullRequest.HasMerged { 776 ctx.NotFound("MergePullRequest", nil) 777 return 778 } 779 780 rebase := ctx.FormString("style") == "rebase" 781 782 if err := issue.PullRequest.LoadBaseRepo(); err != nil { 783 ctx.ServerError("LoadBaseRepo", err) 784 return 785 } 786 if err := issue.PullRequest.LoadHeadRepo(); err != nil { 787 ctx.ServerError("LoadHeadRepo", err) 788 return 789 } 790 791 allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User) 792 if err != nil { 793 ctx.ServerError("IsUserAllowedToMerge", err) 794 return 795 } 796 797 // ToDo: add check if maintainers are allowed to change branch ... (need migration & co) 798 if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) { 799 ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) 800 ctx.Redirect(issue.Link()) 801 return 802 } 803 804 // default merge commit message 805 message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch) 806 807 if err = pull_service.Update(issue.PullRequest, ctx.User, message, rebase); err != nil { 808 if models.IsErrMergeConflicts(err) { 809 conflictError := err.(models.ErrMergeConflicts) 810 flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ 811 "Message": ctx.Tr("repo.pulls.merge_conflict"), 812 "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"), 813 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), 814 }) 815 if err != nil { 816 ctx.ServerError("UpdatePullRequest.HTMLString", err) 817 return 818 } 819 ctx.Flash.Error(flashError) 820 ctx.Redirect(issue.Link()) 821 return 822 } else if models.IsErrRebaseConflicts(err) { 823 conflictError := err.(models.ErrRebaseConflicts) 824 flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ 825 "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), 826 "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), 827 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), 828 }) 829 if err != nil { 830 ctx.ServerError("UpdatePullRequest.HTMLString", err) 831 return 832 } 833 ctx.Flash.Error(flashError) 834 ctx.Redirect(issue.Link()) 835 return 836 837 } 838 ctx.Flash.Error(err.Error()) 839 ctx.Redirect(issue.Link()) 840 return 841 } 842 843 time.Sleep(1 * time.Second) 844 845 ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success")) 846 ctx.Redirect(issue.Link()) 847} 848 849// MergePullRequest response for merging pull request 850func MergePullRequest(ctx *context.Context) { 851 form := web.GetForm(ctx).(*forms.MergePullRequestForm) 852 issue := checkPullInfo(ctx) 853 if ctx.Written() { 854 return 855 } 856 if issue.IsClosed { 857 if issue.IsPull { 858 ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed")) 859 ctx.Redirect(issue.Link()) 860 return 861 } 862 ctx.Flash.Error(ctx.Tr("repo.issues.closed_title")) 863 ctx.Redirect(issue.Link()) 864 return 865 } 866 867 pr := issue.PullRequest 868 869 allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.User) 870 if err != nil { 871 ctx.ServerError("IsUserAllowedToMerge", err) 872 return 873 } 874 if !allowedMerge { 875 ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) 876 ctx.Redirect(issue.Link()) 877 return 878 } 879 880 if pr.HasMerged { 881 ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged")) 882 ctx.Redirect(issue.Link()) 883 return 884 } 885 886 // handle manually-merged mark 887 if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged { 888 if err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { 889 if models.IsErrInvalidMergeStyle(err) { 890 ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) 891 ctx.Redirect(issue.Link()) 892 return 893 } else if strings.Contains(err.Error(), "Wrong commit ID") { 894 ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id")) 895 ctx.Redirect(issue.Link()) 896 return 897 } 898 899 ctx.ServerError("MergedManually", err) 900 return 901 } 902 903 ctx.Redirect(issue.Link()) 904 return 905 } 906 907 if !pr.CanAutoMerge() { 908 ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) 909 ctx.Redirect(issue.Link()) 910 return 911 } 912 913 if pr.IsWorkInProgress() { 914 ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip")) 915 ctx.Redirect(issue.Link()) 916 return 917 } 918 919 if err := pull_service.CheckPRReadyToMerge(pr, false); err != nil { 920 if !models.IsErrNotAllowedToMerge(err) { 921 ctx.ServerError("Merge PR status", err) 922 return 923 } 924 if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.User); err != nil { 925 ctx.ServerError("IsUserRepoAdmin", err) 926 return 927 } else if !isRepoAdmin { 928 ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) 929 ctx.Redirect(issue.Link()) 930 return 931 } 932 } 933 934 if ctx.HasError() { 935 ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) 936 ctx.Redirect(issue.Link()) 937 return 938 } 939 940 message := strings.TrimSpace(form.MergeTitleField) 941 if len(message) == 0 { 942 if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleMerge { 943 message = pr.GetDefaultMergeMessage() 944 } 945 if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleRebaseMerge { 946 message = pr.GetDefaultMergeMessage() 947 } 948 if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleSquash { 949 message = pr.GetDefaultSquashMessage() 950 } 951 } 952 953 form.MergeMessageField = strings.TrimSpace(form.MergeMessageField) 954 if len(form.MergeMessageField) > 0 { 955 message += "\n\n" + form.MergeMessageField 956 } 957 958 pr.Issue = issue 959 pr.Issue.Repo = ctx.Repo.Repository 960 961 noDeps, err := models.IssueNoDependenciesLeft(issue) 962 if err != nil { 963 return 964 } 965 966 if !noDeps { 967 ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked")) 968 ctx.Redirect(issue.Link()) 969 return 970 } 971 972 if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil { 973 if models.IsErrInvalidMergeStyle(err) { 974 ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) 975 ctx.Redirect(issue.Link()) 976 return 977 } else if models.IsErrMergeConflicts(err) { 978 conflictError := err.(models.ErrMergeConflicts) 979 flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ 980 "Message": ctx.Tr("repo.editor.merge_conflict"), 981 "Summary": ctx.Tr("repo.editor.merge_conflict_summary"), 982 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), 983 }) 984 if err != nil { 985 ctx.ServerError("MergePullRequest.HTMLString", err) 986 return 987 } 988 ctx.Flash.Error(flashError) 989 ctx.Redirect(issue.Link()) 990 return 991 } else if models.IsErrRebaseConflicts(err) { 992 conflictError := err.(models.ErrRebaseConflicts) 993 flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ 994 "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), 995 "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), 996 "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut), 997 }) 998 if err != nil { 999 ctx.ServerError("MergePullRequest.HTMLString", err) 1000 return 1001 } 1002 ctx.Flash.Error(flashError) 1003 ctx.Redirect(issue.Link()) 1004 return 1005 } else if models.IsErrMergeUnrelatedHistories(err) { 1006 log.Debug("MergeUnrelatedHistories error: %v", err) 1007 ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories")) 1008 ctx.Redirect(issue.Link()) 1009 return 1010 } else if git.IsErrPushOutOfDate(err) { 1011 log.Debug("MergePushOutOfDate error: %v", err) 1012 ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date")) 1013 ctx.Redirect(issue.Link()) 1014 return 1015 } else if models.IsErrSHADoesNotMatch(err) { 1016 log.Debug("MergeHeadOutOfDate error: %v", err) 1017 ctx.Flash.Error(ctx.Tr("repo.pulls.head_out_of_date")) 1018 ctx.Redirect(issue.Link()) 1019 return 1020 } else if git.IsErrPushRejected(err) { 1021 log.Debug("MergePushRejected error: %v", err) 1022 pushrejErr := err.(*git.ErrPushRejected) 1023 message := pushrejErr.Message 1024 if len(message) == 0 { 1025 ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message")) 1026 } else { 1027 flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ 1028 "Message": ctx.Tr("repo.pulls.push_rejected"), 1029 "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), 1030 "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), 1031 }) 1032 if err != nil { 1033 ctx.ServerError("MergePullRequest.HTMLString", err) 1034 return 1035 } 1036 ctx.Flash.Error(flashError) 1037 } 1038 ctx.Redirect(issue.Link()) 1039 return 1040 } 1041 ctx.ServerError("Merge", err) 1042 return 1043 } 1044 1045 if err := stopTimerIfAvailable(ctx.User, issue); err != nil { 1046 ctx.ServerError("CreateOrStopIssueStopwatch", err) 1047 return 1048 } 1049 1050 log.Trace("Pull request merged: %d", pr.ID) 1051 1052 if form.DeleteBranchAfterMerge { 1053 // Don't cleanup when other pr use this branch as head branch 1054 exist, err := models.HasUnmergedPullRequestsByHeadInfo(pr.HeadRepoID, pr.HeadBranch) 1055 if err != nil { 1056 ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err) 1057 return 1058 } 1059 if exist { 1060 ctx.Redirect(issue.Link()) 1061 return 1062 } 1063 1064 var headRepo *git.Repository 1065 if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil { 1066 headRepo = ctx.Repo.GitRepo 1067 } else { 1068 headRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath()) 1069 if err != nil { 1070 ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err) 1071 return 1072 } 1073 defer headRepo.Close() 1074 } 1075 deleteBranch(ctx, pr, headRepo) 1076 } 1077 1078 ctx.Redirect(issue.Link()) 1079} 1080 1081func stopTimerIfAvailable(user *user_model.User, issue *models.Issue) error { 1082 1083 if models.StopwatchExists(user.ID, issue.ID) { 1084 if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil { 1085 return err 1086 } 1087 } 1088 1089 return nil 1090} 1091 1092// CompareAndPullRequestPost response for creating pull request 1093func CompareAndPullRequestPost(ctx *context.Context) { 1094 form := web.GetForm(ctx).(*forms.CreateIssueForm) 1095 ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes") 1096 ctx.Data["PageIsComparePull"] = true 1097 ctx.Data["IsDiffCompare"] = true 1098 ctx.Data["IsRepoToolbarCommits"] = true 1099 ctx.Data["RequireTribute"] = true 1100 ctx.Data["RequireHighlightJS"] = true 1101 ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes 1102 ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled 1103 upload.AddUploadContext(ctx, "comment") 1104 ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypePullRequests) 1105 1106 var ( 1107 repo = ctx.Repo.Repository 1108 attachments []string 1109 ) 1110 1111 ci := ParseCompareInfo(ctx) 1112 defer func() { 1113 if ci != nil && ci.HeadGitRepo != nil { 1114 ci.HeadGitRepo.Close() 1115 } 1116 }() 1117 if ctx.Written() { 1118 return 1119 } 1120 1121 labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true) 1122 if ctx.Written() { 1123 return 1124 } 1125 1126 if setting.Attachment.Enabled { 1127 attachments = form.Files 1128 } 1129 1130 if ctx.HasError() { 1131 middleware.AssignForm(form, ctx.Data) 1132 1133 // This stage is already stop creating new pull request, so it does not matter if it has 1134 // something to compare or not. 1135 PrepareCompareDiff(ctx, ci, 1136 gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) 1137 if ctx.Written() { 1138 return 1139 } 1140 1141 if len(form.Title) > 255 { 1142 var trailer string 1143 form.Title, trailer = util.SplitStringAtByteN(form.Title, 255) 1144 1145 form.Content = trailer + "\n\n" + form.Content 1146 } 1147 middleware.AssignForm(form, ctx.Data) 1148 1149 ctx.HTML(http.StatusOK, tplCompareDiff) 1150 return 1151 } 1152 1153 if util.IsEmptyString(form.Title) { 1154 PrepareCompareDiff(ctx, ci, 1155 gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) 1156 if ctx.Written() { 1157 return 1158 } 1159 1160 ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form) 1161 return 1162 } 1163 1164 pullIssue := &models.Issue{ 1165 RepoID: repo.ID, 1166 Repo: repo, 1167 Title: form.Title, 1168 PosterID: ctx.User.ID, 1169 Poster: ctx.User, 1170 MilestoneID: milestoneID, 1171 IsPull: true, 1172 Content: form.Content, 1173 } 1174 pullRequest := &models.PullRequest{ 1175 HeadRepoID: ci.HeadRepo.ID, 1176 BaseRepoID: repo.ID, 1177 HeadBranch: ci.HeadBranch, 1178 BaseBranch: ci.BaseBranch, 1179 HeadRepo: ci.HeadRepo, 1180 BaseRepo: repo, 1181 MergeBase: ci.CompareInfo.MergeBase, 1182 Type: models.PullRequestGitea, 1183 } 1184 // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt 1185 // instead of 500. 1186 1187 if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil { 1188 if models.IsErrUserDoesNotHaveAccessToRepo(err) { 1189 ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) 1190 return 1191 } else if git.IsErrPushRejected(err) { 1192 pushrejErr := err.(*git.ErrPushRejected) 1193 message := pushrejErr.Message 1194 if len(message) == 0 { 1195 ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message")) 1196 } else { 1197 flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ 1198 "Message": ctx.Tr("repo.pulls.push_rejected"), 1199 "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), 1200 "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), 1201 }) 1202 if err != nil { 1203 ctx.ServerError("CompareAndPullRequest.HTMLString", err) 1204 return 1205 } 1206 ctx.Flash.Error(flashError) 1207 } 1208 ctx.Redirect(pullIssue.Link()) 1209 return 1210 } 1211 ctx.ServerError("NewPullRequest", err) 1212 return 1213 } 1214 1215 log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID) 1216 ctx.Redirect(pullIssue.Link()) 1217} 1218 1219// CleanUpPullRequest responses for delete merged branch when PR has been merged 1220func CleanUpPullRequest(ctx *context.Context) { 1221 issue := checkPullInfo(ctx) 1222 if ctx.Written() { 1223 return 1224 } 1225 1226 pr := issue.PullRequest 1227 1228 // Don't cleanup unmerged and unclosed PRs 1229 if !pr.HasMerged && !issue.IsClosed { 1230 ctx.NotFound("CleanUpPullRequest", nil) 1231 return 1232 } 1233 1234 // Don't cleanup when there are other PR's that use this branch as head branch. 1235 exist, err := models.HasUnmergedPullRequestsByHeadInfo(pr.HeadRepoID, pr.HeadBranch) 1236 if err != nil { 1237 ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err) 1238 return 1239 } 1240 if exist { 1241 ctx.NotFound("CleanUpPullRequest", nil) 1242 return 1243 } 1244 1245 if err := pr.LoadHeadRepo(); err != nil { 1246 ctx.ServerError("LoadHeadRepo", err) 1247 return 1248 } else if pr.HeadRepo == nil { 1249 // Forked repository has already been deleted 1250 ctx.NotFound("CleanUpPullRequest", nil) 1251 return 1252 } else if err = pr.LoadBaseRepo(); err != nil { 1253 ctx.ServerError("LoadBaseRepo", err) 1254 return 1255 } else if err = pr.HeadRepo.GetOwner(db.DefaultContext); err != nil { 1256 ctx.ServerError("HeadRepo.GetOwner", err) 1257 return 1258 } 1259 1260 perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.User) 1261 if err != nil { 1262 ctx.ServerError("GetUserRepoPermission", err) 1263 return 1264 } 1265 if !perm.CanWrite(unit.TypeCode) { 1266 ctx.NotFound("CleanUpPullRequest", nil) 1267 return 1268 } 1269 1270 fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch 1271 1272 var gitBaseRepo *git.Repository 1273 1274 // Assume that the base repo is the current context (almost certainly) 1275 if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.BaseRepoID && ctx.Repo.GitRepo != nil { 1276 gitBaseRepo = ctx.Repo.GitRepo 1277 } else { 1278 // If not just open it 1279 gitBaseRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath()) 1280 if err != nil { 1281 ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err) 1282 return 1283 } 1284 defer gitBaseRepo.Close() 1285 } 1286 1287 // Now assume that the head repo is the same as the base repo (reasonable chance) 1288 gitRepo := gitBaseRepo 1289 // But if not: is it the same as the context? 1290 if pr.BaseRepoID != pr.HeadRepoID && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil { 1291 gitRepo = ctx.Repo.GitRepo 1292 } else if pr.BaseRepoID != pr.HeadRepoID { 1293 // Otherwise just load it up 1294 gitRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath()) 1295 if err != nil { 1296 ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err) 1297 return 1298 } 1299 defer gitRepo.Close() 1300 } 1301 1302 defer func() { 1303 ctx.JSON(http.StatusOK, map[string]interface{}{ 1304 "redirect": issue.Link(), 1305 }) 1306 }() 1307 1308 // Check if branch has no new commits 1309 headCommitID, err := gitBaseRepo.GetRefCommitID(pr.GetGitRefName()) 1310 if err != nil { 1311 log.Error("GetRefCommitID: %v", err) 1312 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1313 return 1314 } 1315 branchCommitID, err := gitRepo.GetBranchCommitID(pr.HeadBranch) 1316 if err != nil { 1317 log.Error("GetBranchCommitID: %v", err) 1318 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1319 return 1320 } 1321 if headCommitID != branchCommitID { 1322 ctx.Flash.Error(ctx.Tr("repo.branch.delete_branch_has_new_commits", fullBranchName)) 1323 return 1324 } 1325 1326 deleteBranch(ctx, pr, gitRepo) 1327} 1328 1329func deleteBranch(ctx *context.Context, pr *models.PullRequest, gitRepo *git.Repository) { 1330 fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch 1331 if err := repo_service.DeleteBranch(ctx.User, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil { 1332 switch { 1333 case git.IsErrBranchNotExist(err): 1334 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1335 case errors.Is(err, repo_service.ErrBranchIsDefault): 1336 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1337 case errors.Is(err, repo_service.ErrBranchIsProtected): 1338 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1339 default: 1340 log.Error("DeleteBranch: %v", err) 1341 ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) 1342 } 1343 return 1344 } 1345 1346 if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, pr.IssueID, pr.HeadBranch); err != nil { 1347 // Do not fail here as branch has already been deleted 1348 log.Error("DeleteBranch: %v", err) 1349 } 1350 1351 ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName)) 1352} 1353 1354// DownloadPullDiff render a pull's raw diff 1355func DownloadPullDiff(ctx *context.Context) { 1356 DownloadPullDiffOrPatch(ctx, false) 1357} 1358 1359// DownloadPullPatch render a pull's raw patch 1360func DownloadPullPatch(ctx *context.Context) { 1361 DownloadPullDiffOrPatch(ctx, true) 1362} 1363 1364// DownloadPullDiffOrPatch render a pull's raw diff or patch 1365func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) { 1366 pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) 1367 if err != nil { 1368 if models.IsErrPullRequestNotExist(err) { 1369 ctx.NotFound("GetPullRequestByIndex", err) 1370 } else { 1371 ctx.ServerError("GetPullRequestByIndex", err) 1372 } 1373 return 1374 } 1375 1376 binary := ctx.FormBool("binary") 1377 1378 if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch, binary); err != nil { 1379 ctx.ServerError("DownloadDiffOrPatch", err) 1380 return 1381 } 1382} 1383 1384// UpdatePullRequestTarget change pull request's target branch 1385func UpdatePullRequestTarget(ctx *context.Context) { 1386 issue := GetActionIssue(ctx) 1387 pr := issue.PullRequest 1388 if ctx.Written() { 1389 return 1390 } 1391 if !issue.IsPull { 1392 ctx.Error(http.StatusNotFound) 1393 return 1394 } 1395 1396 if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { 1397 ctx.Error(http.StatusForbidden) 1398 return 1399 } 1400 1401 targetBranch := ctx.FormTrim("target_branch") 1402 if len(targetBranch) == 0 { 1403 ctx.Error(http.StatusNoContent) 1404 return 1405 } 1406 1407 if err := pull_service.ChangeTargetBranch(pr, ctx.User, targetBranch); err != nil { 1408 if models.IsErrPullRequestAlreadyExists(err) { 1409 err := err.(models.ErrPullRequestAlreadyExists) 1410 1411 RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name 1412 errorMessage := ctx.Tr("repo.pulls.has_pull_request", html.EscapeString(ctx.Repo.RepoLink+"/pulls/"+strconv.FormatInt(err.IssueID, 10)), html.EscapeString(RepoRelPath), err.IssueID) // FIXME: Creates url insidde locale string 1413 1414 ctx.Flash.Error(errorMessage) 1415 ctx.JSON(http.StatusConflict, map[string]interface{}{ 1416 "error": err.Error(), 1417 "user_error": errorMessage, 1418 }) 1419 } else if models.IsErrIssueIsClosed(err) { 1420 errorMessage := ctx.Tr("repo.pulls.is_closed") 1421 1422 ctx.Flash.Error(errorMessage) 1423 ctx.JSON(http.StatusConflict, map[string]interface{}{ 1424 "error": err.Error(), 1425 "user_error": errorMessage, 1426 }) 1427 } else if models.IsErrPullRequestHasMerged(err) { 1428 errorMessage := ctx.Tr("repo.pulls.has_merged") 1429 1430 ctx.Flash.Error(errorMessage) 1431 ctx.JSON(http.StatusConflict, map[string]interface{}{ 1432 "error": err.Error(), 1433 "user_error": errorMessage, 1434 }) 1435 } else if models.IsErrBranchesEqual(err) { 1436 errorMessage := ctx.Tr("repo.pulls.nothing_to_compare") 1437 1438 ctx.Flash.Error(errorMessage) 1439 ctx.JSON(http.StatusBadRequest, map[string]interface{}{ 1440 "error": err.Error(), 1441 "user_error": errorMessage, 1442 }) 1443 } else { 1444 ctx.ServerError("UpdatePullRequestTarget", err) 1445 } 1446 return 1447 } 1448 notification.NotifyPullRequestChangeTargetBranch(ctx.User, pr, targetBranch) 1449 1450 ctx.JSON(http.StatusOK, map[string]interface{}{ 1451 "base_branch": pr.BaseBranch, 1452 }) 1453} 1454