mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-29 22:31:28 +00:00
The repository commits API (`GET /repos/{owner}/{repo}/commits`) accepts
`since` and `until` query parameters and filters the returned page of
commits by commit date. However, the `X-Total-Count` and `X-Total`
response headers reported the *unfiltered* total number of commits, so
the advertised total could be far larger than the number of commits
actually returned for the requested date range. With a range that
matches no commits, the page is correctly empty while the headers still
claim the full repository total.
## Root cause
`gitrepo.CommitsCount` declared `Since` and `Until` options and the API
handler populated them, but the function never appended
`--since`/`--until` to the underlying `git rev-list --count` invocation.
The date filters were silently dropped, so the count always reflected
the entire revision history.
## Fix
Pass the `Since`/`Until` options through to `git rev-list`, mirroring
the existing commit-listing path (`commitsByRangeWithTime`). The
reported total now matches the filtered range used to build the page.
## Testing
Added `TestCommitsCountWithSinceUntil` in
`modules/gitrepo/commit_test.go`, a table-driven unit test against the
`repo1_bare` fixture covering `since`, `until`, and a bounded
`since`+`until` range. It fails on the pre-fix code (every case returns
the full count of 3) and passes after the change. Existing
`CommitsCount` tests remain green.
## Notes
- No new settings, no default changes; this corrects an incorrect header
value and is backward compatible. Clients that depend on `since`/`until`
already filter the returned commits, and the headers now agree with that
filtering.
Fixes #35886.
---
*AI-assistance disclosure:* this change was developed with the
assistance of Claude Code (Claude Opus 4.8). I have reviewed and
understand the change and take responsibility for it.
104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package gitrepo
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.dev/modules/git"
|
|
"gitea.dev/modules/git/gitcmd"
|
|
)
|
|
|
|
// CommitsCountOptions the options when counting commits
|
|
type CommitsCountOptions struct {
|
|
Not string
|
|
Revision []string
|
|
RelPath []string
|
|
Since string
|
|
Until string
|
|
}
|
|
|
|
// CommitsCount returns number of total commits of until given revision.
|
|
func CommitsCount(ctx context.Context, repo Repository, opts CommitsCountOptions) (int64, error) {
|
|
cmd := gitcmd.NewCommand("rev-list", "--count")
|
|
|
|
cmd.AddDynamicArguments(opts.Revision...)
|
|
|
|
if opts.Not != "" {
|
|
cmd.AddOptionValues("--not", opts.Not)
|
|
}
|
|
|
|
if opts.Since != "" {
|
|
cmd.AddOptionFormat("--since=%s", opts.Since)
|
|
}
|
|
|
|
if opts.Until != "" {
|
|
cmd.AddOptionFormat("--until=%s", opts.Until)
|
|
}
|
|
|
|
if len(opts.RelPath) > 0 {
|
|
cmd.AddDashesAndList(opts.RelPath...)
|
|
}
|
|
|
|
stdout, _, err := cmd.WithDir(repoPath(repo)).RunStdString(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
|
|
}
|
|
|
|
// FileCommitsCount return the number of files at a revision
|
|
func FileCommitsCount(ctx context.Context, repo Repository, revision, file string) (int64, error) {
|
|
return CommitsCount(ctx, repo,
|
|
CommitsCountOptions{
|
|
Revision: []string{revision},
|
|
RelPath: []string{file},
|
|
})
|
|
}
|
|
|
|
// CommitsCountOfCommit returns number of total commits of until current revision.
|
|
func CommitsCountOfCommit(ctx context.Context, repo Repository, commitID string) (int64, error) {
|
|
return CommitsCount(ctx, repo, CommitsCountOptions{
|
|
Revision: []string{commitID},
|
|
})
|
|
}
|
|
|
|
// AllCommitsCount returns count of all commits in repository
|
|
func AllCommitsCount(ctx context.Context, repo Repository, hidePRRefs bool, files ...string) (int64, error) {
|
|
cmd := gitcmd.NewCommand("rev-list")
|
|
if hidePRRefs {
|
|
cmd.AddArguments("--exclude=" + git.PullPrefix + "*")
|
|
}
|
|
cmd.AddArguments("--all", "--count")
|
|
if len(files) > 0 {
|
|
cmd.AddDashesAndList(files...)
|
|
}
|
|
|
|
stdout, _, err := RunCmdString(ctx, repo, cmd)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
|
|
}
|
|
|
|
func GetFullCommitID(ctx context.Context, repo Repository, shortID string) (string, error) {
|
|
return git.GetFullCommitID(ctx, repoPath(repo), shortID)
|
|
}
|
|
|
|
// GetLatestCommitTime returns time for latest commit in repository (across all branches)
|
|
func GetLatestCommitTime(ctx context.Context, repo Repository) (time.Time, error) {
|
|
stdout, _, err := RunCmdString(ctx, repo,
|
|
gitcmd.NewCommand("for-each-ref", "--sort=-committerdate", git.BranchPrefix, "--count", "1", "--format=%(committerdate)"))
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
commitTime := strings.TrimSpace(stdout)
|
|
return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime)
|
|
}
|