README
¶
gh-diet-kit
Providing commands for inspecting dangling git objects on the remote and detecting or estimating storage savings from Git LFS migration.
Installation
gh extension install srz-zumix/gh-diet-kit
Shell Completion
Workaround Available! While gh CLI doesn't natively support extension completion, we provide a patch script that enables it.
Prerequisites: Before setting up gh-diet-kit completion, ensure gh CLI completion is configured for your shell. See gh completion documentation for setup instructions.
For detailed installation instructions and setup for each shell, see the Shell Completion Guide.
Agent Skills
gh-diet-kit bundles agent skills for AI. Use the skills subcommand to install and manage them.
gh diet-kit skills [subcommand] [args...]
For details, see Songmu/skillsmith.
Flags
| Flag | Shorthand | Description |
|---|---|---|
--log-level |
-L |
Set log level (e.g. debug, info, warn, error) |
--read-only |
Run in read-only mode (prevent write operations) | |
--version |
Print the version number | |
--help |
-h |
Show help |
Commands
completion
Generate shell completion scripts for bash, zsh, fish, or powershell.
gh diet-kit completion [bash|zsh|fish|powershell]
dangling blobs
List blobs that are referenced only by commits from squash or rebase merged pull requests, commits dropped by force-pushes on PR head branches, or commits from closed unmerged pull requests, and are not reachable from any normal branch or tag ref on the remote. All detection methods are enabled by default.
Note: Git blobs are content-addressed. A blob introduced by a dangling commit may also appear in a live branch tree via identical file content (e.g. package-lock.json, generated files). Without a local git clone this cannot be detected efficiently via the GitHub API, so results may contain false positives. Use --reachability-check local-object (after running git fetch --all --tags) to filter out blobs that are still reachable from any local ref. Note: git fetch --all alone does not fetch tags that are not reachable from any branch, so commits reachable only from such tags may be misreported.
Output fields: SHA, PATH, SIZE, COMMIT_SHA, PR_NUMBER, PR_URL. In JSON, SIZE is omitted when blob sizes are unavailable.
gh diet-kit dangling blobs [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--clear-cache |
false |
Clear the per-PR and commit blob cache before running, then use cache normally | |
--clear-git-cache |
false |
Clear the git bare clone cache and re-clone before running | |
--concurrency |
0 |
Maximum number of concurrent GitHub API calls per PR for commit blob fetches (<=0 uses the package default of 10) |
|
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--limit |
unlimited | Maximum number of closed PRs to inspect (ignored when --pr is specified) |
|
--no-cache |
false |
Disable per-PR result cache; always re-process all PRs (does not clear existing cache entries) | |
--no-closed |
false |
Disable closed unmerged PR blob detection. Previously cached data for this scope is preserved in the cache when this flag is set. | |
--no-force-push |
false |
Disable force-push dropped commit blob detection. Previously cached data for this scope is preserved in the cache when this flag is set. | |
--no-squash-merge |
false |
Disable squash/rebase merged PR blob detection. Previously cached data for this scope is preserved in the cache when this flag is set. | |
--order |
asc |
Sort order: asc or desc |
|
--pr |
all closed PRs | PR numbers to inspect (comma-separated or repeated, e.g. --pr 1,2 or --pr 1 --pr 2) |
|
--pr-concurrency |
0 |
Maximum number of PRs inspected concurrently (<=0 uses the default of 4). Higher values are faster on large repositories but increase the risk of hitting GitHub's secondary rate limit. Output order is unaffected. |
|
--reachability-check |
none |
Filter out blobs reachable from a local ref (requires git fetch --all --tags): none, local-object |
|
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--sort |
Sort by field: size, path, pr_number |
||
--strict-errors |
false |
Fail immediately on any API or git error instead of logging and continuing | |
--template |
-t |
Format JSON output using a Go template |
dangling branches
List unprotected branches that have no associated pull request (open, closed, or merged), and calculate the total size of blobs introduced by commits unique to each branch. The default branch and all protected branches are always excluded from results.
A commit is considered unique to a branch when it is not present in any other no-PR branch's commit history (commits ahead of the default branch). Commits shared with branches that have an associated pull request are not considered because those branches are excluded from the scan. UNIQUE_SIZE is the sum of blob sizes from the diffs of those unique commits, with blob SHAs deduplicated across commits — an approximation of the space that could be freed by deleting the branch.
Output fields: BRANCH, COMMIT_SHA, AHEAD_COUNT, AUTHOR, UNIQUE_SIZE.
gh diet-kit dangling branches [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--clear-cache |
Clear the cached branch analysis data before running | ||
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--max-branches |
Limit the number of no-PR branches for which blob sizes are computed (0 = unlimited) | ||
--max-commits |
Limit the number of unique commits fetched per branch for blob size computation (0 = unlimited) | ||
--no-blob-size |
Skip blob size computation; UNIQUE_SIZE will be empty in output |
||
--no-cache |
Run without using cached branch analysis data | ||
--order |
asc |
Sort order: asc or desc |
|
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--sort |
Sort by field: branch, ahead_count, unique_size |
||
--template |
-t |
Format JSON output using a Go template |
dangling commits
List commits that originate from squash or rebase merged pull requests, commits dropped by force-pushes on PR head branches, or commits from closed unmerged pull requests, and are not reachable from any normal branch or tag ref on the remote. All detection methods are enabled by default.
Output fields: SHA, PR_NUMBER, PR_URL, SIZE, MESSAGE. SIZE is the sum of sizes of unique blobs added or modified in the commit diff. In JSON, SIZE is omitted when blob sizes are unavailable.
gh diet-kit dangling commits [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--clear-cache |
false |
Clear the per-PR and commit blob cache before running, then use cache normally | |
--clear-git-cache |
false |
Clear the git bare clone cache and re-clone before running | |
--concurrency |
0 |
Maximum number of concurrent GitHub API calls per PR for commit blob fetches (<=0 uses the package default of 10) |
|
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--limit |
unlimited | Maximum number of closed PRs to inspect (ignored when --pr is specified) |
|
--no-blob-size |
false |
Skip blob size computation; SIZE will be empty in output (reduces API calls significantly) |
|
--no-cache |
false |
Disable per-PR result cache; always re-process all PRs (does not clear existing cache entries) | |
--no-closed |
false |
Disable closed unmerged PR detection. Previously cached data for this scope is preserved in the cache when this flag is set. | |
--no-force-push |
false |
Disable force-push dropped commit detection. Previously cached data for this scope is preserved in the cache when this flag is set. | |
--no-squash-merge |
false |
Disable squash/rebase merged PR commit detection. Previously cached data for this scope is preserved in the cache when this flag is set. | |
--order |
asc |
Sort order: asc or desc |
|
--pr |
all closed PRs | PR numbers to inspect (comma-separated or repeated, e.g. --pr 1,2 or --pr 1 --pr 2) |
|
--pr-concurrency |
0 |
Maximum number of PRs inspected concurrently (<=0 uses the default of 4). Higher values are faster on large repositories but increase the risk of hitting GitHub's secondary rate limit. Output order is unaffected. |
|
--reachability-check |
none |
Verify candidates are truly unreachable: none, default-branch, branches, refs, local-object, local-refs |
|
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--sort |
Sort by field: size, pr_number |
||
--strict-errors |
false |
Fail immediately on any API or git error instead of logging and continuing | |
--template |
-t |
Format JSON output using a Go template |
dangling local
List commits that are not reachable from any local branch or tag ref but exist on the remote GitHub repository.
Locally dangling commits can originate from rebasing, amending, fetching now-deleted remote branches, or fetching pull request refs (refs/pull/*/head). When those objects have not been garbage-collected on the remote they remain accessible via the GitHub API.
Must be run inside a local git clone. By default reflog entries are included in reachability analysis; use --no-reflogs to ignore them.
Output fields: SHA, MESSAGE. In JSON, pr_number, pr_url, and size fields are omitted.
gh diet-kit dangling local [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--no-reflogs |
false |
Ignore reflog entries when determining local reachability | |
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--template |
-t |
Format JSON output using a Go template |
lfs detect
Detect files in the repository whose size exceeds a threshold and are not currently stored as Git LFS objects. Files properly tracked by LFS appear as small pointer files (~134 bytes) in the git tree and are therefore not reported.
The minimum allowed threshold is 135 bytes (one byte above the LFS pointer size). Values at or below the pointer size would cause genuine LFS pointer blobs to be reported as candidates.
Output fields: PATH, SIZE, SHA.
gh diet-kit lfs detect [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--order |
asc |
Sort order: asc or desc |
|
--ref |
repository default branch | Branch, tag, or commit SHA to inspect | |
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--sort |
Sort by field: size, path |
||
--template |
-t |
Format JSON output using a Go template | |
--threshold |
10MB |
Minimum file size to report as an LFS candidate (minimum: 135 bytes; e.g. 50MB, 1GB, 10000000) |
lfs estimate
Estimate how much git object storage would be freed by migrating large files to Git LFS.
When path arguments are given, only those specific files are estimated regardless of --threshold. When no paths are given, the entire repository tree is scanned and files exceeding --threshold are reported.
For each candidate the estimated saving is estimated_total_size - (lfs_pointer_size × version_count) where lfs_pointer_size ≈ 134 bytes. By default only the current tree is inspected (version_count = 1). Use --scan-commits to count historic versions; the estimated total size is approximated as current_size × version_count.
Default table columns (without --scan-commits): PATH, CURRENT_SIZE, ESTIMATED_SAVING
Default table columns (with --scan-commits): PATH, CURRENT_SIZE, VERSIONS, ESTIMATED_TOTAL_SIZE, ESTIMATED_SAVING
When --format json is used, the exported object includes JSON fields for the estimate data, including path, current_size, estimated_saving, sha, and version_count. With --scan-commits, the JSON output also includes estimated_total_size.
gh diet-kit lfs estimate [path...] [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--order |
asc |
Sort order: asc or desc |
|
--ref |
repository default branch | Branch, tag, or commit SHA to inspect | |
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--scan-commits |
0 |
Scan up to N commits per file to count historic versions (0 = current tree only, negative = all commits) |
|
--sort |
Sort by field: saving, size, path, versions |
||
--template |
-t |
Format JSON output using a Go template | |
--threshold |
10MB |
Minimum file size to include in the estimate; must be at least 135 bytes (ignored when path arguments are given) |
pr assets dump
Download media assets (images and videos) embedded in pull request bodies, issue comments, and review comments to a local directory, and write a metadata.json file that records the source repository, PR numbers, asset locations, and original URLs.
On subsequent runs, unchanged PRs (by updated_at timestamp) are skipped and already-downloaded files are not re-fetched. Use --overwrite to force a full re-download regardless.
gh diet-kit pr assets dump [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--max-prs |
0 |
Maximum number of PRs to fetch when --pr is not specified (0 = unlimited) |
|
--metadata-file |
<output-dir>/metadata.json |
Path to write the metadata JSON file | |
--no-file-size |
false |
Skip the HEAD request used to record asset file sizes in the metadata | |
--output-dir |
./pr-assets |
Directory to download asset files into | |
--overwrite |
false |
Re-download all assets and overwrite existing files, skipping repository and timestamp checks | |
--pr |
all PRs | PR numbers to scan (repeatable, e.g. --pr 1 --pr 2) |
|
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--state |
all |
Filter pull requests by state: all, open, closed |
pr assets list
Scan pull request bodies, issue comments, and review comments for GitHub-hosted media assets (images and videos) and print a summary table. Detected URL patterns include user-images.githubusercontent.com, private-user-images.githubusercontent.com, github.com/user-attachments/assets/..., and github.com/<owner>/<repo>/assets/....
Output fields: PR_NUMBER, LOCATION, LOCATION_ID, TYPE, FILENAME, FILE_SIZE, ASSET_URL.
gh diet-kit pr assets list [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--fields |
all default fields | Comma-separated list of output fields to display | |
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--max-prs |
0 |
Maximum number of PRs to fetch when --pr is not specified (0 = unlimited) |
|
--no-file-size |
false |
Skip the HEAD request used to determine asset file sizes | |
--pr |
all PRs | PR numbers to scan (repeatable, e.g. --pr 1 --pr 2) |
|
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--state |
all |
Filter pull requests by state: all, open, closed |
|
--template |
-t |
Format JSON output using a Go template |
pr assets restore
Read the metadata.json produced by pr assets dump, upload each local asset file to the destination repository, and replace the old source asset URLs with the new destination CDN URLs in PR bodies, issue comments, and review comments.
By default (--upload-method auto) assets are uploaded directly through GitHub's REST API, with no browser required. Browser automation (Playwright) is used instead when the destination is GitHub Enterprise Server, or as a fallback for files the API cannot accept (unsupported extension, or over its 10 MB image / 100 MB video size limit). On the first run that needs a browser, a window is opened so you can log in to GitHub interactively; the session is persisted to the --browser-state file for headless operation on subsequent runs.
The --upload-delay flag paces uploads to stay under GitHub's per-minute secondary rate limit (about 80 content-generating requests per minute). Asset uploads also count against a stricter per-endpoint content-creation limit whose hourly bucket is lower than the documented 500 requests per hour (roughly 80–90 uploads per hour in practice), so a large restore eventually hits it regardless of --upload-delay. When that happens, the restore automatically waits and resumes: it honors the Retry-After / x-ratelimit-reset response headers (waiting up to ~1 hour) before retrying, so a large restore can be left running unattended.
When the restore finishes it writes a metadata.restored.json file into --input-dir listing the assets that still need work: those whose old URL is still present at the destination but were not fully restored this run (the upload or the destination edit did not complete), plus locations that could not be checked because of transient errors (rate limits, 5xx, auth). Assets confirmed already-restored, successfully updated this run, or belonging to a destination PR/comment that no longer exists are omitted. Each remaining asset's location ID is rewritten to the destination comment ID resolved during the run. Re-run the restore with --continue to resume from <input-dir>/metadata.restored.json without re-searching already-migrated comments. The file is not written in --dryrun mode.
gh diet-kit pr assets restore [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--browser-state |
<user-config-dir>/gh-diet-kit/playwright-state.json |
Path to the Playwright browser state file for session persistence (used only when browser automation is active) | |
--clear-cache |
false |
Delete the saved browser session after the restore completes (used only when browser automation is active) | |
--clear-cache-only |
false |
Delete the saved browser session and exit without restoring | |
--continue |
false |
Resume from <input-dir>/metadata.restored.json written by a previous restore (mutually exclusive with --metadata-file) |
|
--dryrun |
-n |
false |
Preview uploads and URL replacements without making any changes |
--headed |
false |
Run browser in headed (visible) mode even when a saved session exists (used only when browser automation is active) | |
--input-dir |
./pr-assets |
Directory containing the downloaded asset files | |
--metadata-file |
<input-dir>/metadata.json |
Path to the metadata JSON file | |
--pr |
all PRs | PR numbers to restore (repeatable) | |
--repo |
-R |
current repository | Destination repository in [HOST/]OWNER/REPO format |
--upload-delay |
1s |
Minimum delay between asset uploads to avoid GitHub's secondary rate limit | |
--upload-method |
auto |
Upload method: auto, api, or browser |
tree detect
Analyse the git tree structure of a repository and report directories whose direct entry count meets or exceeds a threshold.
Git stores one tree object per directory per commit. A directory with many direct entries produces a large tree object, and a deep or wide hierarchy multiplies the number of tree objects written on every commit. This command helps identify which directories are the primary contributors to tree object bloat.
For each directory the following fields are reported:
ENTRY_COUNT– number of direct children (blobs + sub-trees)TOTAL_FILES– total number of blob entries reachable from this directory (recursive)EST_TREE_SIZE– estimated byte size of the git tree object (28 bytes fixed overhead per entry + base name length)DEPTH– nesting level (0 = repository root)
A summary line with total directory count, total file count, total estimated tree object size, and maximum depth is printed after the table.
Output fields: PATH, DEPTH, ENTRY_COUNT, TOTAL_FILES, EST_TREE_SIZE.
gh diet-kit tree detect [flags]
| Flag | Shorthand | Default | Description |
|---|---|---|---|
--format |
table | Output format: json |
|
--jq |
-q |
Filter JSON output using a jq expression | |
--order |
asc |
Sort order: asc or desc |
|
--ref |
repository default branch | Branch, tag, or commit SHA to inspect | |
--repo |
-R |
current repository | Repository in [HOST/]OWNER/REPO format |
--sort |
Sort by field: entry-count, total-files, est-size, depth, path |
||
--template |
-t |
Format JSON output using a Go template | |
--threshold |
1 |
Minimum number of direct entries in a directory to report (must be >= 1) |