Skip to content

Commit 6595241

Browse files
authored
Add API to get PR by base/head (#29242)
Closes #16289 Add a new API `/repos/{owner}/{repo}/pulls/{base}/{head}` to get a PR by its base and head branch.
1 parent f38888b commit 6595241

File tree

5 files changed

+186
-0
lines changed

5 files changed

+186
-0
lines changed

models/issues/pull.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,35 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest,
652652
return pr, pr.LoadAttributes(ctx)
653653
}
654654

655+
// GetPullRequestsByBaseHeadInfo returns the pull request by given base and head
656+
func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) {
657+
pr := &PullRequest{}
658+
sess := db.GetEngine(ctx).
659+
Join("INNER", "issue", "issue.id = pull_request.issue_id").
660+
Where("base_repo_id = ? AND base_branch = ? AND head_repo_id = ? AND head_branch = ?", baseID, base, headID, head)
661+
has, err := sess.Get(pr)
662+
if err != nil {
663+
return nil, err
664+
}
665+
if !has {
666+
return nil, ErrPullRequestNotExist{
667+
HeadRepoID: headID,
668+
BaseRepoID: baseID,
669+
HeadBranch: head,
670+
BaseBranch: base,
671+
}
672+
}
673+
674+
if err = pr.LoadAttributes(ctx); err != nil {
675+
return nil, err
676+
}
677+
if err = pr.LoadIssue(ctx); err != nil {
678+
return nil, err
679+
}
680+
681+
return pr, nil
682+
}
683+
655684
// GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
656685
// By poster id.
657686
func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) {

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,7 @@ func Routes() *web.Route {
12251225
Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
12261226
Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
12271227
})
1228+
m.Get("/{base}/*", repo.GetPullRequestByBaseHead)
12281229
}, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
12291230
m.Group("/statuses", func() {
12301231
m.Combo("/{sha}").Get(repo.GetCommitStatuses).

routers/api/v1/repo/pull.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,91 @@ func GetPullRequest(ctx *context.APIContext) {
187187
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
188188
}
189189

190+
// GetPullRequest returns a single PR based on index
191+
func GetPullRequestByBaseHead(ctx *context.APIContext) {
192+
// swagger:operation GET /repos/{owner}/{repo}/pulls/{base}/{head} repository repoGetPullRequestByBaseHead
193+
// ---
194+
// summary: Get a pull request by base and head
195+
// produces:
196+
// - application/json
197+
// parameters:
198+
// - name: owner
199+
// in: path
200+
// description: owner of the repo
201+
// type: string
202+
// required: true
203+
// - name: repo
204+
// in: path
205+
// description: name of the repo
206+
// type: string
207+
// required: true
208+
// - name: base
209+
// in: path
210+
// description: base of the pull request to get
211+
// type: string
212+
// required: true
213+
// - name: head
214+
// in: path
215+
// description: head of the pull request to get
216+
// type: string
217+
// required: true
218+
// responses:
219+
// "200":
220+
// "$ref": "#/responses/PullRequest"
221+
// "404":
222+
// "$ref": "#/responses/notFound"
223+
224+
var headRepoID int64
225+
var headBranch string
226+
head := ctx.Params("*")
227+
if strings.Contains(head, ":") {
228+
split := strings.SplitN(head, ":", 2)
229+
headBranch = split[1]
230+
var owner, name string
231+
if strings.Contains(split[0], "/") {
232+
split = strings.Split(split[0], "/")
233+
owner = split[0]
234+
name = split[1]
235+
} else {
236+
owner = split[0]
237+
name = ctx.Repo.Repository.Name
238+
}
239+
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name)
240+
if err != nil {
241+
if repo_model.IsErrRepoNotExist(err) {
242+
ctx.NotFound()
243+
} else {
244+
ctx.Error(http.StatusInternalServerError, "GetRepositoryByOwnerName", err)
245+
}
246+
return
247+
}
248+
headRepoID = repo.ID
249+
} else {
250+
headRepoID = ctx.Repo.Repository.ID
251+
headBranch = head
252+
}
253+
254+
pr, err := issues_model.GetPullRequestByBaseHeadInfo(ctx, ctx.Repo.Repository.ID, headRepoID, ctx.Params(":base"), headBranch)
255+
if err != nil {
256+
if issues_model.IsErrPullRequestNotExist(err) {
257+
ctx.NotFound()
258+
} else {
259+
ctx.Error(http.StatusInternalServerError, "GetPullRequestByBaseHeadInfo", err)
260+
}
261+
return
262+
}
263+
264+
if err = pr.LoadBaseRepo(ctx); err != nil {
265+
ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
266+
return
267+
}
268+
if err = pr.LoadHeadRepo(ctx); err != nil {
269+
ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
270+
return
271+
}
272+
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
273+
}
274+
190275
// DownloadPullDiffOrPatch render a pull's raw diff or patch
191276
func DownloadPullDiffOrPatch(ctx *context.APIContext) {
192277
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.{diffType} repository repoDownloadPullDiffOrPatch

templates/swagger/v1_json.tmpl

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/api_pull_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,27 @@ func TestAPIViewPulls(t *testing.T) {
6161
}
6262
}
6363

64+
func TestAPIViewPullsByBaseHead(t *testing.T) {
65+
defer tests.PrepareTestEnv(t)()
66+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
67+
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
68+
69+
ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository)
70+
71+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name).
72+
AddTokenAuth(ctx.Token)
73+
resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
74+
75+
pull := &api.PullRequest{}
76+
DecodeJSON(t, resp, pull)
77+
assert.EqualValues(t, 3, pull.Index)
78+
assert.EqualValues(t, 2, pull.ID)
79+
80+
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name).
81+
AddTokenAuth(ctx.Token)
82+
ctx.Session.MakeRequest(t, req, http.StatusNotFound)
83+
}
84+
6485
// TestAPIMergePullWIP ensures that we can't merge a WIP pull request
6586
func TestAPIMergePullWIP(t *testing.T) {
6687
defer tests.PrepareTestEnv(t)()

0 commit comments

Comments
 (0)