{"id":51840163,"url":"https://github.com/hugoh/jj-trim","last_synced_at":"2026-07-23T03:30:53.627Z","repository":{"id":372228523,"uuid":"1305223099","full_name":"hugoh/jj-trim","owner":"hugoh","description":"Clean up merged bookmarks and abandoned anonymous commits in a jj repository","archived":false,"fork":false,"pushed_at":"2026-07-19T19:02:24.000Z","size":188,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-19T20:23:01.284Z","etag":null,"topics":["bookmarks","cleanup","cli","git","golang","jj","jujutsu","tui","vcs"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hugoh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-18T20:59:37.000Z","updated_at":"2026-07-19T19:01:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hugoh/jj-trim","commit_stats":null,"previous_names":["hugoh/jj-trim"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/hugoh/jj-trim","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugoh%2Fjj-trim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugoh%2Fjj-trim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugoh%2Fjj-trim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugoh%2Fjj-trim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hugoh","download_url":"https://codeload.github.com/hugoh/jj-trim/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hugoh%2Fjj-trim/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35787350,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-23T02:00:06.683Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bookmarks","cleanup","cli","git","golang","jj","jujutsu","tui","vcs"],"created_at":"2026-07-23T03:30:53.486Z","updated_at":"2026-07-23T03:30:53.618Z","avatar_url":"https://github.com/hugoh.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jj-trim\n\nClean up merged bookmarks and abandoned anonymous commits in a jj repository.\n\n`jj-trim` is a CLI tool for keeping your jj (Jujutsu) commit graph tidy. It finds\nbookmarks whose content is already in trunk, detects anonymous forks that were\nnever bookmarked, and presents everything in a preview graph or an interactive\nTUI — with jj's own `jj op revert` as the safety net.\n\n[![CI](https://github.com/hugoh/jj-trim/actions/workflows/ci.yml/badge.svg)](https://github.com/hugoh/jj-trim/actions/workflows/ci.yml)\n\n## Features\n\n- **Merged bookmark detection** — bookmarks whose commit is an ancestor of\n  `trunk()`. Provably safe, the only bucket `bookmarks apply` touches without a\n  review step.\n- **Probably-merged (squash-merge) detection** — bookmarks whose commit\n  description appears verbatim in a trunk commit, catching the GitHub-style\n  squash-merge case the ancestor check alone misses. Never auto-deleted; shown\n  in preview and review only.\n- **Stale bookmark detection** — old local-only bookmarks past a configurable\n  age threshold (default 90 days). Heuristic, never auto-deleted.\n- **Anonymous fork discovery** — commits in `heads(mutable())` that aren't on\n  any bookmark or the working copy. Sub-classified into git-bypass duplicates\n  (content-identical to a kept commit, almost certainly orphaned working-copy\n  artifacts), no-description forks, and described forks.\n- **Preview graph using jj's own log** — the tool simply passes a revset to\n  `jj log` and annotates it with a legend, so what you see is exactly what jj\n  sees.\n- **Interactive TUI** — Bubbletea-based list + detail pane + confirm flow.\n  Toggle between bookmarks and commits mode, mark items for delete or abandon,\n  see a live tally, and explicitly confirm before anything is written.\n- **Safe by default** — bare `jj-trim` previews only. Nothing is deleted or\n  abandoned without `bookmarks apply` or an explicit review session.\n- **Composable with jj's own safe guards** — every batch operation prints the\n  `jj op revert \u003cop-id\u003e` command to undo exactly that batch.\n\n## Install\n\n### Homebrew (macOS/Linux)\n\n```sh\nbrew install hugoh/tap/jj-trim\n```\n\n### Linux (deb/rpm)\n\nDownload the `.deb` or `.rpm` from the [releases page](https://github.com/hugoh/jj-trim/releases) and install with your package manager:\n\n```sh\n# Debian/Ubuntu\nsudo apt install ./jj-trim_*.deb\n\n# RHEL/Fedora\nsudo dnf install ./jj-trim_*.rpm\n```\n\n### mise\n\n```sh\nmise use -g github:hugoh/jj-trim\n```\n\n### Go install\n\n```sh\ngo install github.com/hugoh/jj-trim@latest\n```\n\n### From source\n\n```sh\ngit clone https://github.com/hugoh/jj-trim\ncd jj-trim\ngo build -o jj-trim .\n```\n\n## Quick start\n\n```sh\n# Preview merged bookmarks (dry run — no changes)\njj-trim bookmarks preview\n\n# Delete merged bookmarks (provably safe — runs without review)\njj-trim bookmarks apply\n\n# Interactive review of all bookmark candidates (merged + probably-merged + stale)\njj-trim bookmarks review\n\n# Preview anonymous commit forks\njj-trim commits preview\n\n# Interactive review of fork candidates\njj-trim commits review\n\n# Launch the TUI (default, same as bare `jj-trim`)\njj-trim\n\n# Fetch first, then classify (so jj's own stray-bookmark cleanup applies)\njj-trim --fetch bookmarks preview\n\n# Operate on a specific repository\njj-trim -R ~/other-project bookmarks preview\n```\n\n## Classification buckets\n\n| Bucket                 | Part      | Certainty                                      | Auto-deletable          |\n| ---------------------- | --------- | ---------------------------------------------- | ----------------------- |\n| `merged`               | Bookmarks | Provable (ancestor of trunk)                   | Yes (`bookmarks apply`) |\n| `probably-merged`      | Bookmarks | Heuristic (message match in trunk)             | No — review only        |\n| `stale`                | Bookmarks | Heuristic (old, no remote)                     | No — review only        |\n| `git-commit-duplicate` | Commits   | Provable (content-identical to a kept commit)  | No — review only        |\n| `no-description`       | Commits   | Heuristic (empty description, high confidence) | No — review only        |\n| `has-description`      | Commits   | Weak (named work — needs human look)           | No — review only        |\n\n## Preview \u0026 legend\n\n`jj-trim bookmarks preview` runs `jj log -r \u003crevset\u003e` with the default template\n(full color, ASCII DAG), then prints a legend mapping each candidate's\nchange-id prefix to its reason and age:\n\n```text\n% jj-trim bookmarks preview\n○  yqnoqprn hugoh@\n│  (no description)\n○  mqtwpolr hugoh@ main\n│  ...\n◆  rlvkwnso hugoh@ trunk\n...\n\nLegend:\n  mqtwpolr  merged (2 weeks old)\n  yqnoqprn  stale, 94 days old, no remote\n```\n\nThe same annotated approach works for `commits preview`, with\nage-and-diffstat on each legend line.\n\n## Interactive TUI\n\n`jj-trim` (bare, no subcommand) launches the full TUI — a navigable live list\nof candidates with a detail pane, mode toggle, and in-session filters:\n\n- **Tab** — toggle between bookmarks mode and commits mode\n- **↑/↓** or **j/k** — navigate the candidate list\n- **d** (bookmarks mode) — mark a bookmark for delete (ref only)\n- **a** — mark for abandon (bookmarks: delete + abandon private chain;\n  commits: abandon only)\n- **u** — clear a marked item's decision\n- **Enter** — open the confirm screen showing exactly what's marked\n- **f** — open the filters overlay (trunk, protected globs, stale-after for\n  bookmarks; no-description-only for commits)\n- **q** / **Esc** / **Ctrl-C** — quit without applying anything\n\nThe confirm screen lists every item under its action type and requires a second\n**Enter** to actually run the batch operations. After applying, a popup shows\nthe result along with one `jj op revert \u003cop-id\u003e` per batch that ran.\n\n## Command reference\n\n```text\njj-trim [-R|--repository \u003cpath\u003e] [--fetch] [\u003ccommand\u003e]\n\n  Bare `jj-trim` (no \u003ccommand\u003e) launches the interactive TUI.\n\n  -R, --repository \u003cpath\u003e   Repo to operate on (default: current directory)\n      --fetch                Run jj git fetch first\n\nCommands:\n  bookmarks preview   Show what would be deleted (default)\n  bookmarks apply     Delete merged (certain) bookmarks only\n  bookmarks review    Interactive walk over merged/probably-merged/stale\n                       bookmarks\n\n    -p, --protected \u003cglob,...\u003e   Bookmarks never deleted (default: none)\n    -t, --trunk \u003crevset\u003e         Override trunk() (default: trunk())\n        --stale-after \u003cduration\u003e  Age threshold for the \"stale, no remote\"\n                                  heuristic (default: 2160h / 90d)\n\n  commits preview   Show what would be abandoned (default)\n  commits review    Interactive walk over anonymous fork candidates\n\n        --no-description-only   Restrict to description(\"\")\n\n  install-completions   Install shell completions (bash/zsh/fish)\n```\n\n## Undo\n\nEvery batch operation prints the exact command to revert it:\n\n```text\n% jj-trim bookmarks apply\nDeleted 3 bookmark(s): [experiment/foo, feature/wip, debug/logging]\nUndo with: jj op revert 50859f2dfaf3\n```\n\n`jj op revert` reverts a specific operation regardless of what has happened\nsince — unlike bare `jj undo`, which always targets the single most recent\noperation.\n\n## Related tools\n\n- [`git-trim`](https://github.com/foriequal0/git-trim) — the inspiration for\n  this tool's bookmark-cleanup half.\n- [tommymorgan/jj-tools](https://github.com/tommymorgan/jj-tools) — narrow\n  cleanup of auto-generated temporary bookmarks. Complements jj-trim rather\n  than competing.\n- [Jujutsu (jj) VCS](https://github.com/jj-vcs/jj) — jj's own `jj git fetch`\n  already handles stray-bookmark cleanup (tracked remote deleted upstream),\n  and `jj abandon` cascades bookmark deletion — jj-trim composes these\n  primitives rather than reimplementing them.\n\n## Contributing\n\nContributions are welcome! See the\n[implementation design doc](IMPLEMENTATION.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhugoh%2Fjj-trim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhugoh%2Fjj-trim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhugoh%2Fjj-trim/lists"}