diff --git a/README.md b/README.md index e5d2fd95..f6ad6dca 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Then embed the cards in your `README.md`: | `tz` | `UTC` | IANA tz for the productive-time card (e.g. `Asia/Saigon`) | | `start_of_week` | `sunday` | First day of week for heatmap rows and weekday bars (`sunday`…`saturday`) | | `top_repos` | `0` | Optional cap on seed repos probed for commit history (`0` = unlimited) | -| `commits_per_repo` | `500` | Max commits sampled per repo (covers last-year and all-time aggregates) | +| `commits_per_repo` | `500` | Max commits sampled per repo, `0` = every commit (covers last-year and all-time aggregates) | | `include_forks` | `true` | Include forked repos in stats and commit probing | | `include_private` | `true` | Include private repos (requires PAT with `repo` scope; silently no-op otherwise) | | `include_org_repos`| `false` | Count org-owned repos you administer toward stars, repo count, languages, top-starred (needs `read:org`) | @@ -168,7 +168,7 @@ ghstats -user tiennm99 -themes dracula -include-org-repos -out output | `-tz` | `Local` | IANA timezone for productive-time cards | | `-start-of-week` | `sunday` | First day of week for heatmap rows and weekday bars (`sunday`…`saturday`) | | `-top-repos` | `0` | Optional cap on seed repos probed (`0` = unlimited) | -| `-commits-per-repo` | `500` | Max commits sampled per repo | +| `-commits-per-repo` | `500` | Max commits sampled per repo, `0` = every commit | | `-include-forks` | `true` | Include forked repos in the stats | | `-include-private` | `true` | Include private repos (requires `repo` PAT scope; silently no-op otherwise) | | `-include-org-repos`| `false` | Count org-owned repos you administer toward stars, repo count, languages, top-starred | diff --git a/action.yml b/action.yml index 2bcc0d96..c20b92ca 100644 --- a/action.yml +++ b/action.yml @@ -36,7 +36,7 @@ inputs: required: false default: "0" commits_per_repo: - description: Max commits sampled per repo (covers last-year and all-time aggregates) + description: Max commits sampled per repo, `0` for every commit (covers last-year and all-time aggregates) required: false default: "500" include_forks: diff --git a/docs/system-architecture.md b/docs/system-architecture.md index 9bc74e7f..7ae9eed5 100644 --- a/docs/system-architecture.md +++ b/docs/system-architecture.md @@ -49,7 +49,7 @@ FetchContributionsAllTime(ctx, profile, opts) │ TotalCommitsAllTime │ ▼ -FetchProductive(ctx, profile, profile.SeedRepos, loc, commitsPerRepo) +FetchProductive(ctx, profile, profile.SeedRepos, loc, commitsPerRepo) // 0 = no cap │ commitHistoryQuery × (#seeds × pages) │ per commit: t = committedDate in loc │ ProductiveAllTime[t.Hour]++ diff --git a/internal/github/commit_cap_test.go b/internal/github/commit_cap_test.go new file mode 100644 index 00000000..09723a09 --- /dev/null +++ b/internal/github/commit_cap_test.go @@ -0,0 +1,27 @@ +package github + +import "testing" + +func TestReachedCommitCap(t *testing.T) { + cases := []struct { + name string + seen int + maxPerRepo int + want bool + }{ + {"under the cap", 100, 500, false}, + {"at the cap", 500, 500, true}, + {"past the cap", 700, 500, true}, + // Zero means unlimited, so even a fresh repo keeps paginating. The + // old behavior stopped here and rendered empty commit-derived cards. + {"zero cap, nothing seen yet", 0, 0, false}, + {"zero cap, deep into history", 100_000, 0, false}, + {"negative cap treated as unlimited", 10, -1, false}, + } + for _, tc := range cases { + if got := reachedCommitCap(tc.seen, tc.maxPerRepo); got != tc.want { + t.Errorf("%s: reachedCommitCap(%d, %d) = %v, want %v", + tc.name, tc.seen, tc.maxPerRepo, got, tc.want) + } + } +} diff --git a/internal/github/productive.go b/internal/github/productive.go index c5749f25..dc8e86bc 100644 --- a/internal/github/productive.go +++ b/internal/github/productive.go @@ -30,9 +30,21 @@ type productiveGQL struct { // magnitude is irrelevant because the card renders percentages. const scaleFactor = 10_000 +// reachedCommitCap reports whether a repo has given up enough commits to stop +// paginating. A cap of zero or less means no cap — keep going until the +// history runs out — matching how -top-repos treats zero. Without the guard a +// zero cap would stop before the first page and quietly empty every +// commit-derived card. +func reachedCommitCap(seen, maxPerRepo int) bool { + if maxPerRepo <= 0 { + return false + } + return seen >= maxPerRepo +} + // FetchProductive paginates the default-branch commit history (authored by -// the target user) for each repo up to maxPerRepo commits, and fills two -// parallel sets of aggregates on the Profile: +// the target user) for each repo up to maxPerRepo commits (0 = all), and +// fills two parallel sets of aggregates on the Profile: // // - Last-year: p.Productive (24h histogram) and p.CommitsByLanguage // - All-time: p.ProductiveAllTime and p.CommitsByLanguageAllTime @@ -62,7 +74,7 @@ func (c *Client) FetchProductive(ctx context.Context, p *Profile, repos []RepoIn var cursor *string seen := 0 for { - if seen >= maxPerRepo { + if reachedCommitCap(seen, maxPerRepo) { break } owner := repo.Owner diff --git a/main.go b/main.go index 1767be16..47f69c03 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,7 @@ func main() { themesFlag = flag.String("themes", "dracula", "comma-separated theme ids, or 'all'") tzName = flag.String("tz", "Local", "timezone for productive-time card (IANA name, e.g. Asia/Saigon)") topRepos = flag.Int("top-repos", 0, "optional cap on seed repos probed for commit history (0 = unlimited)") - perRepo = flag.Int("commits-per-repo", 500, "max commits sampled per repo (covers both last-year and all-time aggregates)") + perRepo = flag.Int("commits-per-repo", 500, "max commits sampled per repo, 0 = every commit (covers both last-year and all-time aggregates)") includeForks = flag.Bool("include-forks", true, "include forked repos in stats and commit probing") includePrivate = flag.Bool("include-private", true, "include private repos (requires PAT with repo scope; silently no-op otherwise)") includeOrgs = flag.Bool("include-org-repos", false, "count org-owned repos you administer toward stars, repo count, repos-per-language and top-starred")