Skip to content

Commit f2d12f7

Browse files
lunnylafriks
andauthored
Fix pull view when head repository or head branch missed and close related pull requests when delete head repository or head branch (#9927)
* 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 <[email protected]>
1 parent ee26f04 commit f2d12f7

File tree

6 files changed

+173
-19
lines changed

6 files changed

+173
-19
lines changed

integrations/pull_create_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,57 @@ func TestPullCreate_TitleEscape(t *testing.T) {
106106
assert.Equal(t, "&lt;u&gt;XSS PR&lt;/u&gt;", titleHTML)
107107
})
108108
}
109+
110+
func testUIDeleteBranch(t *testing.T, session *TestSession, ownerName, repoName, branchName string) {
111+
relURL := "/" + path.Join(ownerName, repoName, "branches")
112+
req := NewRequest(t, "GET", relURL)
113+
resp := session.MakeRequest(t, req, http.StatusOK)
114+
htmlDoc := NewHTMLParser(t, resp.Body)
115+
116+
req = NewRequestWithValues(t, "POST", relURL+"/delete", map[string]string{
117+
"_csrf": getCsrf(t, htmlDoc.doc),
118+
"name": branchName,
119+
})
120+
session.MakeRequest(t, req, http.StatusOK)
121+
}
122+
123+
func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoName string) {
124+
relURL := "/" + path.Join(ownerName, repoName, "settings")
125+
req := NewRequest(t, "GET", relURL)
126+
resp := session.MakeRequest(t, req, http.StatusOK)
127+
htmlDoc := NewHTMLParser(t, resp.Body)
128+
129+
req = NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{
130+
"_csrf": getCsrf(t, htmlDoc.doc),
131+
"repo_name": repoName,
132+
})
133+
session.MakeRequest(t, req, http.StatusFound)
134+
}
135+
136+
func TestPullBranchDelete(t *testing.T) {
137+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
138+
defer prepareTestEnv(t)()
139+
140+
session := loginUser(t, "user1")
141+
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
142+
testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusFound)
143+
testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
144+
resp := testPullCreate(t, session, "user1", "repo1", "master1", "This is a pull title")
145+
146+
// check the redirected URL
147+
url := resp.HeaderMap.Get("Location")
148+
assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
149+
req := NewRequest(t, "GET", url)
150+
session.MakeRequest(t, req, http.StatusOK)
151+
152+
// delete head branch and confirm pull page is ok
153+
testUIDeleteBranch(t, session, "user1", "repo1", "master1")
154+
req = NewRequest(t, "GET", url)
155+
session.MakeRequest(t, req, http.StatusOK)
156+
157+
// delete head repository and confirm pull page is ok
158+
testDeleteRepository(t, session, "user1", "repo1")
159+
req = NewRequest(t, "GET", url)
160+
session.MakeRequest(t, req, http.StatusOK)
161+
})
162+
}

models/pull.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ type PullRequest struct {
6767
// MustHeadUserName returns the HeadRepo's username if failed return blank
6868
func (pr *PullRequest) MustHeadUserName() string {
6969
if err := pr.LoadHeadRepo(); err != nil {
70-
log.Error("LoadHeadRepo: %v", err)
70+
if !IsErrRepoNotExist(err) {
71+
log.Error("LoadHeadRepo: %v", err)
72+
} else {
73+
log.Warn("LoadHeadRepo %d but repository does not exist: %v", pr.HeadRepoID, err)
74+
}
7175
return ""
7276
}
7377
return pr.HeadRepo.OwnerName

modules/repofiles/update.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,18 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions)
495495
return err
496496
}
497497

498-
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
498+
if !isDelRef {
499+
if err = models.RemoveDeletedBranch(repo.ID, opts.Branch); err != nil {
500+
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, opts.Branch, err)
501+
}
502+
503+
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
499504

500-
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
505+
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
506+
// close all related pulls
507+
} else if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil {
508+
log.Error("close related pull request failed: %v", err)
509+
}
501510

502511
if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil {
503512
log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)
@@ -544,11 +553,14 @@ func PushUpdates(repo *models.Repository, optsList []*PushUpdateOptions) error {
544553
if err = models.RemoveDeletedBranch(repo.ID, opts.Branch); err != nil {
545554
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, opts.Branch, err)
546555
}
547-
}
548556

549-
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, opts.Branch, pusher.Name)
557+
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, opts.Branch, pusher.Name)
550558

551-
go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true, opts.OldCommitID, opts.NewCommitID)
559+
go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true, opts.OldCommitID, opts.NewCommitID)
560+
// close all related pulls
561+
} else if err = pull_service.CloseBranchPulls(pusher, repo.ID, opts.Branch); err != nil {
562+
log.Error("close related pull request failed: %v", err)
563+
}
552564

