From 4812e354866a066dcb899af667b0fad5fa094065 Mon Sep 17 00:00:00 2001 From: Augusto Xavier Date: Sun, 28 Jun 2026 16:58:25 -0300 Subject: [PATCH] fix(api): respect since/until when counting commits for X-Total-Count (#38204) 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. --- modules/gitrepo/commit.go | 8 ++++++++ modules/gitrepo/commit_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/modules/gitrepo/commit.go b/modules/gitrepo/commit.go index 38785dcb978..24df90c015c 100644 --- a/modules/gitrepo/commit.go +++ b/modules/gitrepo/commit.go @@ -32,6 +32,14 @@ func CommitsCount(ctx context.Context, repo Repository, opts CommitsCountOptions 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...) } diff --git a/modules/gitrepo/commit_test.go b/modules/gitrepo/commit_test.go index 05cedc39efd..638795a9855 100644 --- a/modules/gitrepo/commit_test.go +++ b/modules/gitrepo/commit_test.go @@ -34,6 +34,37 @@ func TestCommitsCountWithoutBase(t *testing.T) { assert.Equal(t, int64(2), commitsCount) } +func TestCommitsCountWithSinceUntil(t *testing.T) { + bareRepo1 := &mockRepository{path: "repo1_bare"} + revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"} + + // The three commits on this revision are dated 2018-04-18, 2017-12-19 and 2017-12-19. + cases := []struct { + name string + since string + until string + expected int64 + }{ + {name: "no filter", expected: 3}, + {name: "since keeps newer commits", since: "2018-01-01", expected: 1}, + {name: "until keeps older commits", until: "2018-01-01", expected: 2}, + {name: "since and until bound the range", since: "2017-12-19T22:16:00-08:00", until: "2018-01-01", expected: 1}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + commitsCount, err := CommitsCount(t.Context(), bareRepo1, + CommitsCountOptions{ + Revision: revision, + Since: tc.since, + Until: tc.until, + }) + + assert.NoError(t, err) + assert.Equal(t, tc.expected, commitsCount) + }) + } +} + func TestGetLatestCommitTime(t *testing.T) { bareRepo1 := &mockRepository{path: "repo1_bare"} lct, err := GetLatestCommitTime(t.Context(), bareRepo1)