Skip to content

Commit 1dfa28f

Browse files
qwerty287anbraten6543wxiaoguang
authored
Add API endpoint to get changed files of a PR (#21177)
This adds an api endpoint `/files` to PRs that allows to get a list of changed files. built upon #18228, reviews there are included closes #654 Co-authored-by: Anton Bracke <[email protected]> Co-authored-by: 6543 <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 78c15da commit 1dfa28f

File tree

8 files changed

+407
-6
lines changed

8 files changed

+407
-6
lines changed

models/fixtures/pull_request.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@
6060
head_repo_id: 1
6161
base_repo_id: 1
6262
head_branch: pr-to-update
63-
base_branch: branch1
64-
merge_base: 1234567890abcdef
63+
base_branch: branch2
64+
merge_base: 985f0301dba5e7b34be866819cd15ad3d8f508ee
6565
has_merged: false
6666

6767
-

modules/convert/convert.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"code.gitea.io/gitea/modules/log"
2828
api "code.gitea.io/gitea/modules/structs"
2929
"code.gitea.io/gitea/modules/util"
30+
"code.gitea.io/gitea/services/gitdiff"
3031
webhook_service "code.gitea.io/gitea/services/webhook"
3132
)
3233

@@ -414,3 +415,36 @@ func ToLFSLock(l *git_model.LFSLock) *api.LFSLock {
414415
},
415416
}
416417
}
418+
419+
// ToChangedFile convert a gitdiff.DiffFile to api.ChangedFile
420+
func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit string) *api.ChangedFile {
421+
status := "changed"
422+
if f.IsDeleted {
423+
status = "deleted"
424+
} else if f.IsCreated {
425+
status = "added"
426+
} else if f.IsRenamed && f.Type == gitdiff.DiffFileCopy {
427+
status = "copied"
428+
} else if f.IsRenamed && f.Type == gitdiff.DiffFileRename {
429+
status = "renamed"
430+
} else if f.Addition == 0 && f.Deletion == 0 {
431+
status = "unchanged"
432+
}
433+
434+
file := &api.ChangedFile{
435+
Filename: f.GetDiffFileName(),
436+
Status: status,
437+
Additions: f.Addition,
438+
Deletions: f.Deletion,
439+
Changes: f.Addition + f.Deletion,
440+
HTMLURL: fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
441+
ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit),
442+
RawURL: fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
443+
}
444+
445+
if status == "rename" {
446+
file.PreviousFilename = f.OldName
447+
}
448+
449+
return file
450+
}

modules/structs/pull.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,16 @@ type EditPullRequestOption struct {
9595
RemoveDeadline *bool `json:"unset_due_date"`
9696
AllowMaintainerEdit *bool `json:"allow_maintainer_edit"`
9797
}
98+
99+
// ChangedFile store information about files affected by the pull request
100+
type ChangedFile struct {
101+
Filename string `json:"filename"`
102+
PreviousFilename string `json:"previous_filename,omitempty"`
103+
Status string `json:"status"`
104+
Additions int `json:"additions"`
105+
Deletions int `json:"deletions"`
106+
Changes int `json:"changes"`
107+
HTMLURL string `json:"html_url,omitempty"`
108+
ContentsURL string `json:"contents_url,omitempty"`
109+
RawURL string `json:"raw_url,omitempty"`
110+
}

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,7 @@ func Routes(ctx gocontext.Context) *web.Route {
10021002
m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch)
10031003
m.Post("/update", reqToken(), repo.UpdatePullRequest)
10041004
m.Get("/commits", repo.GetPullRequestCommits)
1005+
m.Get("/files", repo.GetPullRequestFiles)
10051006
m.Combo("/merge").Get(repo.IsPullRequestMerged).
10061007
Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest).
10071008
Delete(reqToken(), mustNotBeArchived, repo.CancelScheduledAutoMerge)

