fix(api)!: list only open pull requests when no state is given (#40078)

`GET /repos/{owner}/{repo}/pulls` is documented with `state` defaulting
to `open`, but without `state` it returned every pull request, closed
and merged ones included. Now it parses `state` with
`ParseIssueFilterStateIsClosed`, like the issues and milestones
endpoints, so no state means open, as documented. `closed` and `all`
work as before. GitHub's pulls endpoint also defaults to open.
This commit is contained in:
Jorge authored and GitHub committed 2026-10-30 08:42:49 +00:00
1 parent 467963f0dd
commit 2660b4d2c4
4 files changed
+31 -8

No files matched your search

+4 -4
View File
@@ -14,6 +14,7 @@ import (
user_model "gitea.dev/models/user"
"gitea.dev/modules/container"
"gitea.dev/modules/log"
"gitea.dev/modules/optional"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
@@ -23,7 +24,7 @@ import (
// PullRequestsOptions holds the options for PRs
type PullRequestsOptions struct {
db.ListOptions
State string
IsClosed optional.Option[bool]
SortType string
Labels []int64
MilestoneID int64
@@ -39,9 +40,8 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
}
sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
switch opts.State {
case "closed", "open":
sess.And("issue.is_closed=?", opts.State == "closed")
if opts.IsClosed.Has() {
sess.And("issue.is_closed=?", opts.IsClosed.Value())
}
if len(opts.Labels) > 0 {
+4 -3
View File
@@ -13,6 +13,7 @@ import (
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/optional"
"gitea.dev/modules/setting"
"github.com/stretchr/testify/assert"
@@ -92,7 +93,7 @@ func testPullRequestsNewest(t *testing.T) {
ListOptions: db.ListOptions{
Page: 1,
},
State: "open",
IsClosed: optional.Some(false),
SortType: "newest",
})
assert.NoError(t, err)
@@ -130,7 +131,7 @@ func testPullRequestsClosedRecentSortType(t *testing.T) {
ListOptions: db.ListOptions{
Page: 1,
},
State: "closed",
IsClosed: optional.Some(true),
SortType: test.sortType,
})
require.NoError(t, err)
@@ -175,7 +176,7 @@ func testPullRequestsOldest(t *testing.T) {
ListOptions: db.ListOptions{
Page: 1,
},
State: "open",
IsClosed: optional.Some(false),
SortType: "oldest",
})
assert.NoError(t, err)
+1 -1
View File
@@ -131,7 +131,7 @@ func ListPullRequests(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{
ListOptions: listOptions,
State: ctx.FormTrim("state"),
IsClosed: common.ParseIssueFilterStateIsClosed(ctx.FormTrim("state")),
SortType: ctx.FormTrim("sort"),
Labels: labelIDs,
MilestoneID: ctx.FormInt64("milestone"),
+22
View File
@@ -144,6 +144,28 @@ func TestAPIViewPulls(t *testing.T) {
}))
}
t.Run("DefaultState", func(t *testing.T) {
writeToken := getTokenForLoggedInUser(t, ctx.Session, auth_model.AccessTokenScopeWriteRepository)
req := NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/3", owner.Name, repo.Name), &api.EditPullRequestOption{
State: new(string(api.StateClosed)),
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusCreated)
for query, want := range map[string][]int64{
"": {2, 5},
"state=closed": {3},
"state=all": {2, 3, 5},
} {
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls?%s", owner.Name, repo.Name, query).AddTokenAuth(ctx.Token)
listed := DecodeJSON(t, ctx.Session.MakeRequest(t, req, http.StatusOK), []*api.PullRequest{})
indexes := make([]int64, 0, len(listed))
for _, pr := range listed {
indexes = append(indexes, pr.Index)
}
assert.ElementsMatch(t, want, indexes, query)
}
})
t.Run("APIViewPullsByBaseHead", func(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name).
AddTokenAuth(ctx.Token)