Skip to content

Commit 13f18dd

Browse files
committed
feat: add --skip flag for CLI log pagination
Add skip parameter to engine LogParams, CLI (--skip N), and MCP (skip: N) so users can page through log results without needing continuation tokens. Composes naturally with --max-count: aifr log --oneline --max-count 20 aifr log --oneline --max-count 20 --skip 20 Update text/oneline continuation messages to suggest --skip as an alternative to continuation tokens, since CLI users have no way to pass tokens back. https://claude.ai/code/session_016QBk2VFvP92AXspnSePeLz
1 parent cf6f99e commit 13f18dd

File tree

6 files changed

+79
-3
lines changed

6 files changed

+79
-3
lines changed

cmd/aifr/cmd_log.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
var (
1313
logMaxCount int
14+
logSkip int
1415
logOneline bool
1516
logDivider string
1617
logVerbose bool
@@ -68,6 +69,7 @@ details (when they differ from the author) in JSON output.`,
6869

6970
resp, err := eng.Log(repoName, ref, engine.LogParams{
7071
MaxCount: logMaxCount,
72+
Skip: logSkip,
7173
Verbose: logVerbose,
7274
})
7375
if err != nil {
@@ -81,6 +83,7 @@ details (when they differ from the author) in JSON output.`,
8183

8284
func init() {
8385
logCmd.Flags().IntVar(&logMaxCount, "max-count", 20, "maximum commits to show")
86+
logCmd.Flags().IntVar(&logSkip, "skip", 0, "skip this many commits before showing results")
8487
logCmd.Flags().BoolVar(&logOneline, "oneline", false, "compact one-line-per-commit output")
8588
logCmd.Flags().StringVar(&logDivider, "divider", "plain", "divider format for text output: plain, xml")
8689
logCmd.Flags().BoolVar(&logVerbose, "verbose", false, "include tree hash, parent hashes, committer details")

internal/engine/gitops.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ func (e *Engine) Refs(repoName string, branches, tags, remotes bool) (*protocol.
272272
// LogParams controls git log queries.
273273
type LogParams struct {
274274
MaxCount int // 0 = default (20)
275+
Skip int // skip this many commits before collecting entries
275276
StartHash string // start from this commit's parent (for pagination)
276277
Verbose bool // include tree hash, parent hashes, committer details
277278
}
@@ -322,6 +323,19 @@ func (e *Engine) Log(repoName, ref string, params LogParams) (*protocol.LogRespo
322323
}
323324
}
324325

326+
// Skip commits if requested.
327+
for skipped := 0; skipped < params.Skip && current != nil; skipped++ {
328+
if current.NumParents() == 0 {
329+
current = nil
330+
break
331+
}
332+
current, err = current.Parent(0)
333+
if err != nil {
334+
current = nil
335+
break
336+
}
337+
}
338+
325339
resp := &protocol.LogResponse{
326340
Repo: repoName,
327341
Ref: ref,

internal/engine/pagination_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,60 @@ func TestLogCompleteField(t *testing.T) {
318318
}
319319
}
320320

321+
func TestLogSkip(t *testing.T) {
322+
dir := t.TempDir()
323+
initTestGitRepo(t, dir, 5)
324+
eng := newTestEngine(t, dir)
325+
326+
// Get all commits to know the expected order.
327+
all, err := eng.Log(dir, "HEAD", LogParams{MaxCount: 100})
328+
if err != nil {
329+
t.Fatal(err)
330+
}
331+
if len(all.Entries) != 5 {
332+
t.Fatalf("expected 5 commits, got %d", len(all.Entries))
333+
}
334+
335+
// Skip 2, get 2 — should start at the 3rd commit.
336+
resp, err := eng.Log(dir, "HEAD", LogParams{MaxCount: 2, Skip: 2})
337+
if err != nil {
338+
t.Fatal(err)
339+
}
340+
if len(resp.Entries) != 2 {
341+
t.Fatalf("expected 2 entries, got %d", len(resp.Entries))
342+
}
343+
if resp.Entries[0].Hash != all.Entries[2].Hash {
344+
t.Errorf("first entry after skip=2: got %s, want %s",
345+
resp.Entries[0].Hash[:12], all.Entries[2].Hash[:12])
346+
}
347+
if resp.Entries[1].Hash != all.Entries[3].Hash {
348+
t.Errorf("second entry after skip=2: got %s, want %s",
349+
resp.Entries[1].Hash[:12], all.Entries[3].Hash[:12])
350+
}
351+
352+
// Skip past all commits — should return empty.
353+
resp2, err := eng.Log(dir, "HEAD", LogParams{MaxCount: 10, Skip: 100})
354+
if err != nil {
355+
t.Fatal(err)
356+
}
357+
if len(resp2.Entries) != 0 {
358+
t.Errorf("expected 0 entries after skipping past all, got %d", len(resp2.Entries))
359+
}
360+
if !resp2.Complete {
361+
t.Error("expected Complete=true when no entries returned")
362+
}
363+
364+
// Skip 0 — same as no skip.
365+
resp3, err := eng.Log(dir, "HEAD", LogParams{MaxCount: 2, Skip: 0})
366+
if err != nil {
367+
t.Fatal(err)
368+
}
369+
if resp3.Entries[0].Hash != all.Entries[0].Hash {
370+
t.Errorf("skip=0 first entry: got %s, want %s",
371+
resp3.Entries[0].Hash[:12], all.Entries[0].Hash[:12])
372+
}
373+
}
374+
321375
// initTestGitRepo creates a bare-minimum git repo with N commits using go-git.
322376
func initTestGitRepo(t *testing.T, dir string, numCommits int) {
323377
t.Helper()

internal/mcpserver/tools.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ Use verbose=true in json mode for tree_hash, parent_hashes, and committer detail
197197
"repo": map[string]any{"type": "string", "description": "Named repo, filesystem path, or empty for auto-detect from cwd"},
198198
"ref": map[string]any{"type": "string", "description": "Git ref (default HEAD)"},
199199
"max_count": map[string]any{"type": "integer", "description": "Max commits (default 20)", "default": 20},
200+
"skip": map[string]any{"type": "integer", "description": "Skip this many commits before collecting results", "default": 0},
200201
"continuation": map[string]any{"type": "string", "description": "Continuation token from previous log"},
201202
"format": map[string]any{"type": "string", "enum": []string{"json", "text", "oneline"}, "description": "Output format (default: json)", "default": "json"},
202203
"divider": map[string]any{"type": "string", "enum": []string{"plain", "xml"}, "description": "Divider format for text output (default: plain)", "default": "plain"},
@@ -534,6 +535,7 @@ func (s *Server) handleLog(_ context.Context, req *mcp.CallToolRequest) (*mcp.Ca
534535
Repo string `json:"repo"`
535536
Ref string `json:"ref"`
536537
MaxCount int `json:"max_count"`
538+
Skip int `json:"skip"`
537539
Continuation string `json:"continuation"`
538540
Format string `json:"format"`
539541
Divider string `json:"divider"`
@@ -544,7 +546,7 @@ func (s *Server) handleLog(_ context.Context, req *mcp.CallToolRequest) (*mcp.Ca
544546
}
545547
args.Format = resolveMCPFormat(args.Format)
546548

547-
params := engine.LogParams{MaxCount: args.MaxCount, Verbose: args.Verbose}
549+
params := engine.LogParams{MaxCount: args.MaxCount, Skip: args.Skip, Verbose: args.Verbose}
548550
if args.Continuation != "" {
549551
tok, err := s.decodeContinuation(args.Continuation, "log")
550552
if err != nil {

internal/output/text.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func WriteLogText(w io.Writer, resp *protocol.LogResponse) {
244244
}
245245

246246
if !resp.Complete && resp.Continuation != "" {
247-
fmt.Fprintf(w, "\n... %d commits shown, more available with continuation token\n", resp.Total)
247+
fmt.Fprintf(w, "\n... %d commits shown, more available (use --skip %d or continuation token)\n", resp.Total, resp.Total)
248248
}
249249
}
250250

@@ -271,7 +271,7 @@ func WriteLogOneline(w io.Writer, resp *protocol.LogResponse) {
271271
fmt.Fprintf(w, "%s %s\n", hash, subject)
272272
}
273273
if !resp.Complete && resp.Continuation != "" {
274-
fmt.Fprintf(w, "... %d commits shown, more available with continuation token\n", resp.Total)
274+
fmt.Fprintf(w, "... %d commits shown, more available (use --skip %d or continuation token)\n", resp.Total, resp.Total)
275275
}
276276
}
277277

internal/output/text_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,9 @@ func TestWriteLogText(t *testing.T) {
865865
if !strings.Contains(got, "1 commits shown") {
866866
t.Errorf("expected continuation message, got %q", got)
867867
}
868+
if !strings.Contains(got, "--skip 1") {
869+
t.Errorf("expected --skip hint in continuation message, got %q", got)
870+
}
868871
})
869872
}
870873

0 commit comments

Comments
 (0)