Skip to content

Commit c84a5b5

Browse files
committed
fix: make until parameter inclusive by appending 23:59:59
The until parameter was exclusive, causing commits on the end date to be excluded from results. This fix appends 23:59:59 to the until date to make it inclusive, matching user expectations. Affected tools: - get_commit_stats - get_author_metrics - get_team_summary - get_commit_patterns BREAKING CHANGE: Date range behavior now includes commits on the until date. This aligns with user expectations but may change result counts for existing queries that relied on the exclusive behavior. Fixes: Date range boundary handling Tests: Added 16 comprehensive date range tests Docs: Updated README with date range behavior note
1 parent 5f80518 commit c84a5b5

File tree

3 files changed

+261
-4
lines changed

3 files changed

+261
-4
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,16 @@ Get team summary for /home/user/project from 2025-10-01 to 2025-10-31
182182

183183
## Available Tools
184184

185+
> **Note on Date Ranges**: The `until` parameter is **inclusive** - commits on the end date are included in results. For example, `since="2025-11-01" until="2025-11-30"` includes all commits from November 1st through November 30th.
186+
185187
### get_commit_stats
186188

187189
Get overall commit statistics for a time period.
188190

189191
**Parameters:**
190192
- `repo_path` (required): Path to git repository
191193
- `since` (required): Start date (YYYY-MM-DD)
192-
- `until` (optional): End date (YYYY-MM-DD)
194+
- `until` (optional): End date (YYYY-MM-DD), inclusive
193195
- `author` (optional): Filter by author
194196

195197
**Returns:**

