Skip to content

Commit a9ed1c5

Browse files
authored
Add API to get file commit history (#17652)
Adds an API endpoint `api/v1/repos/{owner}/{repo}/git/history/{filepath}` to get the commits affecting the given file or directory. Closes #16206 and closes #16703
1 parent d155ffc commit a9ed1c5

File tree

3 files changed

+80
-25
lines changed

3 files changed

+80
-25
lines changed

integrations/api_repo_git_commits_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,21 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) {
132132
resp.Body.String())
133133

134134
}
135+
136+
func TestGetFileHistory(t *testing.T) {
137+
defer prepareTestEnv(t)()
138+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
139+
// Login as User2.
140+
session := loginUser(t, user.Name)
141+
token := getTokenForLoggedInUser(t, session)
142+
143+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?path=readme.md&token="+token+"&sha=good-sign", user.Name)
144+
resp := session.MakeRequest(t, req, http.StatusOK)
145+
146+
var apiData []api.Commit
147+
DecodeJSON(t, resp, &apiData)
148+
149+
assert.Len(t, apiData, 1)
150+
assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA)
151+
compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files)
152+
}

routers/api/v1/repo/commits.go

+55-24
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,17 @@ func GetAllCommits(ctx *context.APIContext) {
108108
// in: query
109109
// description: SHA or branch to start listing commits from (usually 'master')
110110
// type: string
111+
// - name: path
112+
// in: query
113+
// description: filepath of a file/dir
114+
// type: string
111115
// - name: page
112116
// in: query
113117
// description: page number of results to return (1-based)
114118
// type: integer
115119
// - name: limit
116120
// in: query
117-
// description: page size of results
121+
// description: page size of results (ignored if used with 'path')
118122
// type: integer
119123
// responses:
120124
// "200":
@@ -149,46 +153,73 @@ func GetAllCommits(ctx *context.APIContext) {
149153
}
150154

151155
sha := ctx.FormString("sha")
156+
path := ctx.FormString("path")
157+
158+
var (
159+
commitsCountTotal int64
160+
commits []*git.Commit
161+
)
162+
163+
if len(path) == 0 {
164+
var baseCommit *git.Commit
165+
if len(sha) == 0 {
166+
// no sha supplied - use default branch
167+
head, err := gitRepo.GetHEADBranch()
168+
if err != nil {
169+
ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err)
170+
return
171+
}
172+
173+
baseCommit, err = gitRepo.GetBranchCommit(head.Name)
174+
if err != nil {
175+
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
176+
return
177+
}
178+
} else {
179+
// get commit specified by sha
180+
baseCommit, err = gitRepo.GetCommit(sha)
181+
if err != nil {
182+
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
183+
return
184+
}
185+
}
152186

153-
var baseCommit *git.Commit
154-
if len(sha) == 0 {
155-
// no sha supplied - use default branch
156-
head, err := gitRepo.GetHEADBranch()
187+
// Total commit count
188+
commitsCountTotal, err = baseCommit.CommitsCount()
157189
if err != nil {
158-
ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err)
190+
ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err)
159191
return
160192
}
161193

162-
baseCommit, err = gitRepo.GetBranchCommit(head.Name)
194+
// Query commits
195+
commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize)
163196
if err != nil {
164-
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
197+
ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
165198
return
166199
}
167200
} else {
168-
// get commit specified by sha
169-
baseCommit, err = gitRepo.GetCommit(sha)
201+
if len(sha) == 0 {
202+
sha = ctx.Repo.Repository.DefaultBranch
203+
}
204+
205+
commitsCountTotal, err = gitRepo.FileCommitsCount(sha, path)
170206
if err != nil {
171-
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
207+
ctx.Error(http.StatusInternalServerError, "FileCommitsCount", err)
208+
return
209+
} else if commitsCountTotal == 0 {
210+
ctx.NotFound("FileCommitsCount", nil)
172211
return
173212
}
174-
}
175213

176-
// Total commit count
177-
commitsCountTotal, err := baseCommit.CommitsCount()
178-
if err != nil {
179-
ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err)
180-
return
214+
commits, err = gitRepo.CommitsByFileAndRange(sha, path, listOptions.Page)
215+
if err != nil {
216+
ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err)
217+
return
218+
}
181219
}
182220

183221
pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize)))
184222

185-
// Query commits
186-
commits, err := baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize)
187-
if err != nil {
188-
ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
189-
return
190-
}
191-
192223
userCache := make(map[string]*user_model.User)
193224

194225
apiCommits := make([]*api.Commit, len(commits))

templates/swagger/v1_json.tmpl

+7-1
Original file line numberDiff line numberDiff line change
@@ -2950,6 +2950,12 @@
29502950
"name": "sha",
29512951
"in": "query"
29522952
},
2953+
{
2954+
"type": "string",
2955+
"description": "filepath of a file/dir",
2956+
"name": "path",
2957+
"in": "query"
2958+
},
29532959
{
29542960
"type": "integer",
29552961
"description": "page number of results to return (1-based)",
@@ -2958,7 +2964,7 @@
29582964
},
29592965
{
29602966
"type": "integer",
2961-
"description": "page size of results",
2967+
"description": "page size of results (ignored if used with 'path')",
29622968
"name": "limit",
29632969
"in": "query"
29642970
}

0 commit comments

Comments
 (0)