From 0f50d593565efc92ee68976b14296c83760b4878 Mon Sep 17 00:00:00 2001 From: Geoffrey Wiseman Date: Fri, 11 Sep 2026 18:39:34 -0400 Subject: [PATCH] Make --skip omit anything with no usage in the period --skip previously hid only repositories with no workflows, while still listing repositories whose workflows all showed zero and the zero-usage workflows inside busy repositories. That distinction dates from the old billing API, where zero billable minutes on a repository with workflows was a signal in itself. With usage computed from job durations, zero just means nothing ran, so --skip now drops zero-usage workflows first and then any repository left with none, on both the current-repo and explicit-target paths. The help text lists the flags, and the README's TSV example no longer shows zero rows under --skip. Closes #11 Co-Authored-By: Claude Fable 5.1 --- README.md | 15 +++------------ main.go | 31 +++++++++++++++++++++++++------ main_test.go | 21 +++++++++++++++++++++ 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 11bae8c..0b0e508 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,8 @@ codiform/gh-actions-usage (2 workflows; 4h 5m): - release (.github/workflows/release.yml, active, 2m 348ms) ``` -Display the usage for a mix of repos using a tab-separated value format (TSV): +Display the usage for a mix of repos using a tab-separated value format (TSV), skipping repositories and workflows +that had no usage in the period: ```shell gh-actions-usage on  feature/formatters [!] via 🐹 v1.21.1 took 2s @@ -145,16 +146,6 @@ GitHub Actions Usage (3a7cfc0) Repo Workflow Milliseconds codiform/gh-actions-usage .github/workflows/ci.yml 350000 codiform/gh-actions-usage .github/workflows/release.yml 2500 -kim0/brave-core .github/workflows/pull_request.yml 0 -kim0/brave-core .github/workflows/require-checklist.yml 0 -kim0/brave-core .github/workflows/set-milestone-from-base-branch.yml 0 -kim0/brave-core .github/workflows/alert_unsigned_commits.yml 0 -kim0/brave-core .github/workflows/codeql-analysis.yml 0 -kim0/haven-main .github/workflows/linux-227.yml 0 -kim0/haven-main .github/workflows/linux-229.yml 0 -kim0/haven-main .github/workflows/macos.yml 0 -kim0/haven-main .github/workflows/windows.yml 0 -kim0/haven-main .github/workflows/docker-build-push.yml 0 kim0/haven-offshore .github/workflows/main.yml 75035 kim0/terraform-switcher .github/workflows/release.yml 1239 ``` @@ -164,7 +155,7 @@ kim0/terraform-switcher .github/workflows/release.yml 1239 - `--month=YYYY-MM` selects the billing period to report, a calendar month in UTC; defaults to the current month. The current month is reported up to now, and a past month in full. Future months are rejected. - `--output=human|tsv` selects the output format; `tsv` is machine-readable. -- `--skip` omits repositories that have no workflows. +- `--skip` omits repositories and workflows that had no usage in the period, leaving only where the time went. - `--verbose` prints full error details instead of the short message. # References diff --git a/main.go b/main.go index c6742ba..f1c8b6e 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,7 @@ func main() { now := time.Now() cfg := &config{w: os.Stdout} - flag.BoolVar(&cfg.skip, "skip", false, "Skips displaying repositories with no workflows") + flag.BoolVar(&cfg.skip, "skip", false, "Skips displaying repositories and workflows with no usage in the period") flag.BoolVar(&cfg.verbose, "verbose", false, "Print verbose output including additional error details") flag.StringVar(&cfg.output, "output", "human", "Output format: human or TSV (machine readable)") flag.StringVar(&cfg.month, "month", now.UTC().Format(monthLayout), "Billing period to report, as YYYY-MM") @@ -131,6 +131,9 @@ func tryDisplayCurrentRepo(cfg config) { printError(cfg, "Error collecting usage", err) return } + if cfg.skip { + skipUnused(repoFlowUsage) + } cfg.format.PrintUsage(repoFlowUsage) } @@ -151,13 +154,24 @@ func tryDisplayAllSpecified(cfg config, targets []string) { return } if cfg.skip { - for repo, flows := range repoFlowUsage { - if len(flows) == 0 { - delete(repoFlowUsage, repo) + skipUnused(repoFlowUsage) + } + cfg.format.PrintUsage(repoFlowUsage) +} + +// skipUnused removes the workflows that had no usage in the period, and then the repositories left with no +// workflows, so that what remains is only where the usage went. +func skipUnused(repoFlowUsage client.RepoUsage) { + for repo, flows := range repoFlowUsage { + for workflow, used := range flows { + if used == 0 { + delete(flows, workflow) } } + if len(flows) == 0 { + delete(repoFlowUsage, repo) + } } - cfg.format.PrintUsage(repoFlowUsage) } // parseMonth returns the first instant, in UTC, of the month named by value as YYYY-MM. Months that have not @@ -301,5 +315,10 @@ func printHelp() { "Target can be one of:\n" + "- username (e.g. geoffreywiseman)\n" + "- organization (e.g. codiform)\n" + - "- repository (e.g. codiform/gh-actions-usage)") + "- repository (e.g. codiform/gh-actions-usage)\n\n" + + "Flags:\n" + + "- --month=YYYY-MM selects the billing period, a calendar month in UTC; defaults to the current month\n" + + "- --output=human|tsv selects the output format\n" + + "- --skip omits repositories and workflows with no usage in the period\n" + + "- --verbose prints full error details") } diff --git a/main_test.go b/main_test.go index a555564..5b7941b 100644 --- a/main_test.go +++ b/main_test.go @@ -217,3 +217,24 @@ func TestPrintError_GenericError(t *testing.T) { // Then assert.Equal(t, "No current repository (use --verbose for details)\n\n", out.String()) } + +func TestSkipUnused(t *testing.T) { + // Given + ci := client.Workflow{Name: "CI", Path: ".github/workflows/ci.yml", State: "active", ID: 1} + release := client.Workflow{Name: "release", Path: ".github/workflows/release.yml", State: "active", ID: 2} + disabled := client.Workflow{Name: "nightly", Path: ".github/workflows/nightly.yml", State: "disabled_manually", ID: 3} + busy := &client.Repository{FullName: "codiform/busy"} + idle := &client.Repository{FullName: "codiform/idle"} + empty := &client.Repository{FullName: "codiform/empty"} + usage := client.RepoUsage{ + busy: {ci: 350000, release: 0, disabled: 2500}, + idle: {ci: 0, release: 0}, + empty: {}, + } + + // When + skipUnused(usage) + + // Then + assert.Equal(t, client.RepoUsage{busy: {ci: 350000, disabled: 2500}}, usage) +}