aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBo-Yi Wu <appleboy.tw@gmail.com>2025-05-20 09:57:58 +0800
committerGitHub <noreply@github.com>2025-05-19 18:57:58 -0700
commitd06eb8d8014b1f179187926b97b92b6f42c65038 (patch)
tree8f11d351306e9e0cbd22fe51a0b2aae2fe7a239b
parent9cfcc079c7353d635e1e0d38006c3f925944e1b6 (diff)
downloadgitea-d06eb8d8014b1f179187926b97b92b6f42c65038.tar.gz
gitea-d06eb8d8014b1f179187926b97b92b6f42c65038.zip
feat(api): add date range filtering to commit retrieval endpoints (#34497)
- Add support for filtering commits by date range via new "since" and "until" parameters - Update API endpoints and command logic to handle the new parameters for fetching commits within given dates - Extend API documentation and Swagger specs to describe the new "since" and "until" query parameters - Refactor related function signatures and implementations to accept and pass "since" and "until" values --------- Signed-off-by: appleboy <appleboy.tw@gmail.com> Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
-rw-r--r--modules/git/commit.go6
-rw-r--r--modules/git/repo_commit.go17
-rw-r--r--modules/git/repo_stats.go7
-rw-r--r--modules/repository/commits_test.go2
-rw-r--r--routers/api/v1/repo/commits.go36
-rw-r--r--routers/web/feed/branch.go2
-rw-r--r--routers/web/repo/commit.go2
-rw-r--r--templates/swagger/v1_json.tmpl14
8 files changed, 76 insertions, 10 deletions
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 3e790e89d9..cd50c51151 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -166,6 +166,8 @@ type CommitsCountOptions struct {
Not string
Revision []string
RelPath []string
+ Since string
+ Until string
}
// CommitsCount returns number of total commits of until given revision.
@@ -199,8 +201,8 @@ func (c *Commit) CommitsCount() (int64, error) {
}
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
-func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) {
- return c.repo.commitsByRange(c.ID, page, pageSize, not)
+func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
+ return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
}
// CommitsBefore returns all the commits before current revision
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 72f35711f0..a44fd8c0e1 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -89,7 +89,8 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
return commits[0], nil
}
-func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) {
+// commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support
+func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) {
cmd := NewCommand("log").
AddOptionFormat("--skip=%d", (page-1)*pageSize).
AddOptionFormat("--max-count=%d", pageSize).
@@ -99,6 +100,12 @@ func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not stri
if not != "" {
cmd.AddOptionValues("--not", not)
}
+ if since != "" {
+ cmd.AddOptionFormat("--since=%s", since)
+ }
+ if until != "" {
+ cmd.AddOptionFormat("--until=%s", until)
+ }
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
@@ -212,6 +219,8 @@ type CommitsByFileAndRangeOptions struct {
File string
Not string
Page int
+ Since string
+ Until string
}
// CommitsByFileAndRange return the commits according revision file and the page
@@ -231,6 +240,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
+ if opts.Since != "" {
+ gitCmd.AddOptionFormat("--since=%s", opts.Since)
+ }
+ if opts.Until != "" {
+ gitCmd.AddOptionFormat("--until=%s", opts.Until)
+ }
gitCmd.AddDashesAndList(opts.File)
err := gitCmd.Run(repo.Ctx, &RunOpts{
diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go
index 76fe92bb34..8c6f31c38c 100644
--- a/modules/git/repo_stats.go
+++ b/modules/git/repo_stats.go
@@ -40,7 +40,9 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
since := fromTime.Format(time.RFC3339)
- stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
+ stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").
+ AddOptionFormat("--since=%s", since).
+ RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@@ -60,7 +62,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
_ = stdoutWriter.Close()
}()
- gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since)
+ gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").
+ AddOptionFormat("--since=%s", since)
if len(branch) == 0 {
gitCmd.AddArguments("--branches=*")
} else {
diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go
index 6e407015c2..030cd7714d 100644
--- a/modules/repository/commits_test.go
+++ b/modules/repository/commits_test.go
@@ -200,5 +200,3 @@ func TestListToPushCommits(t *testing.T) {
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
}
}
-
-// TODO TestPushUpdate
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index 20258064a0..6a93be624f 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -8,6 +8,7 @@ import (
"math"
"net/http"
"strconv"
+ "time"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
@@ -116,6 +117,16 @@ func GetAllCommits(ctx *context.APIContext) {
// in: query
// description: filepath of a file/dir
// type: string
+ // - name: since
+ // in: query
+ // description: Only commits after this date will be returned (ISO 8601 format)
+ // type: string
+ // format: date-time
+ // - name: until
+ // in: query
+ // description: Only commits before this date will be returned (ISO 8601 format)
+ // type: string
+ // format: date-time
// - name: stat
// in: query
// description: include diff stats for every commit (disable for speedup, default 'true')
@@ -148,6 +159,23 @@ func GetAllCommits(ctx *context.APIContext) {
// "409":
// "$ref": "#/responses/EmptyRepository"
+ since := ctx.FormString("since")
+ until := ctx.FormString("until")
+
+ // Validate since/until as ISO 8601 (RFC3339)
+ if since != "" {
+ if _, err := time.Parse(time.RFC3339, since); err != nil {
+ ctx.APIError(http.StatusUnprocessableEntity, "invalid 'since' format, expected ISO 8601 (RFC3339)")
+ return
+ }
+ }
+ if until != "" {
+ if _, err := time.Parse(time.RFC3339, until); err != nil {
+ ctx.APIError(http.StatusUnprocessableEntity, "invalid 'until' format, expected ISO 8601 (RFC3339)")
+ return
+ }
+ }
+
if ctx.Repo.Repository.IsEmpty {
ctx.JSON(http.StatusConflict, api.APIError{
Message: "Git Repository is empty.",
@@ -198,6 +226,8 @@ func GetAllCommits(ctx *context.APIContext) {
RepoPath: ctx.Repo.GitRepo.Path,
Not: not,
Revision: []string{baseCommit.ID.String()},
+ Since: since,
+ Until: until,
})
if err != nil {
ctx.APIErrorInternal(err)
@@ -205,7 +235,7 @@ func GetAllCommits(ctx *context.APIContext) {
}
// Query commits
- commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not)
+ commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not, since, until)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -221,6 +251,8 @@ func GetAllCommits(ctx *context.APIContext) {
Not: not,
Revision: []string{sha},
RelPath: []string{path},
+ Since: since,
+ Until: until,
})
if err != nil {
@@ -237,6 +269,8 @@ func GetAllCommits(ctx *context.APIContext) {
File: path,
Not: not,
Page: listOptions.Page,
+ Since: since,
+ Until: until,
})
if err != nil {
ctx.APIErrorInternal(err)
diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go
index 4a6fe9603d..094fd987ac 100644
--- a/routers/web/feed/branch.go
+++ b/routers/web/feed/branch.go
@@ -15,7 +15,7 @@ import (
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
- commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "")
+ commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "", "", "")
if err != nil {
ctx.ServerError("ShowBranchFeed", err)
return
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index ae5baa9c47..01a6cbc319 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -78,7 +78,7 @@ func Commits(ctx *context.Context) {
}
// Both `git log branchName` and `git log commitId` work.
- commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "")
+ commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "", "", "")
if err != nil {
ctx.ServerError("CommitsByRange", err)
return
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 3512b647c2..c97e525660 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -6605,6 +6605,20 @@
"in": "query"
},
{
+ "type": "string",
+ "format": "date-time",
+ "description": "Only commits after this date will be returned (ISO 8601 format)",
+ "name": "since",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "format": "date-time",
+ "description": "Only commits before this date will be returned (ISO 8601 format)",
+ "name": "until",
+ "in": "query"
+ },
+ {
"type": "boolean",
"description": "include diff stats for every commit (disable for speedup, default 'true')",
"name": "stat",