neongreen/jj-run
Temporary implementation of 'jj run'
{ "createdAt": "2025-07-04T09:23:11Z", "defaultBranch": "main", "description": "Temporary implementation of 'jj run'", "fullName": "neongreen/jj-run", "homepage": "https://github.com/neongreen/mono/tree/main/jj-run", "language": "Python", "name": "jj-run", "pushedAt": "2025-11-01T20:23:10Z", "stargazersCount": 9, "topics": [], "updatedAt": "2025-11-01T20:23:28Z", "url": "https://github.com/neongreen/jj-run"}jj-run migrated to neongreen/mono
Section titled “jj-run migrated to neongreen/mono”See https://github.com/neongreen/mono/tree/main/jj-run (the new implementation in Go), as well as https://github.com/neongreen/mono/tree/main/jj-run-py (the original implementation).
A script to execute shell commands across multiple repository changes in isolated workspaces using jj.
- Runs arbitrary shell commands for each change in a revset, in isolation.
- Uses a temporary workspace for each run, so your main repo doesn’t change while the script is running.
Installation
Section titled “Installation”First, install uv, the best and greatest Python package manager.
Then add to your jj config:
jj config set --user aliases.x '["util", "exec", "--", "uvx", "git+https://github.com/neongreen/jj-run.git"]'Or in the file:
[aliases]x = ["util", "exec", "--", "uvx", "git+https://github.com/neongreen/jj-run.git"](Can’t use run because it’s already defined as a stub.)
Simplest form:
jj x <command> # run a command on all mutable&reachable changesFull form:
jj x -r <revset> [-e <error_strategy>] <command>-r,--revset: The revset of changes to process. If not provided, defaults toreachable(@, mutable())(same asjj fix).-e,--err-strategy: How to handle errors. One of:continue(default): Log errors and continue to next change.stop: Stop on the first error, but finish already started changes.fatal: Abort immediately on any error.
<command>: Required positional argument. The shell command to execute for each change (runs in the temp workspace).
Limitations
Section titled “Limitations”- jj-run can’t encapsulate its changes into a single operation, so to undo the changes you will have to use
jj op restore. - Doesn’t support
--ignore-immutableyet, so it will fail if the revset contains immutable changes. - Can’t change descriptions of existing commits (it’s “for-each-run-and-squash”, not “for-each-run”).
How it works
Section titled “How it works”- For each run, a unique temporary directory is created and a new
jjworkspace is added there. - The script finds the set of changes matching the revset (excluding the workspace’s own change and root).
- For each change:
jj new <change>is run in the temp workspace to create a mutable copy.- The provided command is run in the temp workspace.
- Output and errors are printed.
- After all changes are processed:
- The script attempts to rewrite parent snapshots for the new changes.
- The temp workspace is forgotten and all created changes are abandoned.
Error Handling
Section titled “Error Handling”- If a command fails, the script follows the selected error strategy:
continue: Logs the error and moves to the next change.stop: Stops processing new changes after the first error, but completes any already started ones.fatal: Exits immediately on the first error.
- All changes are isolated in the temp workspace. If the script crashes, cleanup is handled per session. The original repository is never modified by failed runs.
License
Section titled “License”MIT
- Add a quiet mode that only prints stdout/stderr of the command
- Provide CHANGE_ID, COMMIT_ID, REPO_PATH as env vars to the command
--jsonoutput- Add a
--readonlyflag that doesn’t create new changes, just runs the command for each - Handle
--ignore-immutable