src/date-range.test.ts

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2+
import { execSync } from 'child_process';
3+
import { mkdirSync, writeFileSync, rmSync } from 'fs';
4+
import { join } from 'path';
5+
import { tmpdir } from 'os';
6+
import * as handlers from './handlers.js';
7+
8+
describe('Date Range Handling QA', () => {
9+
let testRepo: string;
10+
11+
beforeAll(() => {
12+
testRepo = join(tmpdir(), `date-range-test-${Date.now()}`);
13+
mkdirSync(testRepo, { recursive: true });
14+
15+
execSync('git init', { cwd: testRepo });
16+
execSync('git config user.email "test@example.com"', { cwd: testRepo });
17+
execSync('git config user.name "Test User"', { cwd: testRepo });
18+
19+
// Create commits on specific dates
20+
const dates = [
21+
'2025-01-15',
22+
'2025-02-10',
23+
'2025-03-05',
24+
'2025-04-20',
25+
'2025-05-25',
26+
];
27+
28+
dates.forEach((date, i) => {
29+
writeFileSync(join(testRepo, `file${i}.txt`), `content ${i}\n`);
30+
execSync('git add .', { cwd: testRepo });
31+
execSync(`GIT_COMMITTER_DATE="${date} 12:00:00" git commit --date="${date} 12:00:00" -m "Commit ${i}"`, {
32+
cwd: testRepo,
33+
env: { ...process.env, GIT_COMMITTER_DATE: `${date} 12:00:00` }
34+
});
35+
});
36+
});
37+
38+
afterAll(() => {
39+
if (testRepo) {
40+
rmSync(testRepo, { recursive: true, force: true });
41+
}
42+
});
43+
44+
describe('get_commit_stats', () => {
45+
it('should respect since parameter', () => {
46+
const result = handlers.handleGetCommitStats({
47+
repo_path: testRepo,
48+
since: '2025-03-01',
49+
});
50+
51+
// Should get commits from March onwards (3 commits: Mar, Apr, May)
52+
expect(result.commits).toBe(3);
53+
});
54+
55+
it('should respect since and until parameters', () => {
56+
const result = handlers.handleGetCommitStats({
57+
repo_path: testRepo,
58+
since: '2025-02-01',
59+
until: '2025-04-01',
60+
});
61+
62+
// Should get commits from Feb and Mar only (2 commits)
63+
expect(result.commits).toBe(2);
64+
});
65+
66+
it('should handle exact date boundaries', () => {
67+
const result = handlers.handleGetCommitStats({
68+
repo_path: testRepo,
69+
since: '2025-02-10',
70+
until: '2025-02-10',
71+
});
72+
73+
// Should include the commit on 2025-02-10
74+
expect(result.commits).toBeGreaterThanOrEqual(1);
75+
});
76+
77+
it('should return zero commits when range has no commits', () => {
78+
const result = handlers.handleGetCommitStats({
79+
repo_path: testRepo,
80+
since: '2025-06-01',
81+
until: '2025-06-30',
82+
});
83+
84+
expect(result.commits).toBe(0);
85+
});
86+
});
87+
88+
describe('get_author_metrics', () => {
89+
it('should respect since parameter', () => {
90+
const result = handlers.handleGetAuthorMetrics({
91+
repo_path: testRepo,
92+
since: '2025-03-01',
93+
});
94+
95+
const authors = Object.keys(result);
96+
expect(authors.length).toBeGreaterThan(0);
97+
const totalCommits = Object.values(result).reduce((sum: number, author: any) => sum + author.commits, 0);
98+
expect(totalCommits).toBe(3);
99+
});
100+
101+
it('should respect since and until parameters', () => {
102+
const result = handlers.handleGetAuthorMetrics({
103+
repo_path: testRepo,
104+
since: '2025-02-01',
105+
until: '2025-04-01',
106+
});
107+
108+
const totalCommits = Object.values(result).reduce((sum: number, author: any) => sum + author.commits, 0);
109+
expect(totalCommits).toBe(2);
110+
});
111+
});
112+
113+
describe('get_team_summary', () => {
114+
it('should respect since parameter', () => {
115+
const result = handlers.handleGetTeamSummary({
116+
repo_path: testRepo,
117+
since: '2025-03-01',
118+
});
119+
120+
expect(result.period.since).toBe('2025-03-01');
121+
expect(result.period.until).toBe('now');
122+
expect(result.team.totalCommits).toBe(3);
123+
});
124+
125+
it('should respect since and until parameters', () => {
126+
const result = handlers.handleGetTeamSummary({
127+
repo_path: testRepo,
128+
since: '2025-02-01',
129+
until: '2025-04-01',
130+
});
131+
132+
expect(result.period.since).toBe('2025-02-01');
133+
expect(result.period.until).toBe('2025-04-01');
134+
expect(result.team.totalCommits).toBe(2);
135+
});
136+
});
137+
138+
describe('get_commit_patterns', () => {
139+
it('should respect since parameter', () => {
140+
const result = handlers.handleGetCommitPatterns({
141+
repo_path: testRepo,
142+
since: '2025-03-01',
143+
});
144+
145+
expect(result).toHaveProperty('byDay');
146+
expect(result).toHaveProperty('byHour');
147+
148+
// Count total commits from patterns
149+
const totalCommits = Object.values(result.byDay).reduce((sum: number, count: any) => sum + count, 0);
150+
expect(totalCommits).toBe(3);
151+
});
152+
153+
it('should respect since and until parameters', () => {
154+
const result = handlers.handleGetCommitPatterns({
155+
repo_path: testRepo,
156+
since: '2025-02-01',
157+
until: '2025-04-01',
158+
});
159+
160+
const totalCommits = Object.values(result.byDay).reduce((sum: number, count: any) => sum + count, 0);
161+
expect(totalCommits).toBe(2);
162+
});
163+
});
164+
165+
describe('get_file_churn', () => {
166+
it('should respect since parameter', () => {
167+
const result = handlers.handleGetFileChurn({
168+
repo_path: testRepo,
169+
since: '2025-03-01',
170+
});
171+
172+
// Should have 3 files (from 3 commits)
173+
expect(result.length).toBe(3);
174+
});
175+
176+
it('should not have until parameter but should work with since', () => {
177+
const result = handlers.handleGetFileChurn({
178+
repo_path: testRepo,
179+
since: '2025-01-01',
180+
});
181+
182+
// Should have all 5 files
183+
expect(result.length).toBe(5);
184+
});
185+
});
186+
187+
describe('Edge Cases', () => {
188+
it('should handle future dates gracefully', () => {
189+
const result = handlers.handleGetCommitStats({
190+
repo_path: testRepo,
191+
since: '2026-01-01',
192+
});
193+
194+
expect(result.commits).toBe(0);
195+
});
196+
197+
it('should handle very old dates', () => {
198+
const result = handlers.handleGetCommitStats({
199+
repo_path: testRepo,
200+
since: '2020-01-01',
201+
});
202+
203+
expect(result.commits).toBe(5);
204+
});
205+
206+
it('should handle until before since (should return 0)', () => {
207+
const result = handlers.handleGetCommitStats({
208+
repo_path: testRepo,
209+
since: '2025-05-01',
210+
until: '2025-01-01',
211+
});
212+
213+
expect(result.commits).toBe(0);
214+
});
215+
});
216+
217+
describe('Consistency Check', () => {
218+
it('should return consistent results across different tools', () => {
219+
const commitStats = handlers.handleGetCommitStats({
220+
repo_path: testRepo,
221+
since: '2025-02-01',
222+
until: '2025-04-01',
223+
});
224+
225+
const authorMetrics = handlers.handleGetAuthorMetrics({
226+
repo_path: testRepo,
227+
since: '2025-02-01',
228+
until: '2025-04-01',
229+
});
230+
231+
const teamSummary = handlers.handleGetTeamSummary({
232+
repo_path: testRepo,
233+
since: '2025-02-01',
234+
until: '2025-04-01',
235+
});
236+
237+
const commitPatterns = handlers.handleGetCommitPatterns({
238+
repo_path: testRepo,
239+
since: '2025-02-01',
240+
until: '2025-04-01',
241+
});
242+
243+
// All should report 2 commits
244+
expect(commitStats.commits).toBe(2);
245+
246+
const authorCommits = Object.values(authorMetrics).reduce((sum: number, author: any) => sum + author.commits, 0);
247+
expect(authorCommits).toBe(2);
248+
249+
expect(teamSummary.team.totalCommits).toBe(2);
250+
251+
const patternCommits = Object.values(commitPatterns.byDay).reduce((sum: number, count: any) => sum + count, 0);
252+
expect(patternCommits).toBe(2);
253+
});
254+
});
255+
});

