mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	Fix pull view when head repository or head branch missed and close related pull requests when delete head repository or head branch (#9927) (#9974)
* 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> Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
		| @@ -106,3 +106,57 @@ func TestPullCreate_TitleEscape(t *testing.T) { | |||||||
| 		assert.Equal(t, "<u>XSS PR</u>", titleHTML) | 		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) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -68,7 +68,11 @@ type PullRequest struct { | |||||||
| // MustHeadUserName returns the HeadRepo's username if failed return blank | // MustHeadUserName returns the HeadRepo's username if failed return blank | ||||||
| func (pr *PullRequest) MustHeadUserName() string { | func (pr *PullRequest) MustHeadUserName() string { | ||||||
| 	if err := pr.LoadHeadRepo(); err != nil { | 	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 "" | ||||||
| 	} | 	} | ||||||
| 	return pr.HeadRepo.MustOwnerName() | 	return pr.HeadRepo.MustOwnerName() | ||||||
|   | |||||||
| @@ -475,9 +475,18 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions) | |||||||
| 		return err | 		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) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true) | 		log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) | ||||||
|  |  | ||||||
|  | 		go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true) | ||||||
|  | 		// 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 { | 	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) | 		log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) | ||||||
| @@ -524,12 +533,15 @@ func PushUpdates(repo *models.Repository, optsList []*PushUpdateOptions) error { | |||||||
| 			if err = models.RemoveDeletedBranch(repo.ID, opts.Branch); err != nil { | 			if err = models.RemoveDeletedBranch(repo.ID, opts.Branch); err != nil { | ||||||
| 				log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, opts.Branch, err) | 				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) | ||||||
|  |  | ||||||
|  | 			go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true) | ||||||
|  | 			// 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) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		log.Trace("TriggerTask '%s/%s' by %s", repo.Name, opts.Branch, pusher.Name) |  | ||||||
|  |  | ||||||
| 		go pull_service.AddTestPullRequestTask(pusher, repo.ID, opts.Branch, true) |  | ||||||
|  |  | ||||||
| 		if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { | 		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) | 			log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| @@ -261,3 +262,79 @@ func PushToBaseRepo(pr *models.PullRequest) (err error) { | |||||||
|  |  | ||||||
| 	return nil | 	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 | ||||||
|  | } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/notification" | 	"code.gitea.io/gitea/modules/notification" | ||||||
|  | 	pull_service "code.gitea.io/gitea/services/pull" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // CreateRepository creates a repository for the user/organization. | // CreateRepository creates a repository for the user/organization. | ||||||
| @@ -48,6 +49,10 @@ func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc | |||||||
|  |  | ||||||
| // DeleteRepository deletes a repository for a user or organization. | // DeleteRepository deletes a repository for a user or organization. | ||||||
| func DeleteRepository(doer *models.User, repo *models.Repository) error { | 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 { | 	if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user