routers/api/v1/repo/pull.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ import (
2626
"code.gitea.io/gitea/modules/git"
2727
"code.gitea.io/gitea/modules/log"
2828
"code.gitea.io/gitea/modules/notification"
29+
"code.gitea.io/gitea/modules/setting"
2930
api "code.gitea.io/gitea/modules/structs"
3031
"code.gitea.io/gitea/modules/timeutil"
3132
"code.gitea.io/gitea/modules/web"
3233
"code.gitea.io/gitea/routers/api/v1/utils"
3334
asymkey_service "code.gitea.io/gitea/services/asymkey"
3435
"code.gitea.io/gitea/services/automerge"
3536
"code.gitea.io/gitea/services/forms"
37+
"code.gitea.io/gitea/services/gitdiff"
3638
issue_service "code.gitea.io/gitea/services/issue"
3739
pull_service "code.gitea.io/gitea/services/pull"
3840
repo_service "code.gitea.io/gitea/services/repository"
@@ -1323,3 +1325,137 @@ func GetPullRequestCommits(ctx *context.APIContext) {
13231325

13241326
ctx.JSON(http.StatusOK, &apiCommits)
13251327
}
1328+
1329+
// GetPullRequestFiles gets all changed files associated with a given PR
1330+
func GetPullRequestFiles(ctx *context.APIContext) {
1331+
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/files repository repoGetPullRequestFiles
1332+
// ---
1333+
// summary: Get changed files for a pull request
1334+
// produces:
1335+
// - application/json
1336+
// parameters:
1337+
// - name: owner
1338+
// in: path
1339+
// description: owner of the repo
1340+
// type: string
1341+
// required: true
1342+
// - name: repo
1343+
// in: path
1344+
// description: name of the repo
1345+
// type: string
1346+
// required: true
1347+
// - name: index
1348+
// in: path
1349+
// description: index of the pull request to get
1350+
// type: integer
1351+
// format: int64
1352+
// required: true
1353+
// - name: skip-to
1354+
// in: query
1355+
// description: skip to given file
1356+
// type: string
1357+
// - name: whitespace
1358+
// in: query
1359+
// description: whitespace behavior
1360+
// type: string
1361+
// enum: [ignore-all, ignore-change, ignore-eol, show-all]
1362+
// - name: page
1363+
// in: query
1364+
// description: page number of results to return (1-based)
1365+
// type: integer
1366+
// - name: limit
1367+
// in: query
1368+
// description: page size of results
1369+
// type: integer
1370+
// responses:
1371+
// "200":
1372+
// "$ref": "#/responses/ChangedFileList"
1373+
// "404":
1374+
// "$ref": "#/responses/notFound"
1375+
1376+
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
1377+
if err != nil {
1378+
if issues_model.IsErrPullRequestNotExist(err) {
1379+
ctx.NotFound()
1380+
} else {
1381+
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
1382+
}
1383+
return
1384+
}
1385+
1386+
if err := pr.LoadBaseRepo(); err != nil {
1387+
ctx.InternalServerError(err)
1388+
return
1389+
}
1390+
1391+
if err := pr.LoadHeadRepo(); err != nil {
1392+
ctx.InternalServerError(err)
1393+
return
1394+
}
1395+
1396+
baseGitRepo := ctx.Repo.GitRepo
1397+
1398+
var prInfo *git.CompareInfo
1399+
if pr.HasMerged {
1400+
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName(), true, false)
1401+
} else {
1402+
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName(), true, false)
1403+
}
1404+
if err != nil {
1405+
ctx.ServerError("GetCompareInfo", err)
1406+
return
1407+
}
1408+
1409+
headCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
1410+
if err != nil {
1411+
ctx.ServerError("GetRefCommitID", err)
1412+
return
1413+
}
1414+
1415+
startCommitID := prInfo.MergeBase
1416+
endCommitID := headCommitID
1417+
1418+
maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
1419+
1420+
diff, err := gitdiff.GetDiff(baseGitRepo,
1421+
&gitdiff.DiffOptions{
1422+
BeforeCommitID: startCommitID,
1423+
AfterCommitID: endCommitID,
1424+
SkipTo: ctx.FormString("skip-to"),
1425+
MaxLines: maxLines,
1426+
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
1427+
MaxFiles: maxFiles,
1428+
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
1429+
})
1430+
if err != nil {
1431+
ctx.ServerError("GetDiff", err)
1432+
return
1433+
}
1434+
1435+
listOptions := utils.GetListOptions(ctx)
1436+
1437+
totalNumberOfFiles := diff.NumFiles
1438+
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
1439+
1440+
start, end := listOptions.GetStartEnd()
1441+
1442+
if end > totalNumberOfFiles {
1443+
end = totalNumberOfFiles
1444+
}
1445+
1446+
apiFiles := make([]*api.ChangedFile, 0, end-start)
1447+
for i := start; i < end; i++ {
1448+
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
1449+
}
1450+
1451+
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
1452+
ctx.SetTotalCountHeader(int64(totalNumberOfFiles))
1453+
1454+
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
1455+
ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
1456+
ctx.RespHeader().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
1457+
ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
1458+
ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore")
1459+
1460+
ctx.JSON(http.StatusOK, &apiFiles)
1461+
}

routers/api/v1/swagger/repo.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,28 @@ type swaggerCommitList struct {
254254
Body []api.Commit `json:"body"`
255255
}
256256

257+
// ChangedFileList
258+
// swagger:response ChangedFileList
259+
type swaggerChangedFileList struct {
260+
// The current page
261+
Page int `json:"X-Page"`
262+
263+
// Commits per page
264+
PerPage int `json:"X-PerPage"`
265+
266+
// Total commit count
267+
Total int `json:"X-Total"`
268+
269+
// Total number of pages
270+
PageCount int `json:"X-PageCount"`
271+
272+
// True if there is another page
273+
HasMore bool `json:"X-HasMore"`
274+
275+
// in: body
276+
Body []api.ChangedFile `json:"body"`
277+
}
278+
257279
// Note
258280
// swagger:response Note
259281
type swaggerNote struct {

0 commit comments

Comments
 (0)