src/handlers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function handleGetCommitStats(args: any) {
88
if (until) validateDate(until, "until");
99

1010
let cmd = `git log --since="${since}"`;
11-
if (until) cmd += ` --until="${until}"`;
11+
if (until) cmd += ` --until="${until} 23:59:59"`;
1212
if (author) cmd += ` --author="${author}"`;
1313
cmd += ` --pretty=format:"%H|%an|%ae|%ad|%s" --date=short --numstat`;
1414

@@ -44,7 +44,7 @@ export function handleGetAuthorMetrics(args: any) {
4444
if (until) validateDate(until, "until");
4545

4646
let cmd = `git log --since="${since}"`;
47-
if (until) cmd += ` --until="${until}"`;
47+
if (until) cmd += ` --until="${until} 23:59:59"`;
4848
cmd += ` --pretty=format:"%an <%ae>" --numstat`;
4949

5050
const output = runGitCommand(repo_path, cmd);
@@ -121,7 +121,7 @@ export function handleGetCommitPatterns(args: any) {
121121
if (until) validateDate(until, "until");
122122

123123
let cmd = `git log --since="${since}"`;
124-
if (until) cmd += ` --until="${until}"`;
124+
if (until) cmd += ` --until="${until} 23:59:59"`;
125125
cmd += ` --pretty=format:"%ad" --date=format:"%u %H"`;
126126

127127
const output = runGitCommand(repo_path, cmd);

0 commit comments

Comments
 (0)