mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	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 https://github.com/go-gitea/gitea/issues/16206 and closes https://github.com/go-gitea/gitea/issues/16703
			
			
This commit is contained in:
		| @@ -132,3 +132,21 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { | ||||
| 		resp.Body.String()) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGetFileHistory(t *testing.T) { | ||||
| 	defer prepareTestEnv(t)() | ||||
| 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) | ||||
| 	// Login as User2. | ||||
| 	session := loginUser(t, user.Name) | ||||
| 	token := getTokenForLoggedInUser(t, session) | ||||
|  | ||||
| 	req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?path=readme.md&token="+token+"&sha=good-sign", user.Name) | ||||
| 	resp := session.MakeRequest(t, req, http.StatusOK) | ||||
|  | ||||
| 	var apiData []api.Commit | ||||
| 	DecodeJSON(t, resp, &apiData) | ||||
|  | ||||
| 	assert.Len(t, apiData, 1) | ||||
| 	assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) | ||||
| 	compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) | ||||
| } | ||||
|   | ||||
| @@ -108,13 +108,17 @@ func GetAllCommits(ctx *context.APIContext) { | ||||
| 	//   in: query | ||||
| 	//   description: SHA or branch to start listing commits from (usually 'master') | ||||
| 	//   type: string | ||||
| 	// - name: path | ||||
| 	//   in: query | ||||
| 	//   description: filepath of a file/dir | ||||
| 	//   type: string | ||||
| 	// - name: page | ||||
| 	//   in: query | ||||
| 	//   description: page number of results to return (1-based) | ||||
| 	//   type: integer | ||||
| 	// - name: limit | ||||
| 	//   in: query | ||||
| 	//   description: page size of results | ||||
| 	//   description: page size of results (ignored if used with 'path') | ||||
| 	//   type: integer | ||||
| 	// responses: | ||||
| 	//   "200": | ||||
| @@ -149,46 +153,73 @@ func GetAllCommits(ctx *context.APIContext) { | ||||
| 	} | ||||
|  | ||||
| 	sha := ctx.FormString("sha") | ||||
| 	path := ctx.FormString("path") | ||||
|  | ||||
| 	var baseCommit *git.Commit | ||||
| 	if len(sha) == 0 { | ||||
| 		// no sha supplied - use default branch | ||||
| 		head, err := gitRepo.GetHEADBranch() | ||||
| 	var ( | ||||
| 		commitsCountTotal int64 | ||||
| 		commits           []*git.Commit | ||||
| 	) | ||||
|  | ||||
| 	if len(path) == 0 { | ||||
| 		var baseCommit *git.Commit | ||||
| 		if len(sha) == 0 { | ||||
| 			// no sha supplied - use default branch | ||||
| 			head, err := gitRepo.GetHEADBranch() | ||||
| 			if err != nil { | ||||
| 				ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			baseCommit, err = gitRepo.GetBranchCommit(head.Name) | ||||
| 			if err != nil { | ||||
| 				ctx.Error(http.StatusInternalServerError, "GetCommit", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			// get commit specified by sha | ||||
| 			baseCommit, err = gitRepo.GetCommit(sha) | ||||
| 			if err != nil { | ||||
| 				ctx.Error(http.StatusInternalServerError, "GetCommit", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Total commit count | ||||
| 		commitsCountTotal, err = baseCommit.CommitsCount() | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err) | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		baseCommit, err = gitRepo.GetBranchCommit(head.Name) | ||||
| 		// Query commits | ||||
| 		commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetCommit", err) | ||||
| 			ctx.Error(http.StatusInternalServerError, "CommitsByRange", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		// get commit specified by sha | ||||
| 		baseCommit, err = gitRepo.GetCommit(sha) | ||||
| 		if len(sha) == 0 { | ||||
| 			sha = ctx.Repo.Repository.DefaultBranch | ||||
| 		} | ||||
|  | ||||
| 		commitsCountTotal, err = gitRepo.FileCommitsCount(sha, path) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetCommit", err) | ||||
| 			ctx.Error(http.StatusInternalServerError, "FileCommitsCount", err) | ||||
| 			return | ||||
| 		} else if commitsCountTotal == 0 { | ||||
| 			ctx.NotFound("FileCommitsCount", nil) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		commits, err = gitRepo.CommitsByFileAndRange(sha, path, listOptions.Page) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Total commit count | ||||
| 	commitsCountTotal, err := baseCommit.CommitsCount() | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize))) | ||||
|  | ||||
| 	// Query commits | ||||
| 	commits, err := baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "CommitsByRange", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	userCache := make(map[string]*user_model.User) | ||||
|  | ||||
| 	apiCommits := make([]*api.Commit, len(commits)) | ||||
|   | ||||
| @@ -2950,6 +2950,12 @@ | ||||
|             "name": "sha", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "filepath of a file/dir", | ||||
|             "name": "path", | ||||
|             "in": "query" | ||||
|           }, | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "description": "page number of results to return (1-based)", | ||||
| @@ -2958,7 +2964,7 @@ | ||||
|           }, | ||||
|           { | ||||
|             "type": "integer", | ||||
|             "description": "page size of results", | ||||
|             "description": "page size of results (ignored if used with 'path')", | ||||
|             "name": "limit", | ||||
|             "in": "query" | ||||
|           } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user