* fix pull view when head repository or head branch missed and close related pull requests when delete branch
* fix pull view broken when head repository deleted
* close pull requests when head repositories deleted
* Add tests for broken pull request head repository or branch
* fix typo
* ignore special error when close pull request
Co-authored-by: Lauris BH <lauris@nix.lv>
assert.Equal(t, "<u>XSS PR</u>", titleHTML)
})
}
+
+func testUIDeleteBranch(t *testing.T, session *TestSession, ownerName, repoName, branchName string) {
+ relURL := "/" + path.Join(ownerName, repoName, "branches")
+ req := NewRequest(t, "GET", relURL)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+
+ req = NewRequestWithValues(t, "POST", relURL+"/delete", map[string]string{
+ "_csrf": getCsrf(t, htmlDoc.doc),
+ "name": branchName,
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+}
+
+func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoName string) {
+ relURL := "/" + path.Join(ownerName, repoName, "settings")
+ req := NewRequest(t, "GET", relURL)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+
+ req = NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{
+ "_csrf": getCsrf(t, htmlDoc.doc),
+ "repo_name": repoName,
+ })
+ session.MakeRequest(t, req, http.StatusFound)
+}
+
+func TestPullBranchDelete(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ defer prepareTestEnv(t)()
+
+ session := loginUser(t, "user1")
+ testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
+ testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusFound)
+ testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
+ resp := testPullCreate(t, session, "user1", "repo1", "master1", "This is a pull title")
+
+ // check the redirected URL
+ url := resp.HeaderMap.Get("Location")
+ assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
+ req := NewRequest(t, "GET", url)
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // delete head branch and confirm pull page is ok
+ testUIDeleteBranch(t, session, "user1", "repo1", "master1")
+ req = NewRequest(t, "GET", url)
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // delete head repository and confirm pull page is ok
+ testDeleteRepository(t, session, "user1", "repo1")
+ req = NewRequest(t, "GET", url)
+ session.MakeRequest(t, req, http.StatusOK)
+ })
+}
// MustHeadUserName returns the HeadRepo's username if failed return blank
func (pr *PullRequest) MustHeadUserName() string {
if err := pr.LoadHeadRepo(); err != nil {
- log.Error("LoadHeadRepo: %v", err)
+ if !IsErrRepoNotExist(err) {
+ log.Error("LoadHeadRepo: %v", err)
+ } else {
+ log.Warn("LoadHeadRepo %d but repository does not exist: %v", pr.HeadRepoID, err)
+ }
return ""
}
return pr.HeadRepo.OwnerName
return err
}
- log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
+ if !isDelRef {
+ if err = models.RemoveDeletedBranch(repo.ID, opts.Branch); err != nil {
+ log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, opts.Branch, err)
+ }
+
+ log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
- go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
+ go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
+ // close all related pulls
+ } else if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil {
+ log.Error("close related pull request failed: %v", err)
+ }
if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil {
log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)
if err = models.RemoveDeletedBranch(repo.ID, opts.Branch); err != nil {
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, opts.Branch, err)
}
- }
- log.Trace("TriggerTask '%s/%s' by %s", repo.Name, opts.Branch, pusher.Name)
+ log.Trace("TriggerTask '%s/%s' by %s", repo.Name, opts.Branch, pusher.Name)
- go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true, opts.OldCommitID, opts.NewCommitID)
+ go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true, opts.OldCommitID, opts.NewCommitID)
+ // close all related pulls
+ } else if err = pull_service.CloseBranchPulls(pusher, repo.ID, opts.Branch); err != nil {
+ log.Error("close related pull request failed: %v", err)
+ }
if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil {
log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)
setMergeTarget(ctx, pull)
- divergence, err := pull_service.GetDiverging(pull)
- if err != nil {
- ctx.ServerError("GetDiverging", err)
- return nil
- }
- ctx.Data["Divergence"] = divergence
- allowUpdate, err := pull_service.IsUserAllowedToUpdate(pull, ctx.User)
- if err != nil {
- ctx.ServerError("IsUserAllowedToUpdate", err)
- return nil
- }
- ctx.Data["UpdateAllowed"] = allowUpdate
-
if err := pull.LoadProtectedBranch(); err != nil {
ctx.ServerError("LoadProtectedBranch", err)
return nil
}
}
+ if headBranchExist {
+ allowUpdate, err := pull_service.IsUserAllowedToUpdate(pull, ctx.User)
+ if err != nil {
+ ctx.ServerError("IsUserAllowedToUpdate", err)
+ return nil
+ }
+ ctx.Data["UpdateAllowed"] = allowUpdate
+
+ divergence, err := pull_service.GetDiverging(pull)
+ if err != nil {
+ ctx.ServerError("GetDiverging", err)
+ return nil
+ }
+ ctx.Data["Divergence"] = divergence
+ }
+
sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
if err != nil {
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
return nil
}
+
+type errlist []error
+
+func (errs errlist) Error() string {
+ if len(errs) > 0 {
+ var buf strings.Builder
+ for i, err := range errs {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(err.Error())
+ }
+ return buf.String()
+ }
+ return ""
+}
+
+// CloseBranchPulls close all the pull requests who's head branch is the branch
+func CloseBranchPulls(doer *models.User, repoID int64, branch string) error {
+ prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
+ if err != nil {
+ return err
+ }
+
+ prs2, err := models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
+ if err != nil {
+ return err
+ }
+
+ prs = append(prs, prs2...)
+ if err := models.PullRequestList(prs).LoadAttributes(); err != nil {
+ return err
+ }
+
+ var errs errlist
+ for _, pr := range prs {
+ if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrIssueWasClosed(err) {
+ errs = append(errs, err)
+ }
+ }
+ if len(errs) > 0 {
+ return errs
+ }
+ return nil
+}
+
+// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository
+func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
+ branches, err := git.GetBranchesByPath(repo.RepoPath())
+ if err != nil {
+ return err
+ }
+
+ var errs errlist
+ for _, branch := range branches {
+ prs, err := models.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name)
+ if err != nil {
+ return err
+ }
+
+ if err = models.PullRequestList(prs).LoadAttributes(); err != nil {
+ return err
+ }
+
+ for _, pr := range prs {
+ if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrIssueWasClosed(err) {
+ errs = append(errs, err)
+ }
+ }
+ }
+
+ if len(errs) > 0 {
+ return errs
+ }
+ return nil
+}
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
+ pull_service "code.gitea.io/gitea/services/pull"
)
// CreateRepository creates a repository for the user/organization.
// DeleteRepository deletes a repository for a user or organization.
func DeleteRepository(doer *models.User, repo *models.Repository) error {
+ if err := pull_service.CloseRepoBranchesPulls(doer, repo); err != nil {
+ log.Error("CloseRepoBranchesPulls failed: %v", err)
+ }
+
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
return err
}