553565
if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil {
554566
log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)

routers/repo/pull.go

+16-13
Original file line numberDiff line numberDiff line change
@@ -343,19 +343,6 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
343343

344344
setMergeTarget(ctx, pull)
345345

346-
divergence, err := pull_service.GetDiverging(pull)
347-
if err != nil {
348-
ctx.ServerError("GetDiverging", err)
349-
return nil
350-
}
351-
ctx.Data["Divergence"] = divergence
352-
allowUpdate, err := pull_service.IsUserAllowedToUpdate(pull, ctx.User)
353-
if err != nil {
354-
ctx.ServerError("IsUserAllowedToUpdate", err)
355-
return nil
356-
}
357-
ctx.Data["UpdateAllowed"] = allowUpdate
358-
359346
if err := pull.LoadProtectedBranch(); err != nil {
360347
ctx.ServerError("LoadProtectedBranch", err)
361348
return nil
@@ -392,6 +379,22 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
392379
}
393380
}
394381

382+
if headBranchExist {
383+
allowUpdate, err := pull_service.IsUserAllowedToUpdate(pull, ctx.User)
384+
if err != nil {
385+
ctx.ServerError("IsUserAllowedToUpdate", err)
386+
return nil
387+
}
388+
ctx.Data["UpdateAllowed"] = allowUpdate
389+
390+
divergence, err := pull_service.GetDiverging(pull)
391+
if err != nil {
392+
ctx.ServerError("GetDiverging", err)
393+
return nil
394+
}
395+
ctx.Data["Divergence"] = divergence
396+
}
397+
395398
sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
396399
if err != nil {
397400
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)

services/pull/pull.go

+76
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,79 @@ func PushToBaseRepo(pr *models.PullRequest) (err error) {
355355

356356
return nil
357357
}
358+
359+
type errlist []error
360+
361+
func (errs errlist) Error() string {
362+
if len(errs) > 0 {
363+
var buf strings.Builder
364+
for i, err := range errs {
365+
if i > 0 {
366+
buf.WriteString(", ")
367+
}
368+
buf.WriteString(err.Error())
369+
}
370+
return buf.String()
371+
}
372+
return ""
373+
}
374+
375+
// CloseBranchPulls close all the pull requests who's head branch is the branch
376+
func CloseBranchPulls(doer *models.User, repoID int64, branch string) error {
377+
prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
378+
if err != nil {
379+
return err
380+
}
381+
382+
prs2, err := models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
383+
if err != nil {
384+
return err
385+
}
386+
387+
prs = append(prs, prs2...)
388+
if err := models.PullRequestList(prs).LoadAttributes(); err != nil {
389+
return err
390+
}
391+
392+
var errs errlist
393+
for _, pr := range prs {
394+
if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrIssueWasClosed(err) {
395+
errs = append(errs, err)
396+
}
397+
}
398+
if len(errs) > 0 {
399+
return errs
400+
}
401+
return nil
402+
}
403+
404+
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository
405+
func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
406+
branches, err := git.GetBranchesByPath(repo.RepoPath())
407+
if err != nil {
408+
return err
409+
}
410+
411+
var errs errlist
412+
for _, branch := range branches {
413+
prs, err := models.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name)
414+
if err != nil {
415+
return err
416+
}
417+
418+
if err = models.PullRequestList(prs).LoadAttributes(); err != nil {
419+
return err
420+
}
421+
422+
for _, pr := range prs {
423+
if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrIssueWasClosed(err) {
424+
errs = append(errs, err)
425+
}
426+
}
427+
}
428+
429+
if len(errs) > 0 {
430+
return errs
431+
}
432+
return nil
433+
}

services/repository/repository.go

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"code.gitea.io/gitea/modules/log"
1212
"code.gitea.io/gitea/modules/notification"
1313
repo_module "code.gitea.io/gitea/modules/repository"
14+
pull_service "code.gitea.io/gitea/services/pull"
1415
)
1516

1617
// CreateRepository creates a repository for the user/organization.
@@ -49,6 +50,10 @@ func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc
4950

5051
// DeleteRepository deletes a repository for a user or organization.
5152
func DeleteRepository(doer *models.User, repo *models.Repository) error {
53+
if err := pull_service.CloseRepoBranchesPulls(doer, repo); err != nil {
54+
log.Error("CloseRepoBranchesPulls failed: %v", err)
55+
}
56+
5257
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
5358
return err
5459
}

0 commit comments

Comments
 (0)