git-loom is a Git CLI tool that weaves your branches together into a single integration branch. Inspired by tools like jujutsu and Git Butler, git-loom helps you work on multiple features simultaneously while keeping your branches organized and independent.
Think of it as a loom that weaves multiple threads (feature branches) into a single fabric (integration branch).
Core Concepts
Integration Branch
An integration branch merges multiple feature branches together, allowing you to:
- Work on several features at once in a single branch
- Test how features interact with each other
- Keep feature branches independent and manageable
- See a clear relationship between your integration and feature branches
You create an integration branch with git loom init. It tracks a remote upstream (e.g. origin/main) and serves as the hub for all your feature work.
Feature Branches
Feature branches are independent branches combined into the integration branch. You can manage them — reorder, amend, split — without leaving the integration context:
- Commit to the feature branch from the integration branch with
git loom commit - Move commits between branches with
git loom fold - Remove branches cleanly with
git loom drop - Create feature branches with
git loom branch
Tip
git loom commitis the primary way to create branches — it will prompt you for the branch name, or let you create a new one on the fly. Usegit loom branchonly for advanced cases where you need to create an empty branch ahead of time or for branching out loose commits.
Weaving
When a feature branch is created inside the integration branch, git-loom automatically weaves it into the topology — restructuring the linear history into a merge-based layout where each feature branch appears as a side branch joined by a merge commit. This is what makes git loom status able to display the clear branch-aware graph.
Short IDs
git-loom assigns compact, human-friendly identifiers to branches, commits, and files shown in git loom status. You can use these short IDs with any command instead of typing full hashes or branch names. What you see in the status output is what you type.
Quick Start
# Start on your main branch
git checkout main
# Create an integration branch
git loom init
# Create a commit on a feature branch (branch `feature-auth` is automatically created)
git loom commit -b feature-auth -m "add login form" zz
# Create a second commit on the branch
git loom commit -b feature-auth -m "improve login form" zz
# See the branch-aware status
git loom status
# Push a feature branch for review
git loom push feature-auth
Installation
Cargo (all platforms)
If you have Rust installed, the easiest way to install git-loom is via crates.io:
cargo install git-loom
Scoop (Windows)
Install git-loom with Scoop:
scoop bucket add narnaud https://github.com/narnaud/scoop-bucket
scoop install git-loom
Pre-built binaries
Download the latest archive for your platform from the Releases page:
| Platform | Archive |
|---|---|
| Linux x86_64 | git-loom-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | git-loom-aarch64-unknown-linux-gnu.tar.gz |
| macOS x86_64 | git-loom-x86_64-apple-darwin.tar.gz |
| macOS Apple Silicon | git-loom-aarch64-apple-darwin.tar.gz |
| Windows x86_64 | git-loom-x86_64-pc-windows-msvc.zip |
Extract the binary and place it somewhere on your PATH.
From Source
Requires Rust 1.90 or later.
git clone https://github.com/narnaud/git-loom.git
cd git-loom
cargo install --path .
Requirements
- Git 2.38 or later — git-loom checks the Git version at startup and will report an error if the version is too old.
ghCLI (optional) — needed for automatic GitHub PR creation withgit loom push. Install from cli.github.com.
Shell Setup
git-loom provides shell completions for tab-completion of commands and options.
Every subcommand and its aliases are completed, along with each command’s own flags. Git options passed after a -- are not completed — git’s option surface is not mirrored here.
PowerShell
Add the following to your PowerShell profile ($PROFILE):
Invoke-Expression (&git loom completions powershell | Out-String)
To find your profile path, run echo $PROFILE in PowerShell.
Clink
Clink adds completion support to cmd.exe. Create a file at %LocalAppData%\clink\git-loom.lua with:
load(io.popen('git loom completions clink'):read("*a"))()
Tutorial
This tutorial walks you through a typical git-loom workflow — from initializing an integration branch to working on multiple features simultaneously.
Getting Started
You have a project tracked by Git with a remote origin/main. Let’s set up git-loom.
git checkout main
git loom init
git-loom creates an integration branch that tracks origin/main. Run git loom status (or just git loom) to see the baseline:
╭─ zz [local changes]
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
A clean slate — the zz local changes section is empty, and you can see the upstream marker. Time to start working.
Your First Commit
You add a login form to your project — create src/auth.rs and templates/login.html. Check the status:
╭─ zz [local changes]
│ ⁕ src/auth.rs
│ ⁕ templates/login.html
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Your new files show up as untracked in the local changes section. Instead of the usual git add / git commit dance, you use git-loom to commit directly to a feature branch:
git loom commit -b feature-auth -m "add login form" zz
This single command:
- Stages all your changes (
zzmeans “everything”) - Creates the
feature-authbranch (it didn’t exist yet) - Weaves it into the integration topology
- Creates the commit on that branch
Check the status:
│╭─ fa [feature-auth]
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Your commit sits on feature-auth, shown as a side branch off the integration line. The short IDs fa (branch) and d0 (commit) are what you’ll type in subsequent commands.
You keep working on the same feature — add password validation and commit again:
git loom commit -b fa -m "add password validation" zz
Notice you can use the short ID fa instead of the full branch name. The status now shows:
│╭─ fa [feature-auth]
│● c2 add password validation
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Working on Multiple Features
While feature-auth is in progress, you want to start on a dashboard. You create src/dashboard.rs and templates/dashboard.html:
╭─ zz [local changes]
│ ⁕ src/dashboard.rs
│ ⁕ templates/dashboard.html
│
│╭─ fa [feature-auth]
│● c2 add password validation
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
No need to switch branches — just commit to a new one:
git loom commit -b feature-dashboard -m "add dashboard layout" zz
│╭─ fd [feature-dashboard]
│● e1 add dashboard layout
├╯
│
│╭─ fa [feature-auth]
│● c2 add password validation
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Two independent feature branches, both woven into the integration branch. You can build and test everything together while keeping the branches separate. This is the core of git-loom: work on multiple features simultaneously without branch switching.
Staying Up to Date
Your teammates have been pushing to origin/main. Time to pull their changes and rebase your work on top:
git loom update
This fetches upstream changes, rebases your integration branch (including all woven feature branches) on top, and updates submodules if any. Your working tree changes are automatically preserved.
│╭─ fd [feature-dashboard]
│● e1 add dashboard layout
├╯
│
│╭─ fa [feature-auth]
│● c2 add password validation
│● d0 add login form
├╯
│
● b2c3d4e (upstream) [origin/main] Teammate's latest commit
· a1b2c3d 2026-03-07 Latest upstream commit
The upstream marker moved forward — your branches are now rebased on top of the latest changes.
If any of your pushed feature branches have been merged and deleted on the remote, update will notice and offer to clean up the local branches:
# ! 1 local branch with a gone upstream:
# · feature-auth
# ? Remove them? [y/N]
Note
If a rebase conflict occurs,
updateaborts automatically and tells you the fullgit rebasecommand to re-run and resolve conflicts manually.
See also: update reference
Now that you know the basics, check out the recipe guides for common operations:
- Amending a Past Commit
- Fixing Up a Commit
- Splitting a Commit
- Moving a Commit Between Branches
- Moving Files Between Commits
- Uncommitting Changes
- Auto-absorbing Changes
- Pushing for Review
Amending a Past Commit
You realize the login form is missing a CSRF token. You fix src/auth.rs and check the status:
╭─ zz [local changes]
│ M src/auth.rs
│
│╭─ fa [feature-auth]
│● c2 add password validation
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
You want to amend this change into the original “add login form” commit (d0), not create a new commit.
$ git loom fold src/auth.rs d0
This stages src/auth.rs and amends it into commit d0. The branch topology stays the same — the commit just gains the new changes.
If you’ve already staged the files you want to amend, you can use the single-argument form:
$ git add src/auth.rs
$ git loom fold d0
This folds only the staged changes — any unstaged modifications to the same files are preserved.
To amend all working tree changes into a commit at once:
$ git loom fold zz d0
Tip
Use
git loom status -f d0to see which files are in a commit before and after amending.
See also: fold reference
Fixing Up a Commit
You have two commits on feature-auth and realize that c2 (“add password validation”) should really be part of d0 (“add login form”) — they’re logically the same change.
│╭─ fa [feature-auth]
│● c2 add password validation
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Fold the newer commit into the older one:
$ git loom fold c2 d0
Commit c2 disappears from history and its changes are absorbed into d0:
│╭─ fa [feature-auth]
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
The source commit must be newer than the target. The target keeps its message.
See also: fold reference
Splitting a Commit
A commit touches multiple files that should really be separate commits. Let’s look at the current state:
git loom status -f d0
│╭─ fa [feature-auth]
│● d0 add login form
│┊ d0:0 A src/auth.rs
│┊ d0:1 A src/validation.rs
│┊ d0:2 A templates/login.html
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
You want src/validation.rs in its own commit. Split the commit:
$ git loom split d0 -m "add validation helpers"
# ? Select files for the first commit
# > [x] src/validation.rs
# [ ] src/auth.rs
# [ ] templates/login.html
Select the files for the first commit — the remaining files stay in the second commit, which keeps the original message. The result:
│╭─ fa [feature-auth]
│● d1 add login form
│● d0 add validation helpers
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
The commit must have at least two files — otherwise there’s nothing to split. Both sides must get at least one file.
Tip
If you omit
-m, git-loom opens your editor for the first commit’s message.
See also: split reference
Moving a Commit Between Branches
You committed a logging helper to feature-auth by mistake — it belongs in feature-dashboard.
│╭─ fd [feature-dashboard]
│● e1 add dashboard layout
├╯
│
│╭─ fa [feature-auth]
│● a3 add logging helper
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Move it with fold:
$ git loom fold a3 fd
Commit a3 is removed from feature-auth and appended to feature-dashboard:
│╭─ fd [feature-dashboard]
│● a3 add logging helper
│● e1 add dashboard layout
├╯
│
│╭─ fa [feature-auth]
│● d0 add login form
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
You can also move a commit into a new branch in one step with --create:
$ git loom fold -c a3 feature-logging
See also: fold reference
Moving Files Between Commits
Sometimes a commit touches files that belong in different commits. Use the commit:index syntax shown by git loom status -f to move a single file.
First, check which files are in each commit:
$ git loom status -f
Note
-fwithout arguments shows files for all commits. You can pass specific short IDs (e.g.git loom status -f d0) to limit the output.
│╭─ fd [feature-dashboard]
│● e1 add dashboard layout
│┊ e1:0 A src/dashboard.rs
│┊ e1:1 A templates/dashboard.html
├╯
│
│╭─ fa [feature-auth]
│● d0 add login form
│┊ d0:0 M src/auth.rs
│┊ d0:1 A templates/login.html
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
You realize templates/login.html (index d0:1) would be better off in the dashboard commit. Move it:
$ git loom fold d0:1 e1
The file’s changes are removed from d0 and applied to e1:
│╭─ fd [feature-dashboard]
│● e1 add dashboard layout
│┊ e1:0 A src/dashboard.rs
│┊ e1:1 A templates/dashboard.html
│┊ e1:2 A templates/login.html
├╯
│
│╭─ fa [feature-auth]
│● d0 add login form
│┊ d0:0 M src/auth.rs
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
See also: fold reference
Uncommitting Changes
Sometimes you want to undo a commit or pull a file out of one — maybe to re-split changes differently. Here’s the starting point:
$ git loom status -f
│╭─ fa [feature-auth]
│● c2 add password validation
│┊ c2:0 M src/auth.rs
│● d0 add login form
│┊ d0:0 A src/auth.rs
│┊ d0:1 A templates/login.html
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Uncommitting a Commit
You decide c2 (“add password validation”) was premature — you want its changes back in the working tree. Fold it into zz (the working directory):
$ git loom fold c2 zz
The commit is removed from history and its changes appear as unstaged modifications:
╭─ zz [local changes]
│ M src/auth.rs
│
│╭─ fa [feature-auth]
│● d0 add login form
│┊ d0:0 A src/auth.rs
│┊ d0:1 A templates/login.html
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
Uncommitting a File
Instead of removing the whole commit, you just want to extract templates/login.html (index d0:1) from d0:
$ git loom fold d0:1 zz
The file is removed from the commit and appears as an untracked file in the working directory, leaving the rest of d0 intact:
╭─ zz [local changes]
│ ⁕ templates/login.html
│
│╭─ fa [feature-auth]
│● c2 add password validation
│┊ c2:0 M src/auth.rs
│● d0 add login form
│┊ d0:0 A src/auth.rs
├╯
│
● a1b2c3d (upstream) [origin/main] Latest upstream commit
See also: fold reference
Auto-absorbing Changes
You’ve been tweaking code across several files — fixing a typo in src/auth.rs, adjusting a layout in templates/dashboard.html. Each change was last touched by a different commit. Instead of manually folding each one, let git-loom figure it out:
git loom absorb
For each changed file, absorb splits the diff into hunks, blames each hunk’s original lines to find the originating commit, and folds each hunk into the right place — even when a single file has changes belonging to different commits.
Run a dry run first to see what would happen:
$ git loom absorb -n # or --dry-run
# src/auth.rs -> d0 "add login form"
# templates/dashboard.html -> e1 "add dashboard layout"
# src/shared.rs [hunk 1/2] -> d0 "add login form"
# src/shared.rs [hunk 2/2] -- skipped (pure addition)
# Dry run: would absorb 3 hunk(s) from 3 file(s) into 2 commit(s)
When a file has hunks going to different commits, each hunk is absorbed independently. Hunks that can’t be attributed are left in the working tree.
Note
Absorb works by blaming existing lines to find their originating commit. It handles modified and deleted lines well, but newly added lines (insertions that don’t replace existing code) cannot be traced to a commit and will be skipped. Use
foldfor those.
You can also restrict absorption to specific files:
git loom absorb src/auth.rs
See also: absorb reference
Selecting Hunks with -p
Sometimes you want to stage or commit only part of your changes — a few specific lines rather than entire files. The -p (patch) flag opens an interactive TUI that lets you pick individual hunks before the operation runs. It works with add, commit, and fold.
The TUI
╭─ Files ──────────╮╭─ Diff ─────────────────────────╮
│ M main.rs ││ [✓] Hunk 1/3 (staged) │
│ ▼ src/ ││ @@ -10,4 +10,6 @@ │
│ MM lib.rs ││ -old line │
│ A new.rs ││ +new line │
│ ?? README.md ││ │
╰──────────────────╯╰────────────────────────────────╯
Navigate: ↑/↓ | Switch Pane: tab | Toggle: space | Confirm: c or Enter | Quit: q or Esc
The left pane lists files with git status–style codes (M, MM, A, ??, D). The right pane shows diff hunks — check the ones you want, leave the rest unchecked, then confirm.
| Key | Action |
|---|---|
↑ / k, ↓ / j | Navigate up/down |
Tab / Shift+Tab | Switch between left and right pane |
Space | Toggle hunk (right pane) or all hunks in file/directory (left pane) |
c / Enter | Confirm selections |
q / Esc / Ctrl+C | Cancel without changes |
Staging hunks (add -p)
To stage a subset of changes before any commit:
$ git loom add -p
# Opens TUI showing all changed files
Filter to specific files:
$ git loom add -p src/auth.rs
$ git loom add -p a3 # using a short ID
Committing hunks (commit -p)
Commit only selected hunks to a feature branch, without staging anything first:
$ git loom commit -b feature-auth -p
# Opens TUI for all working tree changes
# Only selected hunks are committed to feature-auth
You can narrow the picker to specific files:
$ git loom commit -b feature-auth -p src/auth.rs -m "partial auth fix"
# TUI shows only src/auth.rs hunks
# Other staged files are saved aside and restored after the commit
Amending hunks into a past commit (fold -p)
Fold only selected working tree hunks into an existing commit:
$ git loom fold -p d0
# Opens TUI for all working tree changes
# Selected hunks are staged and folded into commit d0
Narrow to specific files by listing them before the target:
$ git loom fold -p src/auth.rs d0
# TUI shows only src/auth.rs hunks
# Selected hunks are folded into d0; the rest stay in the working tree
Common patterns
Split a file’s changes across two commits
You edited src/auth.rs and want different hunks in different branches.
$ git loom commit -b feature-auth -p src/auth.rs -m "tighten auth check"
# Pick the first hunk → committed to feature-auth
$ git loom commit -b feature-ui -p src/auth.rs -m "restyle auth form"
# Pick the remaining hunk → committed to feature-ui
Amend only part of a file into a past commit
$ git loom fold -p src/auth.rs d0
# Opens TUI filtered to src/auth.rs
# Pick only the hunks that belong in d0
# Unselected hunks stay in the working tree unchanged
Stage interactively, then commit
If you prefer to separate the two steps:
$ git loom add -p
# Select exactly what to stage
$ git loom commit -b feature-auth -m "fix auth check"
# Commits whatever is staged
Pushing for Review
Your feature-auth branch is ready. Push it to the remote:
$ git loom push fa
git-loom detects your remote type automatically and runs the appropriate commands:
- GitHub — pushes the branch, then checks if a PR exists. If a PR already exists, prints its URL (
PR updated: https://...). Otherwise creates a PR via theghCLI with a title and description auto-generated from the branch’s commits. - Azure DevOps — pushes the branch, then checks if a PR exists. If a PR already exists, prints its URL. Otherwise creates a PR via the
azCLI with a title and description auto-generated from the branch’s commits. - Gerrit — pushes to
refs/for/<target>(where<target>is your upstream branch, e.g.mainormaster). Review URLs from the Gerrit remote are displayed after the push. - Plain Git — pushes with
--force-with-lease.
If gh or az are not installed, the push still succeeds — you just won’t get the automatic PR creation.
If feature-auth is stacked on another branch in the same repository, the lower branch is pushed with it and each PR targets the branch below. GitHub links the PRs into a stack. Fork PRs instead target upstream and are not linked; see Stacked Branches.
If you just want to push without creating a PR (e.g. to back up your work):
$ git loom push fa --no-pr
When you omit the branch argument, git-loom shows an interactive picker:
$ git loom push
# ? Select branch to push
# > feature-auth
# feature-dashboard
See also: push reference
Resolving Conflicts
When a loom operation rewrites history and two commits touch the same lines, git can’t merge them automatically. Instead of aborting, loom pauses the operation and lets you fix the conflict before continuing.
What a Paused Operation Looks Like
$ git loom commit -b feature-auth -m "add auth middleware" zz
✓ Created branch `feature-auth` at `a1b2c3d`
! Conflicts detected — resolve them with git, then run:
loom continue to complete the commit
loom abort to cancel and restore original state
The process exits with code 0. Your work is safe — loom saved the operation
state to .git/loom/state.json and left the rebase paused at the conflicting
commit.
Step 1: Find the Conflicts
$ git status
You are currently rebasing branch 'integration' on 'a1b2c3d'.
(fix conflicts and then run "git rebase --continue")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/middleware.rs
The conflicting files are listed under Unmerged paths. You can also run
git diff to see the conflict markers inline.
Step 2: Resolve Each File
Open each conflicting file in your editor. Git inserts conflict markers to show both versions:
<<<<<<< HEAD
// existing middleware code
=======
// your new auth middleware
>>>>>>> feature-auth
Edit the file to keep what you want — either one side, the other, or a
combination of both — and remove the <<<<<<<, =======, and >>>>>>>
markers entirely.
Tip
Most editors have built-in conflict resolution UI. In VS Code, click Accept Current, Accept Incoming, or Accept Both above each conflict block. For a dedicated mergetool, run
git mergetool. See the git documentation on resolving conflicts for more detail.
Step 3: Mark Files as Resolved
Once a file is clean (no more conflict markers), stage it:
$ git add src/middleware.rs
For a file that should be deleted entirely as the resolution, use:
$ git rm src/middleware.rs
Repeat for every conflicting file. When git status shows no more unmerged
paths, you’re ready to continue.
Step 4: Continue or Abort
To finish the operation:
$ git loom continue
✓ Created commit `b2c3d4e` on branch `feature-auth`
Loom runs git rebase --continue internally, completes the interrupted
command’s post-rebase work (restoring staged patches, printing the success
message), and removes the saved state.
To cancel and go back to where you started:
$ git loom abort
✓ Aborted `loom commit` and restored original state
Abort rolls back all branch refs, removes any branches created during the
operation, and restores any staged changes that were saved aside. For commit,
the content you were committing comes back as unstaged working-tree changes so
nothing is lost.
Multiple Conflicts
If your branch has several commits that conflict, each loom continue may
pause again at the next one. Repeat the resolve → git add → loom continue
cycle until the operation completes:
$ git loom update
! Conflicts detected...
$ git add src/api.rs && git loom continue
! Conflicts remain — resolve them and run `loom continue` again
$ git add src/models.rs && git loom continue
✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
If You Finished the Rebase Yourself
That’s fine. If you resolved the conflicts with raw git commands and ran
git rebase --continue to the end, loom notices the rebase is no longer
active and skips straight to the post-rebase work when you run
loom continue: worktree syncs, submodule updates, branch cleanup, and
finally removing the state file.
The blocked-command error tells you which case you are in:
✗ A `loom update` is paused, but no rebase is in progress.
› If you finished it yourself, run `loom continue` to wrap up and clear the state.
› Run `loom abort` to discard it instead.
If the State File is Stale
If loom blocks you with a “paused operation” error but you know no operation
is actually in progress (e.g., after a crash or force-reset), run
loom continue to finish up, or loom abort to discard the operation.
As a last resort you can delete the state file by hand:
rm .git/loom/state.json
In a linked worktree it lives under that worktree’s git directory instead:
.git/worktrees/<name>/loom/state.json.
Warning
Only do this if you are certain no loom operation is paused. If a rebase is still in progress, run
loom abortinstead — that also aborts the rebase and restores your branch refs.
See Also
continue— reference forloom continueabort— reference forloom abort- Git documentation: Basic Merge Conflicts
Commands Overview
Usage: git-loom [OPTIONS] [COMMAND]
Workflow:
init Initialize a new integration branch
update, up Pull-rebase and update submodules
push, pr Push a branch to remote
agent Install the loom skill for AI agents
Staging:
add Stage files using short IDs or paths [-p for interactive hunks]
Commits:
commit, ci Create a commit on a feature branch [-p for interactive hunks]
fold Amend, fixup, or move commits [-p for interactive hunks] [amend, am, fixup, mv, rub]
absorb Auto-distribute changes into originating commits
split Split a commit into two [-p for interactive hunks]
swap Swap two commits
reword, rw Reword a commit message or rename a branch
drop, rm Drop a change, commit, or branch
Branches:
branch, br Manage feature branches (create, merge, unmerge)
switch, sw Switch to any branch for testing (without weaving)
Inspection:
status Show the branch-aware status (default command)
tui Interactive status TUI (tree + diff, with actions)
show, sh Show commit details (like git show)
diff, di Show a diff using short IDs (like git diff)
trace Show the latest command trace
Recovery:
continue, c Resume a paused operation after resolving conflicts
abort, a Cancel a paused operation and restore original state
Options:
--no-color Disable colored output
--agent Machine-readable JSON status output for AI agents (see also LOOM_AGENT)
--theme <THEME> Color theme for graph output [default: auto] [possible values: auto, dark, light]
-h, --help Print help (see more with '--help')
-V, --version Print version
Running git loom with no command is equivalent to git loom status.
All commands that accept a target (commit, branch, or file) support short IDs — the compact identifiers shown in the status output. You can also use full git hashes, branch names, or partial hashes.
Passing Options to Git
show, diff, commit and add each wrap a single git command. Anything
after a -- separator is handed to that command untouched:
git loom show -- --stat
git loom diff ab..d0 -- --name-only
git loom commit -m "wip" -- --no-verify
git loom add zz -- -f
Before the separator loom parses strictly, so an option it doesn’t define is an
error rather than a guess — the message tells you to move it after the --.
That also means a flag keeps loom’s meaning on loom’s side of the separator and
git’s meaning on git’s: git loom diff -a is loom’s --all, while
git loom diff -- -a is git’s --text.
Forwarded arguments land after the revisions loom resolved and before any
pathspec loom builds, so options stay options and paths stay paths. Because the
tokens are never inspected, values may be attached or detached — -U5,
--unified=5 and -S x all reach git as written.
When you forward arguments to add or commit, loom steps back: the git
command runs uncaptured so its own output reaches you, and loom stops narrating
what it can no longer vouch for. (Under --agent those two stay captured —
there is nobody there to close an editor.) show and diff are always
uncaptured; displaying is all they do.
The other commands don’t take a --: they either render their own output or
drive a rebase, where there is no single git command to forward to.
init
Initialize a new integration branch tracking a remote upstream. This is the entry point for starting a git-loom workflow.
Usage
git loom init [name]
Arguments
| Argument | Description |
|---|---|
[name] | Branch name (optional, defaults to integration) |
What It Does
- Creates a new local branch at the upstream tip
- Configures upstream tracking (e.g.
origin/main) - Switches HEAD to the new branch
All three happen in a single atomic operation.
Upstream Detection
The upstream is resolved automatically in priority order:
- Current branch’s upstream — if you’re on
maintrackingorigin/main, the integration branch will also trackorigin/main - Remote scan — scans all remotes for branches named
main,master, ordevelop - Interactive prompt — if multiple candidates are found, you’re asked to choose
- Error — if no remote tracking branches are found
Examples
Default
git loom init
# Initialized integration branch 'integration' tracking origin/main
Custom name
git loom init my-integration
# Initialized integration branch 'my-integration' tracking origin/main
Error: branch already exists
git loom init
# error: Branch 'integration' already exists
Error: no remotes
git loom init
# error: No remote tracking branches found.
# Set up a remote with: git remote add origin <url>
Prerequisites
- Must be in a git repository with a working tree
- At least one remote with a fetchable branch must be configured
update
Pull-rebase the integration branch onto the latest upstream and update submodules.
Alias: up
Usage
git loom update [-y]
Options
| Option | Description |
|---|---|
-y, --yes | Skip confirmation prompt when removing branches that are fully merged upstream or have a gone upstream |
Configuration
| Config | Description |
|---|---|
loom.pruneGoneBranches | When true, always remove fully merged and gone-upstream branches without prompting (same as --yes). Set with git config loom.pruneGoneBranches true. |
What It Does
Fetch
Runs git fetch --tags --force --prune against the tracked remote. Force-updates moved tags and prunes deleted remote branches from local tracking refs.
In a fork workflow, where loom push sends feature branches to another remote (see push), that remote is fetched too with git fetch --prune <remote> — otherwise its tracking refs go stale and branches deleted on the fork are never seen as gone. Tags are only fetched from the tracked remote. If the fork is unreachable, loom warns and continues with the update.
Upstream Commit Filtering
Before rebasing, loom scans every feature-branch commit against the new upstream and drops any that are already present. Two strategies are applied:
- Direct merge — if the upstream is a descendant of the commit’s OID, the commit was merged directly.
- Cherry-pick — if the commit’s patch-ID matches a new upstream commit, it was cherry-picked.
If an entire branch empties out after filtering, its section and merge entry are removed from the rebase todo. This also covers a stacked branch whose commits all landed upstream while the branch built on top of it did not. Such fully merged branches are offered for removal after the rebase (see Branch Cleanup below).
Rebase
Replays local commits onto the updated upstream using a topology-aware weave model — ensuring new upstream commits land on the base line, not inside feature branch sections. Uncommitted working tree changes are automatically stashed and restored.
If the current branch has no weave topology (a plain tracked branch), loom falls back to a standard git rebase --autostash --update-refs --rebase-merges.
Submodule Update
If .gitmodules exists, runs git submodule update --init --recursive.
Branch Cleanup
Lists local branches that are fully merged upstream (every commit was filtered out before the rebase) and local branches whose upstream tracking ref was pruned in the fetch step, then prompts once to remove them. Pass -y to skip the prompt. Branches are force-deleted one by one; each success message shows the tip the branch had, so it can be revived. A branch that cannot be deleted (checked out in another worktree, for instance) is skipped with a warning rather than aborting the cleanup.
Examples
Standard update
git loom update
# ✓ Fetched latest changes
# ✓ Rebased onto upstream
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
Cherry-picked commits auto-dropped
# feature-a had commits F1, F2, F3 — upstream cherry-picked F1 and F2
git loom update
# ✓ Fetched latest changes
# ✓ Rebased onto upstream
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
# F1 and F2 are silently dropped; F3 remains on feature-a
With submodules
git loom update
# ✓ Fetched latest changes
# ✓ Rebased onto upstream
# ✓ Updated submodules
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
Branch merged upstream
git loom update
# ✓ Fetched latest changes
# ✓ Rebased onto upstream
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
# ! 1 local branch fully merged upstream:
# › feature-x
# ? Remove it? [y/N]
Gone upstream branches
git loom update
# ✓ Fetched latest changes
# ✓ Rebased onto upstream
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
# ! 2 local branches with a gone upstream:
# › feature-x
# › feature-y
# ? Remove them? [y/N]
Skip the removal prompt
git loom update -y
# ✓ Fetched latest changes
# ✓ Rebased onto upstream
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
# ✓ Removed branch `old-feature`
# ✓ Removed branch `closed-pr`
The same behavior can be made permanent with git config loom.pruneGoneBranches true.
Gone branch that cannot be deleted
git loom update
# ✓ Fetched latest changes
# ✓ Rebased onto upstream
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
# ! 1 local branch with a gone upstream:
# › work-in-progress
# Remove it? [y/N] y
# ! Skipped branch `work-in-progress` — could not delete it (run `loom trace` for the git error)
Conflicts
If the rebase encounters a conflict, loom saves state and pauses:
git loom update
# ✓ Fetched latest changes
# ! Conflicts detected — resolve them with git, then run:
# loom continue to complete the update
# loom abort to cancel and restore original state
After resolving:
git add <resolved-files> && git loom continue
# ✓ Updated branch `integration` with `origin/main` (abc1234 Latest commit)
Or cancel:
git loom abort
# ✓ Aborted `loom update` and restored original state
See continue and abort for details.
Prerequisites
- Must be in a git repository with a working tree
- Current branch must have upstream tracking configured (use
initfirst) - Network access to the remote
push
Push a feature branch to the remote, together with the branches it is stacked on. Automatically detects the remote type and uses the appropriate push strategy.
Usage
git loom push [branch] [--no-pr] [-f|--force]
Arguments
| Argument | Description |
|---|---|
[branch] | Branch name or short ID (optional; interactive picker if omitted) |
Flags
| Flag | Description |
|---|---|
--no-pr | Push without creating a PR or Gerrit review (see below) |
-f, --force | Push with --force instead of --force-with-lease --force-if-includes |
Stacked Branches
A branch is stacked on another when it is built on top of it — its oldest commit’s parent is the other branch’s tip. That is exactly what git loom status draws with │├─ between two branches. Stacks can be several branches deep.
Pushing a stacked branch pushes what its review depends on. With d on c on b on a:
git loom push b
# ✓ Pushed `a`, `b` to `origin`
# Re-pushed above `b`: `c`
# ! Not pushed above `b`: `d`
# Run `loom push d` to publish them
a,b— the branch and everything below it, always.c— a branch abovebwhose remote is no longer at its tip, whether the rebase that rewrotebrewrote it too or it carries commits of its own. Either way the server no longer matches the stack, so it is re-pushed. No PR is created for it.d— a branch abovebthat was never pushed is only mentioned. It has no remote ref to keep in step, and creating one is your call.
Everything goes out in one atomic git push (one lease check, and a refused branch leaves the others untouched). Branches hidden by loom.hideBranchPattern are never pushed: pushing one, or a branch stacked on one, is refused. Gerrit needs none of this: refs/for/ already uploads the whole chain of changes.
Pull requests per layer
Each pushed branch gets a PR that targets the branch below it; the bottom one targets your upstream branch (main):
| Remote | What loom does |
|---|---|
| GitHub | Creates missing PRs with --base <branch below>, retargets an existing PR whose base is wrong (PR retargeted to 'a': …), then links the PRs into a GitHub stack |
| GitLab | Pushes each branch with merge_request.target=<branch below>; GitLab shows the dependency and retargets on merge. A re-pushed upper branch keeps its existing MR and gets no new one |
| Azure DevOps | Not supported — pushing a stacked branch is refused (see Azure DevOps) |
| Gerrit | Unchanged |
On GitHub the stack shows a stack map on every PR, reviewers see one layer at a time, and when the bottom PR merges GitHub rebases and retargets the ones above by itself. Nothing is stored locally: loom reads each PR’s stack membership back through gh api on every push and creates or extends the stack as needed. In a stack, new PRs are created directly and their URLs printed instead of opening the browser, because the stack can only be linked once every PR exists.
git loom push b
# ✓ Pushed `a`, `b` to `origin`
# ✓ PR created: https://github.com/owner/repo/pull/41
# ✓ PR created: https://github.com/owner/repo/pull/42
# ✓ Stack #7 registered with 2 PRs
GitHub does not support stacks across forks: in a fork workflow every layer still gets its PR, but they all target the upstream branch and a warning says so. If the stack API is unavailable on your host, or a PR’s stack membership cannot be read, the PR bases are still set and loom points you at the gh-stack extension.
Creating a stack
A stack is just a shape of your branches:
git loom branch part-1 -t <commit inside feature>splits a branch into two stacked layers.git loom branch b -t afollowed bygit loom commit -b bputs b’s first commit on top of a.
Amending any layer (fold, absorb, reword) rewrites the layers above it as well; the next push of any layer re-publishes them. Adding a commit to a lower layer with commit -b or fold <commit> <branch> is not supported yet — commit to the top layer or fold into an existing commit instead.
When the bottom PR merges, git loom update rebases your integration branch and the stack shrinks from the bottom.
Remote Type Detection
Detection priority (first match wins):
- Explicit config —
git config loom.remote-typeset togithub,gitlab,azure,gerrit, orplain - URL heuristics — remote URL contains
github.com→ GitHub - URL heuristics — remote URL contains
gitlab→ GitLab - URL heuristics — remote URL contains
dev.azure.com→ Azure DevOps - Hook inspection —
.git/hooks/commit-msgcontains “gerrit” → Gerrit - Gerrit confirmation — if nothing matched but the remote URL uses Gerrit’s standard SSH port (
:29418/) or recent commits carry aChange-Id:trailer, you are asked to confirm; the answer is saved asloom.remote-type(gerritorplain) so you are only asked once - Fallback — Plain Git
Self-hosted GitLab whose hostname does not contain gitlab (e.g. invent.kde.org) is not auto-detected — set git config loom.remote-type gitlab. Even without detection, a plain push still surfaces the MR link the server prints.
Push Remote Selection
Detection priority (first match wins):
- Explicit config —
git config loom.push-remote <remote> - GitHub fork convention — if the integration remote is named
upstreamandoriginexists, push toorigin - Fallback — integration branch’s remote
For non-standard fork setups (e.g., integration branch tracks origin but you push to personal), set:
git config loom.push-remote personal
Push Strategies
Plain Git (default)
git push --force-with-lease --force-if-includes -u <remote> <branch>
Uses --force-with-lease because woven branches are frequently rebased. --force-if-includes adds extra safety.
When the remote rewrote your branch
Landing the pull request below a stacked branch makes the forge rebase that branch for you, server-side. The tip on the remote is then a commit your clone never held, and --force-if-includes refuses every later push of that branch — with a hint to git pull that does not help, because merging it back only adds content you already have.
loom names the flag that does:
✗ git push failed
› If `feature-a` has diverged on the remote, push again with `loom push feature-a -f`
That is the push you ran, forced — --no-pr and the branch you named both survive it. loom does not check whether the remote really diverged, or whether what is there is yours: a colleague’s commit and a forge’s rebase look the same from your clone, and only you can tell them apart. Fetch and look if you are unsure. Typing the flag is what makes it your call.
Pass -f / --force to push with plain --force instead, for when the lease check refuses a push you know is correct. It applies to every remote type except a Gerrit refs/for/ review push, which never forces. It also applies to every branch the push contains, not only the one named: a stack goes out in a single git push, so forcing it overwrites the downstack and re-published upstack branches too.
Any remote: lines containing an http(s) URL are shown below the success message, so the MR/PR creation link that servers like GitLab print on push is visible even when the remote type was not detected.
GitHub
Pushes the branch with --force-with-lease, then checks whether a PR already exists for the branch:
- PR exists — prints the PR URL (
PR updated: https://github.com/owner/repo/pull/42) without opening the browser - No PR — creates the PR via
gh pr createwith an auto-generated title and description (see PR Title and Description below)
For a stacked branch, see Stacked Branches. If gh is not installed, the push succeeds with a message suggesting to install it.
In a fork workflow (tracking upstream/main), pushes go to origin (your fork) and the PR targets the upstream repository automatically.
If the branch being pushed is the upstream target branch itself, PR creation is skipped.
GitLab
git push --force-with-lease --force-if-includes \
-o merge_request.create -o merge_request.target=<target> -u <remote> <branch>
Uses GitLab push options so the server creates a merge request (or points to the existing one) during the push. The MR URL GitLab prints is shown below the success message. No extra CLI tool is required. If the branch being pushed is the upstream target branch itself, the MR push options are skipped.
Azure DevOps
Azure has no stacked pull requests, and az repos pr update cannot retarget an existing one either, so a stack would land as PRs whose base says nothing a reviewer can rely on. Pushing a stacked branch is refused before anything reaches the remote:
✗ `b` is stacked on `a` — Azure DevOps has no stacked pull requests
Land the branches below it first, or push without a PR (`--no-pr`)
--no-pr still pushes the whole stack; it is the pull requests Azure cannot express.
For a branch of its own, loom pushes it with --force-with-lease, then checks whether a PR already exists:
- PR exists — prints the PR URL (
PR updated: https://dev.azure.com/...) without opening the browser - No PR — creates the PR via
az repos pr createwith an auto-generated title and description (see PR Title and Description below)
The organization, project and repository are read from the remote URL and passed explicitly; --detect is only used when the URL cannot be parsed. If az is not installed, the push succeeds with a message suggesting to install it.
Gerrit
git push <remote> <branch>:refs/for/<target>
Uses the refs/for/ refspec. No topic is set. After pushing, any review URLs returned by Gerrit are extracted from the remote output and displayed below the success message.
PR Title and Description
When creating a new PR (GitHub or Azure DevOps), git-loom auto-generates the title and description from the commits the PR contains — those between its base and the branch tip. A stacked PR includes only its branch’s own commits. When a PR targets the trunk instead (in a fork, or because its lower layer was dropped as merged), it also includes commits from the lower layers, so the description always matches the diff a reviewer sees:
- Single commit — the commit subject becomes the PR title and the commit body becomes the description.
- Multiple commits — you are prompted for a PR title (the prompt names the branch). The description is built by concatenating all commit messages (oldest to newest), separated by
---dividers. - Empty branch — the branch name is used as the title with an empty description.
Pushing Without a PR or Review
Use --no-pr when you want to push a branch to the remote without triggering PR or review creation — for example, to back up a branch, share work-in-progress, or push to a staging ref.
| Remote type | --no-pr behavior |
|---|---|
| Plain | Same as normal (force-with-lease push) |
| GitHub | Skips gh pr create |
| GitLab | Plain push without merge_request.create push options |
| Azure DevOps | Skips az repos pr create |
| Gerrit | Plain push to branch ref instead of refs/for/ (see below) |
Gerrit: wip/ prefix warning
In Gerrit, pushing directly to a branch ref (not refs/for/) creates a remote branch that requires a project admin to delete. To protect against accidental non-deletable branches, --no-pr on Gerrit prompts when the branch name doesn’t start with wip/:
? Branch `feature-a` is not prefixed with `wip/` — a Gerrit admin will be needed to delete the remote branch later
> Push as `feature-a` (admin required to delete it later)
Push as `wip/feature-a` instead
Cancel
- Push as-is — pushes to
remote/feature-a; an admin is needed to delete it later - Push as
wip/<branch>— pushes with refspecfeature-a:wip/feature-a; your local branch name is unchanged - Cancel — aborts the push
If the branch already starts with wip/, no prompt is shown.
Examples
Push to a plain remote
git loom push feature-a
# Pushed 'feature-a' to origin
Push to GitHub (new PR)
git loom push feature-a
# Pushed 'feature-a' to origin
# (browser opens to PR creation page)
Push to GitHub (PR already exists)
git loom push feature-a
# Pushed 'feature-a' to origin
# PR updated: https://github.com/owner/repo/pull/42
Push a stacked branch to GitHub
git loom push feature-b # feature-b is stacked on feature-a
# Pushed `feature-a`, `feature-b` to `origin`
# PR updated: https://github.com/owner/repo/pull/41
# PR created: https://github.com/owner/repo/pull/42
# Stack #7 registered with 2 PRs
Push to Azure DevOps (new PR)
git loom push feature-a
# Pushed 'feature-a' to origin
# (browser opens to PR creation page)
Push to Azure DevOps (PR already exists)
git loom push feature-a
# Pushed 'feature-a' to origin
# PR updated: https://dev.azure.com/org/project/_git/repo/pullrequest/42
Push to Gerrit
git loom push feature-a
# Pushed 'feature-a' to origin (Gerrit: refs/for/main)
# › https://gerrit.example.com/c/project/+/12345
Interactive selection
git loom push
# ? Select branch to push
# > feature-a
# feature-b
# Pushed 'feature-a' to origin
Push without opening a PR (GitHub)
git loom push feature-a --no-pr
# Pushed 'feature-a' to origin
Push without a review, renaming to wip/ (Gerrit)
git loom push feature-a --no-pr
# ? Branch `feature-a` is not prefixed with `wip/`...
# > Push as `wip/feature-a` instead
# Pushed 'feature-a' to origin as 'wip/feature-a'
Override remote type
git config loom.remote-type gerrit
git loom push feature-a
# Pushed 'feature-a' to origin (Gerrit: refs/for/main)
# › https://gerrit.example.com/c/project/+/12345
Prerequisites
- Must be on an integration branch with upstream tracking
- The target branch must be woven into the integration branch
- Network access to the remote
ghCLI (optional, for GitHub PR creation)azCLI (optional, for Azure DevOps PR creation)
agent
Set up AI agent integration: install the loom skill for an AI coding agent, and drive loom itself with the machine-readable --agent mode.
Usage
git loom agent init [<agent>] [--project]
Arguments
| Argument | Description |
|---|---|
<agent> | AI agent to install the skill for. Currently claude (default). |
Options
| Option | Description |
|---|---|
--project | Install into the repository (.claude/skills/git-loom/SKILL.md) instead of the home directory |
agent init is unrelated to init, which sets up an integration branch.
What It Does
agent init
Installs a skill file at ~/.claude/skills/git-loom/SKILL.md (or under the work tree root with --project) that teaches the agent to use loom instead of raw git — including the --agent invocation rules below. Re-run it after upgrading loom to refresh the skill:
- File absent → created (
Installed Claude skill at ...) - File differs → overwritten (
Updated Claude skill at ...) - File identical → untouched (
Claude skill already up to date)
Keeping the skill up to date
The skill is compiled into the loom binary, so loom can see when an installed copy no longer matches what it ships. In agent mode every invocation checks the installed skills — the home one, plus the in-repo one when inside a work tree — and warns on any that differ:
{"status":"ok","messages":["The Claude git-loom skill at `~/.claude/skills/git-loom/SKILL.md` differs from the one this loom ships. Run `git-loom agent init` to refresh it (local edits are overwritten). Restart Claude Code to pick up the new skill."]}
The notice exists only in the JSON — it is not printed as a ! line, since the agent parses the JSON and would otherwise read it twice. It rides on ok and paused responses; an error response carries no messages, and the check simply reports again on the next command that succeeds. Nothing is rewritten automatically, and a location with no skill installed is never mentioned.
Because the comparison is byte-for-byte, the installed skill is not a file to edit: any local change is reported as stale, and agent init overwrites it.
Agent mode (--agent)
The global --agent flag (or the LOOM_AGENT environment variable, any value except 0) makes every loom invocation end with exactly one JSON status as the last line of stderr; stdout stays reserved for command payload (status graph, show/diff output).
| Status | Exit code | Meaning |
|---|---|---|
ok | 0 | Success. messages collects the progress lines, including skipped optional follow-ups. |
needs_input | 10 | A prompt would have opened; nothing was changed. options lists the choices, hint the command to re-run. allow_other: true means a new value is also accepted. |
needs_confirmation | 10 | A yes/no question would have opened; nothing was changed. |
paused | 0 | A rebase stopped on conflicts — resolve, then continue or abort. |
error | 1 | The command failed. |
In agent mode:
- Interactive prompts never render — they answer
needs_input/needs_confirmationinstead. -p/--patchis rejected (the hunk picker is a full-screen UI).commit,split, andrewordrequire-m(no editor is opened).pushnever opens a browser: PR creation is skipped and reported inmessages.updateskips the gone-branch pruning question (use-yto prune).show/diffdisable the git pager.
Agent mode is never inferred from a missing terminal — it must be requested explicitly.
Examples
Install the Claude skill
git loom agent init
# ✓ Installed Claude skill at `C:\Users\me\.claude\skills\git-loom\SKILL.md`
# › Restart Claude Code to pick up the new skill
Install into the current repository
git loom agent init claude --project
# ✓ Installed Claude skill at `D:\myrepo\.claude\skills\git-loom\SKILL.md`
An agent commits without picking a branch
git loom commit --agent -m "Fix login"
# {"status":"needs_input","kind":"select","prompt":"Select target branch",
# "options":["feature-auth","feature-ui"],"allow_other":true,
# "hint":"re-run with: loom commit -b <branch> -m <message> [files...] (a new name creates the branch), or -i for the integration branch itself"}
git loom commit --agent -b feature-auth -m "Fix login"
# {"status":"ok","messages":["Created commit `1a2b3c4` on branch `feature-auth`"]}
A conflicting update
git loom update --agent -y
# {"status":"paused","message":"Conflicts detected — the `loom update` is paused",
# "hint":"resolve conflicts, stage them, then run: loom continue (or loom abort)"}
# resolve the conflicts, then:
git loom continue --agent
# {"status":"ok","messages":["Updated branch `integration` with `origin/main`"]}
Prerequisites
agent init: a resolvable home directory (or a git repository with--project)- Agent mode: none beyond each command’s own prerequisites
add
Stage files into the git index using short IDs, paths, or zz for all — with optional interactive hunk selection.
Usage
git-loom add [-p] [<files...>] [-- <git args>...]
File arguments are optional: with none, add opens the same interactive hunk selector as -p, showing all changed files.
Arguments
| Argument | Description |
|---|---|
<files...> | Files to stage: short IDs from loom status, relative paths, or zz to stage everything |
Options
| Option | Description |
|---|---|
-p, --patch | Open the interactive hunk selector TUI |
Git Options
Everything after a -- separator goes to git add untouched, ahead of the pathspec loom builds — see Passing Options to Git:
git loom add zz -- -f # stage an ignored file too
git loom add src/main.rs -- -N # record the path, not the content
Forwarded arguments run uncaptured, so git’s own output (a --dry-run listing, -v) reaches you, and loom drops its own “Staged N file(s)” line — what was staged is the option’s business, not loom’s to claim.
Interactive staging (-p, or no file arguments) applies a patch rather than running git add, so it takes no forwarded arguments.
What It Does
Plain Staging
Resolves each argument to a file path (via short ID or filename) and stages it. If any argument is zz, all changes are staged immediately regardless of other arguments.
Prints "Staged N file(s)" on success, or "Staged all changes" when zz is used.
With no file arguments, add opens the hunk selector below instead of staging whole files.
Interactive Hunk Staging (-p)
Opens a two-pane TUI showing all staged and unstaged hunks across the affected files. Staged hunks start selected; unstaged hunks start deselected. The user can toggle individual hunks (or entire files/directories) in either direction, then confirm to apply all changes atomically.
On confirm, prints "Applied N change(s) across M file(s)" or "No changes to apply" if nothing was toggled.
File Resolution
Arguments (in both plain and -p modes) are resolved in this order:
zz— always stages everything (plain mode) or shows all files (-pmode)- Short IDs — file short IDs from
loom statusoutput (e.g.a3,0f) - Plain paths — relative file paths (e.g.
src/main.rs)
Examples
Stage a file by short ID
git-loom add a3
# Staged 1 file(s)
Stage multiple files
git-loom add a3 0f src/lib.rs
# Staged 3 file(s)
Stage everything
git-loom add zz
# Staged all changes
Interactive hunk selection for all files
git-loom add -p
# Opens TUI — confirm with c/Enter, cancel with q/Esc
Interactive hunk selection for a specific file
git-loom add -p src/main.rs
# Opens TUI filtered to src/main.rs hunks
Interactive hunk selection by short ID
git-loom add -p a3
# Opens TUI filtered to the file identified by short ID a3
Prerequisites
- Must be in a git repository with a working tree (not bare)
- At least one file must have changes (staged, unstaged, or untracked) for
-pmode
commit
Create a commit on a feature branch without leaving the integration branch.
Usage
git loom commit [-b <branch> | -i] [-m <message>] [-p] [files...] [-- <git args>...]
Alias: ci
Options
| Option | Description |
|---|---|
-b, --branch <branch> | Target feature branch (name or short ID). Prompts if omitted. |
-i, --integration | Commit to the integration branch itself (loose commit), skipping the branch prompt. Mutually exclusive with -b. |
-m, --message <message> | Commit message. Opens editor if omitted. |
-p, --patch | Interactively select hunks to stage before committing. |
File Arguments
| Argument | Description |
|---|---|
| (none) | Uses already-staged files (index as-is) |
zz | Stages all unstaged changes (like git add -A) |
| short IDs / filenames | Stages only those specific files |
When zz appears alongside other file arguments, zz wins and stages everything.
Git Options
Everything after a -- separator goes to git commit untouched — see Passing Options to Git:
git loom commit -m "wip" -- --no-verify
git loom commit -m "fix" -- --signoff
git loom commit -m "port" -- "--author=Someone <someone@example.com>"
They shape the commit loom creates, not the rebase that relocates it onto the feature branch.
What It Does
- Stage — applies the staging rules based on file arguments
- Branch resolution — determines the target feature branch
- Message resolution — gets the commit message (flag or editor)
- Commit — creates the commit
- Relocate — moves the commit to the target feature branch, updating all branch refs and integration topology automatically
Patch Mode
With -p, an interactive TUI opens before staging, letting you pick individual hunks to include in the commit. Any file arguments narrow the picker to those files; omitting them (or using zz) shows all changes.
If specific files are given alongside -p, any other staged files are saved aside first so they don’t accidentally end up in the commit. They are restored automatically afterward.
Loose Commit
When -b is omitted and the integration branch name matches the upstream’s local counterpart (e.g. main tracking origin/main), the commit is created directly on the integration branch as a loose commit. No branch targeting or rebase is needed. This works regardless of whether local commits or woven branches already exist.
Branches with names that differ from their upstream (e.g. integration tracking origin/main) need -b or -i.
-i forces a loose commit on any integration branch, whatever its name and whatever branches are woven into it — for the occasional change that belongs to the integration branch itself. Unlike git commit -i, it selects the target of the commit, not extra paths to include.
Branch Resolution
When the integration branch has diverged (woven branches exist):
- If
-bmatches a woven feature branch: uses it - If
-bmatches an unwoven branch: error - If
-bdoesn’t match any branch: creates a new branch at the merge-base and weaves it - If
-bis omitted: interactive picker with all woven branches + option to create a new one - If
-iis given: no branch resolution at all — the commit lands on the integration tip
New Branch Creation
When the target branch doesn’t exist, git-loom validates the name, creates the branch at the merge-base, and weaves it into the integration topology — all automatically.
Examples
Interactive
git loom commit
# ? Select target branch
# > feature-auth
# feature-ui
# (opens editor for commit message)
Fully specified
git loom commit -b feature-auth -m "add password validation" zz
# Stages all changes, commits to feature-auth
Specific files by short ID
git loom commit -b feature-auth ar -m "fix auth check"
# Stages only src/auth.rs (short ID: ar), commits to feature-auth
To a new branch
git loom commit -b feature-logging -m "add request logging" zz
# Creates feature-logging, weaves it, stages all, commits
Loose commit on a fresh integration branch
git loom commit -m "initial scaffold" zz
# No -b flag, branch matches remote → creates loose commit directly
Loose commit on a custom-named integration branch
git loom commit -i -m "bump integration config" zz
# Commits on the integration tip, no branch picker
Interactive hunk selection
git loom commit -b feature-auth -p -m "fix auth check"
# Opens hunk picker for all changes
# Only selected hunks are staged and committed to feature-auth
Hunk selection for specific files
git loom commit -b feature-auth -p ar -m "partial auth fix"
# Opens hunk picker filtered to src/auth.rs
# Other staged files are saved aside and restored after the commit
Conflicts
If the rebase that moves the commit to its target branch hits a conflict, the
operation is paused rather than aborted. The committed content is safe in
git history; loom saves recovery state to .git/loom/state.json and exits
with code 0.
git loom commit -b feature-auth -m "add auth" zz
# ✓ Created branch `feature-auth` at `a1b2c3d`
# ! Conflicts detected — resolve them with git, then run:
# loom continue to complete the commit
# loom abort to cancel and restore original state
Resolve conflicts, then:
git add <resolved-files>
git loom continue
# ✓ Created commit `b4c5d6e` on branch `feature-auth`
Or cancel and return to the original state (the commit content comes back as unstaged working-tree changes):
git loom abort
# ✓ Aborted `loom commit` and restored original state
See continue and abort for details.
Prerequisites
- Must be on an integration branch (has upstream tracking and woven feature branches)
- Must have something to commit (staged or stageable changes)
fold
Fold source(s) into a target — a polymorphic command that amends files into commits, fixups commits together, moves commits between branches, or uncommits changes.
Usage
git loom fold <target>
git loom fold <source>... <target>
git loom fold -p [<files>...] <target>
git loom fold -p <commit1> <commit2>
git loom fold -p <commit> zz
git loom fold --create <commit>... <new-branch>
When only a target is given, currently staged files are folded into the target commit. When two or more arguments are provided, the last argument is the target and all preceding arguments are sources.
Options
| Option | Description |
|---|---|
-p, --patch | Interactively select hunks before folding. Three forms depending on argument types (see below). |
-c, --create | Create a new branch and move the source commit(s) into it. |
Type Dispatch
The action depends on the types of the arguments, detected automatically:
| Source | Target | Action |
|---|---|---|
| (staged) | Commit | Amend staged: fold currently staged files into the commit |
| File(s) | Commit | Amend: stage files into the commit |
zz | Commit | Amend all: stage all changed files into the commit |
| Commit | Commit | Fixup: absorb source commit into target |
| Commit | Branch | Move: relocate commit(s) to the branch |
| Commit | zz | Uncommit: remove commit, put changes in working directory |
| CommitFile | zz | Uncommit file: remove one file from a commit to working directory |
| CommitFile | Commit | Move file: move one file’s changes between commits |
| Commit | New branch (-c) | Create: make a new branch and move the commit(s) into it |
CommitFile sources use the commit_sid:index format shown by git loom status -f (e.g. fa:0 for the first file in commit fa).
Actions
Fold staged files into a commit
When only a target is given, staged files are folded into the commit:
git add src/auth.rs
git loom fold ab
# Folds staged changes into commit ab
Only files in the git index are folded — unstaged changes to the same files are preserved. Errors with "Nothing to commit" if nothing is staged.
Amend files into a commit
git loom fold src/auth.rs ab
# Stages src/auth.rs and amends it into commit ab
Multiple files can be folded at once:
git loom fold src/main.rs src/lib.rs HEAD
# Amends both files into the HEAD commit
Use zz to fold all working tree changes at once (staged and unstaged):
git loom fold zz ab
# Stages all changed files and amends them into commit ab
If zz is mixed with individual file arguments, zz takes precedence and all changed files are folded.
Interactive hunk selection (-p)
With -p, an interactive TUI opens for hunk-level selection. There are three forms depending on the argument types.
Form 1 — pick working-tree hunks → fold into commit:
git loom fold -p ab
# Opens hunk picker for all working-tree changes
# Selected hunks are staged and folded into commit ab
Provide file arguments before the target to narrow the picker:
git loom fold -p src/auth.rs ab
# Opens hunk picker filtered to src/auth.rs
Form 2 — pick hunks from a commit → move into another commit:
git loom fold -p c2 c1
# Opens commit-diff picker for c2
# Selected hunks are removed from c2 and added to c1
The source (c2) must be newer than the target (c1). Binary and deleted files are not supported.
Form 3 — pick hunks from a commit → uncommit to working tree:
git loom fold -p ab zz
# Opens commit-diff picker for ab
# Selected hunks are removed from ab and appear as unstaged modifications
All -p forms error with "No hunks selected" if nothing is selected.
Fixup a commit into another
Absorbs the source commit’s changes into the target. The source disappears from history; the target keeps its message.
git loom fold c2 c1
# c2's changes are absorbed into c1, c2 disappears
The source commit must be newer than the target.
Move a commit to another branch
Removes the commit from its current branch and appends it to the target branch’s tip.
git loom fold d0 feature-b
# Commit d0 moves to feature-b, removed from its original branch
Several commits can go in one move. They are ordered oldest-first whatever order you list them in — ancestors before their descendants, and commits from unrelated branches by commit date — so they travel in a single rebase and land in history order.
git loom fold d0 d1 d2 feature-b
# d0, d1 and d2 all move to feature-b
A single commit can be resumed with git loom continue if it conflicts. A move of several rolls back instead, leaving history as it was.
A branch that ended at d0 (a stacked branch) stays behind: it ends at the commit before, or at the base if d0 was its only commit. It never follows the commit into feature-b. A branch left empty this way is named in the result:
git loom fold d0 feature-b
# ✓ Moved d0 to branch feature-b (now e1f2a3b)
# › branch feature-x now empty, at the base
The target can be a branch stacked inside another one: the commit lands right after that branch’s tip, and the branch stacked on top is replayed over it.
git loom fold d0 feature-a
# feature-c is stacked on feature-a: d0 becomes feature-a's tip,
# feature-c's commits now build on d0
Create a new branch and move a commit into it
Use --create (-c) to create a new branch and move the commit in one step. Works whether the commit is a loose commit on the integration line or already on an existing branch.
git loom fold -c d0 new-feature
# Creates new-feature and moves commit d0 into it
You can list several commits to move them all into the new branch. They are ordered oldest-first so the new branch preserves their history order.
git loom fold -c d0 d1 d2 new-feature
# Creates new-feature and moves d0, d1, d2 into it
Like any move of several commits, -c is not resumable: a conflict rolls it back rather than pausing for git loom continue.
-c creates, so a name that is already taken is refused. Moving onto a branch that exists is a plain fold, and accepting the name here would let a typo drop your commits into another branch.
git loom fold -c d0 existing-branch
# ✗ Branch `existing-branch` already exists
# Use `loom fold <commit>... existing-branch` to move commits onto it
Uncommit to the working directory
Removes a commit from history and places its changes as unstaged modifications.
git loom fold ab zz
# Removes commit ab, its changes appear as unstaged modifications
The changes are merged back into the working tree three-way, so a later commit
that edited nearby lines does not break the apply (-p hunk selections are the
exception — they carry no blob ids to merge through). It still fails when they
overlap for real, or when you have uncommitted changes in one of the same
files. Either way nothing is left half-done: history and your uncommitted
changes both go back to where they were.
If ab was the only commit of a branch, the branch survives, empty, at the base it built on — ready for git loom commit -b <branch> once the change is reworked:
git loom fold ab zz
# ✓ Uncommitted ab to working directory
# › branch feature-x now empty, at the base
Uncommit a single file
Removes one file’s changes from a commit, preserving the rest of the commit.
git loom fold ab:1 zz
# Removes the second file from commit ab to the working directory
Move a file between commits
Moves one file’s changes from one commit to another.
git loom fold c2:1 c1
# Moves the second file from c2 to c1
Arguments
Arguments can be:
- File paths — files with changes in the working tree
- Commit hashes — full or partial git hashes
- Branch names — local branch names
- Short IDs — compact IDs from
git loom status - Git references —
HEAD,HEAD~2, etc. zz— reserved token for the unstaged working directory
Conflicts
The following fold operations support conflict recovery (pause/resume):
- Amend files into a non-HEAD commit
- Fixup a commit into another
- Move a commit to a branch
- Uncommit a commit to the working directory (non-HEAD)
If a supported fold hits a conflict, the operation is paused:
git loom fold d0 feature-b
# ! Conflicts detected — resolve them with git, then run:
# loom continue to complete the fold
# loom abort to cancel and restore original state
git add <resolved-files> && git loom continue
# ✓ Moved `d0` to branch `feature-b` (now `e1f2a3b`)
The following fold operations do not support pause/resume and abort immediately on conflict:
- All
-p(patch mode) forms — any conflict causes an automatic abort and restores the original state - Uncommit a single file (
CommitFile → zz) - Move a file between commits (
CommitFile → Commit) - Create a new branch and move a commit (
--create)
See continue and abort for details.
Prerequisites
- Must be in a git repository with a working tree
- For short ID arguments: must have upstream tracking configured
- All operations are atomic and automatically preserve uncommitted changes
absorb
Automatically distribute working tree changes into the commits that last touched the affected lines. Uses blame to determine the correct target for each hunk, then amends those commits — all in a single operation.
Usage
git loom absorb [-n] [files...]
Options
| Option | Description |
|---|---|
-n, --dry-run | Show what would be absorbed without making changes |
Arguments
| Argument | Description |
|---|---|
[files...] | Files to restrict absorption to (default: all tracked changed files) |
How It Works
For each file with uncommitted changes:
- Parses the unified diff into individual hunks
- For each hunk, blames the modified/deleted lines to find their originating commit
- If all hunks trace to the same in-scope commit, the whole file is absorbed
- If hunks trace to different commits, each hunk is independently absorbed into its target
- Hunks that can’t be attributed (pure additions, ambiguous) are skipped and left in the working tree
After analysis, all assigned hunks are folded into their target commits in a single rebase operation.
Examples
Absorb all changes
git loom absorb
# src/auth.rs -> a1b2c3d "Add authentication"
# src/utils.rs -> d4e5f6a "Add utility helpers"
# Absorbed 2 hunk(s) from 2 file(s) into 2 commit(s)
Absorb hunks into different commits
# src/shared.rs has changes in two separate regions,
# each originating from a different commit
git loom absorb
# src/shared.rs [hunk 1/2] -> a1b2c3d "Add login form"
# src/shared.rs [hunk 2/2] -> d4e5f6a "Add dashboard"
# Absorbed 2 hunk(s) from 1 file(s) into 2 commit(s)
Dry run
git loom absorb --dry-run
# src/auth.rs -> a1b2c3d "Add authentication"
# src/shared.rs [hunk 1/2] -> d4e5f6a "Add utility helpers"
# src/shared.rs [hunk 2/2] -- skipped (pure addition)
# Dry run: would absorb 2 hunk(s) from 2 file(s) into 2 commit(s)
Restrict to specific files
git loom absorb src/auth.rs src/utils.rs
# src/auth.rs -> a1b2c3d "Add authentication"
# src/utils.rs -> d4e5f6a "Add utility helpers"
# Absorbed 2 hunk(s) from 2 file(s) into 2 commit(s)
Conflicts
If the rebase that folds the fixup commits hits a conflict, the operation is paused. Pre-existing staged changes are saved aside automatically.
git loom absorb
# src/auth.rs -> a1b2c3d "Add authentication"
# ! Conflicts detected — resolve them with git, then run:
# loom continue to complete the absorb
# loom abort to cancel and restore original state
git add <resolved-files> && git loom continue
# ✓ Absorbed 1 hunk(s) from 1 file(s) into 1 commit(s)
See continue and abort for details.
Prerequisites
- Must be on an integration branch
- Working tree must have uncommitted changes
- Target commits must be in scope (between merge-base and HEAD)
split
Split a commit into two sequential commits by selecting which files (or hunks) go into the first.
Usage
git loom split [-p] [-m <message>] <target> [<files>...]
Arguments
| Argument | Description |
|---|---|
<target> | Commit hash, short ID, or HEAD |
<files>... | Files for the first commit. Shows an interactive picker if omitted. Ignored when -p is used. |
Options
| Option | Description |
|---|---|
-m, --message <message> | Message for the first commit. Opens editor if omitted. |
-p, --patch | Interactively pick individual hunks for the first commit |
What It Does
File-based split (default)
Shows an interactive multi-select of all files changed in the commit. The files you pick go into the first commit; the rest stay in the second commit, which keeps the original message.
You can skip the picker by listing <files> on the command line. The commit must touch at least two files.
Hunk-based split (-p)
Opens the hunk picker TUI showing every hunk in the commit. All hunks start unselected (no-op). Toggle hunks with Space; selected hunks go into the first commit, unselected hunks stay in the second. Works on single-file commits.
HEAD vs non-HEAD
- HEAD commit:
reset --mixed HEAD~1then re-commit in two steps — no rebase needed. - Non-HEAD commit: uses an edit-and-continue rebase to pause at the target, split it, then replay descendants.
Both paths preserve any pre-existing staged changes and abort cleanly on error.
Examples
Split HEAD interactively by file
git loom split HEAD
# ? Select files for the first commit
# > [x] src/auth.rs
# [ ] src/main.rs
# (opens editor for the first commit message)
# ✓ Split `abc123d` into `def456a` and `789bcd0`
Split HEAD by file non-interactively
git loom split HEAD -m "refactor: extract auth" src/auth.rs
# ✓ Split `abc123d` into `def456a` and `789bcd0`
Split a commit by short ID using the hunk picker
git loom split -p ab -m "fix: extract bounds check"
# (hunk picker TUI opens — toggle hunks for first commit)
# ✓ Split `ab12345` into `cd67890` and `ef01234`
Split a non-HEAD commit by file
git loom split ab -m "refactor: extract helpers" src/helpers.rs
# ✓ Split `ab12345` into `cd67890` and `ef01234`
Prerequisites
- Must be in a git repository with a working tree
- Target must be a commit (not a branch, file, or
zz) - Merge commits cannot be split
- File-based split requires the commit to touch at least two files
- Git ≥ 2.38
swap
Swap two commits within the same sequence.
Usage
git loom swap <a> <b>
Arguments
| Argument | Description |
|---|---|
<a> | Commit hash or short ID — first commit |
<b> | Commit hash or short ID — second commit |
What It Does
Swaps the positions of two commits within their shared sequence (a branch section or the integration line). All descendant commits are replayed in the new order.
Both commits must belong to the same sequence — swapping commits across different branch sections, or between a branch section and the integration line, is an error.
Target Resolution
Accepts full OID, partial OID prefix, or 2-char short ID. Branch names are not accepted.
Examples
Swap two commits on the integration line
git loom swap abc123 def456
# Swapped commits `abc123` and `def456`
Swap two commits in a branch section using short IDs
git loom swap aa bb
# Swapped commits `aa` and `bb`
Error: commits in different branch sections
git loom swap ca1 cb1
# ! Cannot swap commits from different branch sections
Conflicts
If a conflict occurs during the rebase, the operation is paused:
git loom swap abc123 def456
# ! Conflicts detected — resolve them with git, then run:
# loom continue to complete the swap
# loom abort to cancel and restore original state
git add <resolved-files> && git loom continue
# ✓ Swapped `abc123` and `def456`
See continue and abort for details.
Prerequisites
- Both commits must be woven into the current integration branch
- Both commits must be in the same sequence (same branch section or both on the integration line)
- Uncommitted working tree changes are preserved automatically
reword
Reword a commit message or rename a branch.
Usage
git loom reword <target> [-m <message>]
Arguments
| Argument | Description |
|---|---|
<target> | Commit hash, branch name, or short ID |
Options
| Option | Description |
|---|---|
-m, --message <message> | New commit message or branch name. Opens editor/prompt if omitted. |
What It Does
When Target is a Commit
Changes the commit message using git’s native interactive rebase. All descendant commits are replayed to update their hashes.
- Works on any commit in history, including the root commit
- With
-m: applies the new message non-interactively - Without
-m: opens the git editor with the current message
What changes: target commit gets a new message and hash; all descendant commits get new hashes.
What stays the same: commit content (files, diffs), topology, and branches outside the ancestry chain.
Conflicts
Every commit above the target gets a new hash, so any merge commit in the way has to be rebuilt instead of reused. A merge you originally resolved by hand will conflict again — a merge commit records the tree it produced, never the resolution that produced it. (With rerere enabled, git replays your recorded resolution, so the file has no conflict markers — the reword still pauses, and you stage the replayed resolution before loom continue.)
When that happens the reword pauses rather than throwing away the new message:
git loom reword ab -m "Fix authentication bug"
# ! Conflicts detected — resolve them with git, then run:
# `loom continue` to complete the reword
# `loom abort` to cancel and restore original state
git add shared.rs && git loom continue
# ✓ Updated commit message for `ab12cd3` (now `e45f678`)
With rerere.autoUpdate set, git stages the replayed resolution too, and the pause says so:
# ! `rerere` resolved the conflicts for you — review the result, then run:
# `loom continue` to complete the reword
# `loom abort` to cancel and restore original state
git loom abort restores the original message, HEAD, and every branch ref. See continue and abort.
When Target is a Branch
Renames the branch using git branch -m.
- With
-m: renames non-interactively - Without
-m: interactive prompt showing current name as placeholder
Target Resolution
The target is resolved in this order:
- Branch names — exact match resolves to a branch (for renaming)
- Git references — full/partial hashes,
HEAD, etc. resolve to commits - Short IDs — branch short IDs resolve to branches, commit short IDs to commits
To reword the commit at a branch tip, use its commit hash or commit short ID (not the branch name, which would trigger a rename).
Examples
Reword a commit with editor
git loom reword ab
# Opens editor with current message
Reword a commit directly
git loom reword ab -m "Fix authentication bug in login flow"
Rename a branch interactively
git loom reword feature-a
# ? New branch name › feature-a
# User types: feature-authentication
Rename a branch directly
git loom reword fa -m feature-authentication
Rename to a hidden branch name
If the new name matches the configured hidden prefix (default: local-), git-loom prints a warning before the success message:
git loom reword feature-secrets -m local-secrets
# ! Branch `local-secrets` is hidden from status by default. Use `--all` to show it.
# ✓ Renamed branch `feature-secrets` to `local-secrets`
Prerequisites
- Any git repository for commit rewording
- For short IDs: must be on a branch with upstream tracking configured
drop
Drop a commit, branch, file, or all local changes.
Usage
git loom drop [-y] <target>
Arguments
| Argument | Description |
|---|---|
<target> | Commit hash, branch name, file short ID, or short ID |
Options
| Option | Description |
|---|---|
-y, --yes | Skip confirmation prompt |
What It Does
When Target is a Commit
Removes the commit from history. All descendant commits are replayed to maintain a consistent history.
Dropping a commit never deletes a branch. If the commit was the only commit of a branch, that branch survives, empty, at the base it built on — unwoven, and ready for git loom commit -b <branch> once the change is reworked. This holds for a branch that owns the commit, several branches at the same sole commit, and a stacked branch whose only commit it is; the prompt and the result name them.
git loom drop a1
# Drop commit `a1` Add login form, leaving branch `feature-a` empty? (y/n)
# ✓ Dropped commit a1
# › branch feature-a now empty, at the base
To remove the branch as well, drop the branch itself — that takes its commits with it in one command:
git loom drop feature-a
When Target is a Branch
Removes the entire branch in a single operation:
- All commits owned by the branch are removed
- The merge topology is unwoven (if the branch was woven)
- The branch ref is deleted
Co-located branches (sharing the same tip commit with another branch): only the branch ref is deleted. Commits are preserved for the surviving sibling branch, and the merge topology is reassigned.
When Target is a File
Behavior depends on the file’s status:
- Tracked file with modifications —
git restore --staged --worktree <path>. Prompt:"Discard changes to '<path>'?". Output:"Restored '<path>'". - Staged new file (
Ain index) —git rm --force <path>. Prompt:"Delete '<path>'?". Output:"Deleted '<path>'". - Untracked file (
??) — deleted from disk. Prompt:"Delete '<path>'?". Output:"Deleted '<path>'".
A confirmation prompt is shown first (skippable with -y).
When Target is zz (all local changes)
Discards everything in the working tree and index:
git restore --staged --worktree .— reverts all tracked modificationsgit clean -fd— deletes all untracked files and directories
If there are no local changes, the command errors with "No local changes to discard".
Target Resolution
- Branch names — exact match resolves to a branch (drops the branch)
- Git references — full/partial hashes resolve to commits
- Short IDs — branch short IDs resolve to branches, commit short IDs to commits, file short IDs to files
zz— always resolves to all local changes
Examples
Drop a commit by short ID
git loom drop ab
# Removes the commit from history
Drop a commit by hash
git loom drop abc123d
# Removes the commit from history
Drop a branch
git loom drop feature-a
# Removes all commits, unweaves merge topology, deletes branch ref
Drop a branch by short ID
git loom drop fa
# Same as above, using the short ID
Drop a file (discard changes)
git loom drop ma
# Discard changes to `src/main.rs`? (y/n)
# Restored `src/main.rs`
Drop a new or untracked file
git loom drop nf
# Delete `new_feature.rs`? (y/n)
# Deleted `new_feature.rs`
Drop all local changes
git loom drop zz
# Discard all local changes? (y/n)
# Discarded all local changes
Drop a co-located branch
git loom drop feature-a
# Removes feature-a ref, reassigns section to sibling branch
# Commits preserved for the surviving branch
Conflicts
Dropping a commit supports conflict recovery. If the rebase hits a conflict, the operation is paused:
git loom drop ab
# ! Conflicts detected — resolve them with git, then run:
# loom continue to complete the drop
# loom abort to cancel and restore original state
git add <resolved-files> && git loom continue
# ✓ Dropped commit `ab`
Dropping a branch does not support pause/resume — if a conflict occurs it aborts immediately and leaves the repository in its original state.
See continue and abort for details.
Prerequisites
- Must be in a git repository with a working tree
- For branch drops: the branch must be in the integration range
- All operations are atomic and automatically preserve uncommitted changes
branch
Manage feature branches: create new branches, weave existing branches into the integration topology, or remove them.
Alias: br
Subcommands
| Subcommand | Description |
|---|---|
new (alias: create) | Create a new feature branch at a specified commit |
merge | Weave an existing branch into the integration branch |
unmerge | Remove a branch from integration (keeps the branch ref) |
Running git loom branch without a subcommand defaults to new.
branch new
Create a new feature branch at a specified commit.
Usage
git loom branch [name] [-t <target>]
git loom branch new [name] [-t <target>]
git loom branch create [name] [-t <target>]
Arguments
| Argument | Description |
|---|---|
[name] | Branch name (optional; prompts interactively if omitted) |
Options
| Option | Description |
|---|---|
-t, --target <target> | Commit hash, short ID, or branch name (defaults to upstream merge-base) |
What It Does
- Name resolution — if no name is provided, an interactive prompt asks for one
- Validation — the name is trimmed, checked for emptiness, validated against git’s naming rules, and checked for duplicates
- Target resolution — the target is resolved to a commit via the shared resolution system, or defaults to the merge-base
- Creation — the branch is created at the resolved commit
Automatic Weaving
When a branch is created at a commit on the first-parent line from HEAD to the merge-base, git-loom automatically weaves it into the integration branch — restructuring the linear history into a merge-based topology.
Before (linear):
origin/main → A1 → A2 → A3 → HEAD
After git loom branch feature-a -t A2:
A1 → A2 (feature-a)
/ \
origin/main merge → A3' (HEAD)
All first-parent commits from the start up to (and including) the target move into the new branch section. Commits after the target are replayed on top of the resulting merge commit.
No-op cases — weaving does not trigger and only the branch ref is created:
- Branch at merge-base — no commits to move into the branch.
- Branch inside an existing side branch — the target commit is already part of a merge topology (reachable through a merge second-parent), so no restructuring is needed.
Branching at HEAD weaves all current first-parent commits into the new branch:
git loom branch feature-a # target = HEAD (all commits go into feature-a)
If the working tree has uncommitted changes, they are automatically stashed and restored after the operation.
If a weave rebase encounters conflicts, it aborts automatically and reports an error — no state is saved and no loom continue is available. Resolve the situation and retry.
Target Resolution
The -t flag accepts:
- Branch names — resolves to the branch’s tip commit
- Git hashes — full or partial commit hashes
- Short IDs — the compact IDs shown in
git loom status - Default — the merge-base between HEAD and upstream
Examples
Interactive
git loom branch
# ? Branch name ›
# User types: feature-authentication
# ✓ Created branch `feature-authentication` at abc1234
At merge-base (default)
git loom branch feature-auth
# ✓ Created branch `feature-auth` at abc1234
At a specific commit by short ID
git loom branch feature-auth -t ab
# ✓ Created branch `feature-auth` at 72f9d3a
# ✓ Woven `feature-auth` into integration branch
At another branch’s tip
git loom branch feature-b -t feature-a
# ✓ Created branch `feature-b` at feature-a's tip commit
Branching at HEAD (weaves all commits)
git loom branch feature-a
# ✓ Created branch `feature-a` at HEAD
# ✓ Woven `feature-a` into integration branch
branch merge
Weave an existing branch into the integration branch using a merge commit.
Usage
git loom branch merge [branch] [--all]
Arguments
| Argument | Description |
|---|---|
[branch] | Branch name (optional; shows interactive picker if omitted) |
Options
| Option | Description |
|---|---|
-a, --all | Also show remote branches without a local counterpart |
What It Does
- Branch selection — uses the provided name, or shows an interactive picker listing non-woven local branches
- Validation — checks that the branch exists and is not already woven into integration
- Remote handling — if a remote branch is selected (with
--all), creates a local tracking branch automatically - Merge — performs a
git merge --no-ffto weave the branch into the integration topology
Examples
Merge a specific branch
git loom branch merge feature-auth
# ✓ Woven `feature-auth` into integration branch
Interactive picker
git loom branch merge
# ? Select branch to weave ›
# feature-auth
# feature-logging
# ✓ Woven `feature-auth` into integration branch
Include remote branches
git loom branch merge --all
# ? Select branch to weave ›
# feature-auth
# origin/feature-logging
branch unmerge
Remove a branch from the integration topology without deleting the branch ref.
Usage
git loom branch unmerge [branch]
Arguments
| Argument | Description |
|---|---|
[branch] | Branch name or short ID (optional; shows interactive picker if omitted) |
What It Does
- Branch selection — uses the provided name/short ID, or shows an interactive picker listing woven branches
- Validation — checks that the branch is actually woven into the integration branch
- Unweave — rebases the integration branch to remove the branch’s merge topology
- Preserve — the branch ref is kept intact, pointing at its original commits
This is different from drop, which deletes the branch entirely.
If the unweave rebase encounters conflicts, it aborts automatically and reports an error — no state is saved and no loom continue is available.
Examples
Unmerge a specific branch
git loom branch unmerge feature-auth
# ✓ Unwoven `feature-auth` from integration branch
Interactive picker
git loom branch unmerge
# ? Select branch to unmerge ›
# feature-auth
# feature-logging
# ✓ Unwoven `feature-auth` from integration branch
Conflicts
branch merge — supports pause/resume
If the merge encounters a conflict, loom saves state and pauses:
git loom branch merge feature-auth
# ! Conflicts detected — resolve them with git, then run:
# loom continue to complete the merge
# loom abort to cancel and restore original state
After resolving:
git add <resolved-files> && git loom continue
# ✓ Woven `feature-auth` into integration branch
branch new / branch unmerge — hard fail
Neither subcommand supports pause/resume. If a rebase conflict occurs, the rebase is aborted automatically, the repository is left in its original state, and an error is reported. Retry after resolving the conflicting situation.
See continue and abort for details.
Hidden Branch Warning
If the branch name matches the configured hidden prefix (default: local-), git-loom prints a warning before the success message:
! Branch `local-secrets` is hidden from status by default. Use `--all` to show it.
✓ Created branch `local-secrets` at abc1234
See Configuration to customize the prefix.
Reserved Names
The subcommand names new, create, merge, and unmerge are reserved and cannot be used as branch names.
Prerequisites
- Must be in a git repository with a working tree
- For the default target: must have upstream tracking configured
- For short ID targets: must have upstream tracking configured
switch
Check out any branch for testing without weaving it into the integration branch.
Usage
git loom switch [<branch>]
git loom sw [<branch>]
If no branch is given, an interactive picker lists all local branches and all remote-only branches.
Arguments
| Argument | Description |
|---|---|
<branch> | Branch to switch to: local branch name, remote-tracking name (e.g. origin/feature-x), or short ID of a woven branch. Optional — omit to pick interactively. |
What It Does
When Target is a Local Branch
HEAD moves to the named local branch (attached, not detached). No branch refs or commit history are changed.
When Target is a Remote-Only Branch
A remote-only branch is a remote-tracking ref (e.g. origin/colleague-work) with no local counterpart. HEAD is detached at that ref’s commit. No local tracking branch is created, so there is nothing to clean up afterward.
Interactive Picker (no argument)
Shows all local branches except the current one, followed by all remote-only branches. Selecting a local branch switches normally; selecting a remote-only branch detaches HEAD.
Target Resolution
- Local branch name — exact match against local branches
- Remote branch name — exact match against remote-tracking refs (e.g.
origin/feature-x) - Short ID — best-effort lookup via the woven-branch graph (requires being on an integration branch with upstream tracking configured; silently skipped otherwise)
Examples
Switch to a local branch
git loom switch feature-x
# ✓ Switched to `feature-x`
Switch using a short ID
git loom switch fx
# ✓ Switched to `feature-x`
Inspect a remote-only branch
git loom switch origin/colleague-work
# ✓ Detached HEAD at `origin/colleague-work`
# No local branch is created.
Return to the integration branch
git loom switch integration
# ✓ Switched to `integration`
Prerequisites
- Must be in a git repository with a working tree (not bare)
- Working tree must be clean: no staged changes and no unstaged modifications to tracked files (untracked files are allowed)
- Blocked while a loom operation is paused — run
continueorabortfirst
status
Show the branch-aware commit graph. This is the default command when running git loom with no arguments.
Usage
git loom [status] [-f [COMMIT...]] [N]
Arguments
| Argument | Description |
|---|---|
N | Number of context commits to show before the base (default: 1) |
Options
| Option | Description |
|---|---|
-f, --files [COMMIT...] | Show files changed in each commit, optionally filtered to specific commits |
-a, --all | Show all branches including hidden ones |
Output
The status displays a branch-aware commit graph using UTF-8 box-drawing characters, showing commits grouped by feature branch:
╭─ [local changes]
│ !! conflicted.rs
│ M file.txt
│ A new_file.rs
│ ⁕ untracked.txt
│
│╭─ [feature-b] ✓
│● d0472f9 Fix bug in feature B
│● 7a067a9 Start feature B
├╯
│
│╭─ [feature-a] ↑
│● 2ee61e1 Add feature A
├╯
│
● ff1b247 (upstream) [origin/main] Initial commit
Sections
The graph is rendered top-to-bottom with these sections:
-
Local changes — shown only if the working tree has modifications, new files, or deletions. Files are split into three groups:
- Conflicted files are shown first with a
!!marker in bold red (filename also bold red). These appear during an in-progress rebase or merge. - Tracked changes are listed next with a 2-char
XYstatus matchinggit status --short(index green, worktree red). - Untracked files are listed last with a
⁕marker (magenta). When there are more than 5 untracked files, they are displayed in a multi-column grid layout sized to the terminal width.
- Conflicted files are shown first with a
-
Feature branches — each branch is rendered as a side branch with its name in brackets, followed by its commits, closed with
├╯. A remote tracking indicator appears after the closing]when an upstream has been configured for the branch. -
Loose commits — commits not belonging to any feature branch, shown on the main integration line.
-
Upstream marker — the merge-base between HEAD and the upstream tracking branch.
Symbols
| Symbol | Meaning |
|---|---|
╭─ | Start of a section |
├─ | Start of a subsequent branch in a stack |
│ | Integration line continuation |
││ | Continuation between stacked branches |
● | A commit |
├╯ | End of a side branch |
!! | Conflicted file marker (bold red) |
⁕ | Untracked file marker (magenta) |
⏫ | Upstream has new commits |
· | Context commit before the base (dimmed) |
✓ | Branch remote is in sync (green) |
↑ | Branch tip differs from its remote (yellow) |
✗ | Branch remote is gone (red) |
Short IDs
Each branch, commit, and file in the output is assigned a short ID — a compact identifier you can use with other git-loom commands. What you see in the status is what you type.
Showing Files
Use -f to show the files changed in each commit:
git loom status -f
│╭─ fa [feature-a]
│● d0 Add feature A
│┊ d0:0 M src/feature.rs
│┊ d0:1 A tests/feature_test.rs
├╯
To show files for specific commits only, pass their short IDs or git hashes after -f:
git loom status -f d0
git loom status -f d0 ab
git loom status -f abc1234
Only the listed commits display their file list; all other commits are rendered normally. Unknown identifiers are silently ignored.
Branch Topologies
Independent branches
Each feature branch forks from the integration line independently:
│╭─ [feature-b]
│● d0472f9 Fix bug in feature B
├╯
│
│╭─ [feature-a]
│● 2ee61e1 Add feature A
├╯
Stacked branches
Feature-b is stacked on top of feature-a:
│╭─ [feature-b]
│● 4e046ab Second commit on feature-b
│● 0b85ca7 First commit on feature-b
││
│├─ [feature-a]
│● caa87a9 Second commit on feature-a
│● 18faee8 First commit on feature-a
├╯
Co-located branches
Multiple branches pointing to the same commit:
│╭─ [feature-a-v2]
│├─ [feature-a]
│● 2ee61e1 Add feature A
├╯
Upstream ahead
When upstream has new commits beyond the common base:
● abc1234 Fix typo
│
│● [origin/main] ⏫ 3 new commits
├╯ 204e309 (common base) 2025-07-06 Merge pull request #10
Context commits
Show history before the base with a positional argument (git loom 3 or git loom status 3):
● ff1b247 (upstream) [origin/main] Initial commit
· abc1234 2025-07-05 Previous work
· def5678 2025-07-04 Earlier change
Context commits are dimmed and display-only (no short ID, not actionable). The default is 1 (no extra context).
Hidden Branches
Branches whose names start with the configured prefix (default: local-) are hidden from the status output by default. Both the branch section and its commits are fully suppressed — they do not appear as loose commits either.
This is useful for keeping local-only branches (personal configuration, secrets) out of the status view without removing them from the integration branch.
git loom --all # show all branches including hidden
git loom status --all # same, explicit
The hidden prefix is configurable (see Configuration).
Theming
The graph colors adapt to the terminal background via the global --theme flag:
git loom --theme light status # Light terminal background
git loom --theme dark status # Dark terminal background
git loom --theme auto status # Auto-detect (default)
See Configuration for details.
Prerequisites
- Must be on a local branch (not detached HEAD)
- Branch must have an upstream tracking branch configured
tui
Open an interactive, full-screen status view: the branch-aware tree on the left, the diff of the item under the cursor on the right, with common loom actions one keypress away.
Usage
git loom tui
No arguments or flags. The global --theme and --no-color options apply.
What It Does
Shows the same tree as git loom status with files enabled, plus a live diff pane. Navigate to the thing you see, press one key, and the underlying loom command runs with the right arguments filled in. After every action the tree reloads.
┌ Status ──────────────────────┐┌ Diff ─────────────────────────┐
│ ╭─ z0 [local changes] ││ diff --git a/src/main.rs ... │
│ │ a1 M src/main.rs ││ @@ -1,3 +1,4 @@ │
│ │╭─ b0 [feature-a] ✓ ││ +new line │
│ │● d4f2 Add parser (2 files) ││ ... │
│ ├╯ ││ │
│ ● 9999999 (upstream) ... ││ │
└──────────────────────────────┘└───────────────────────────────┘
Navigate: ↑/↓ | Fold/unfold: ←/→ | Select: space | Commit: c | ...
Short IDs are displayed like in git loom status, so the tree doubles as a cheat-sheet for manual commands.
Navigation
| Key | Effect |
|---|---|
↑/k, ↓/j | Move the cursor (tree focused) or scroll the diff (diff focused) |
→/l / ←/h | Unfold / fold the current row; ← on a file row jumps to its parent |
Enter | Toggle fold (or confirm a fold target — see below) |
Tab | Switch focus between tree and diff pane |
Ctrl-← / Ctrl-→ | Narrow / widen the left pane (2% per press, clamped to 10–90%) |
PgUp/PgDn | Scroll the diff by a page |
| Mouse click / wheel | Focus, move, scroll |
R / F5 | Reload the tree from the repo |
Esc | Cancel fold mode → clear selection → quit (first that applies) |
q / Ctrl-C | Quit |
Commits are collapsed by default; unfolding reveals one row per changed file. Local changes start expanded. Expansion state survives reloads.
Selection
Space toggles selection of the current row (marked ✓) and advances the cursor. Actions use the selection when one exists, otherwise the cursor row. Selection is cleared on reload.
Actions
Every action suspends the TUI, runs the regular loom command — prompts and editors work exactly as on the command line — prints its output, waits for Enter, then reloads the tree.
| Key | Command | Arguments |
|---|---|---|
c | commit | Selected working files; nothing relevant selected → the index as-is. Branch and message are prompted as usual. |
f | fold | Two-step: f captures the selection (or cursor row) as sources; move the cursor to the target and press Enter (Esc cancels). While picking a target, other action keys are inactive. |
b | branch new | Cursor commit or branch as the -t target when on one; name is prompted. |
d | drop | Cursor commit, branch, or working file; confirmation prompt as usual. |
r | reword | Cursor commit (opens editor) or branch (prompts rename). |
Diff Pane
| Row | Diff shown |
|---|---|
[local changes] | Staged + unstaged changes (git diff HEAD) |
| Working file | That file’s diff; untracked files show their content as added lines |
| Branch name | Everything the branch owns |
| Commit | git show with stats and patch |
| Commit file | That file’s change within the commit |
Conflicts
If an action pauses on conflicts, the TUI exits with the standard guidance — every other command is blocked while an operation is paused, so the TUI cannot stay open:
# ! A `loom fold` is paused due to conflicts.
# Resolve them, then run `loom continue` to resume,
# or `loom abort` to cancel.
See continue and abort for details.
Prerequisites
- Must be on an integration branch with upstream tracking configured (same requirement as
status) - An interactive terminal — the TUI is unavailable in agent mode
show
Show the diff and metadata for a commit, like git show.
Usage
git loom show [<target>] [-- <git args>...]
Alias: sh
Arguments
| Argument | Description |
|---|---|
<target> | Commit hash, branch name, or short ID |
Git Options
Everything after a -- separator goes to git show untouched — see Passing Options to Git:
git loom show -- --stat
git loom show ab -- -U5
git loom show ab -- -- src/main.rs
What It Does
Displays the commit metadata (author, date, message) and diff for the resolved commit, exactly like git show. Uses git’s native pager when running in a terminal.
- When given a commit (hash, partial hash, or short ID): shows that commit
- When given a branch (name or short ID): shows every commit the branch owns, newest first — the same commits
git loom statuslists under that branch, merge commits excluded. Naming a hidden branch shows it, even thoughgit loom statusleaves it out - When given the integration branch: shows its loose commits, the ones
git loom statusputs on the integration line - With no target: shows the commit at the top of
git loom status
A branch that is not part of the integration stack — no upstream configured, a detached HEAD, or an unrelated history — falls back to plain git show behavior and displays only the branch tip. A branch that is part of the stack but owns no commits of its own is an error.
Target Resolution
The target is resolved in this order:
- Branch names — exact match resolves to the branch’s commits
- Git references — full/partial hashes,
HEAD, etc. resolve to commits - Short IDs — branch short IDs resolve to the branch’s commits, commit short IDs to commits
Examples
Show a commit by short ID
git loom show ab
# Displays commit info and diff for the commit with short ID "ab"
Show a commit by hash
git loom show 9f484b6
Show a whole branch
git loom show feature-a
# Shows every commit on feature-a, newest first
Prerequisites
- Any git repository
- For short IDs, and to show a branch’s full set of commits: must be on an integration branch with upstream tracking configured
diff
Show a diff using short IDs, like git diff.
Usage
git loom diff [args...] [--staged] [--all] [-- <git args>...]
Alias: di
Each argument is a file, commit, or commit range. Arguments can be mixed freely in a single invocation.
Arguments
| Argument | Description |
|---|---|
[args...] | Files (short ID or path), commits (short ID, hash), or ranges (left..right) |
Options
| Option | Description |
|---|---|
--staged (alias --cached) | Show staged changes (index vs HEAD) |
-a, --all | Show all changes, staged and unstaged combined (working tree vs HEAD) |
--staged and --all are mutually exclusive. With neither flag, only unstaged changes are shown, exactly like git diff.
Git Options
Everything after a -- separator goes to git diff untouched — see Passing Options to Git:
git loom diff -- --stat
git loom diff ma -- -w
git loom diff ab..d0 -- --name-only
git loom diff -- -a # git's --text; loom's own -a is --all
What It Does
When No Arguments Are Given
Shows unstaged changes in the working tree — identical to git diff with no arguments. Use --staged to show staged changes instead, or --all to show both.
When a Commit Is Given
Resolves the token to a full hash and passes it to git diff, showing the diff between that commit and the working tree.
When a File Is Given
Resolves the token to a file path and runs git diff -- <path>, showing unstaged changes to that file. With --staged the staged changes are shown (git diff --staged -- <path>); with --all both are shown (git diff HEAD -- <path>).
When a Commit Range Is Given
Tokens of the form left..right are resolved on each side and forwarded to git diff. Branch names, HEAD, and tags that can’t be resolved as short IDs are passed through to git unchanged, so all standard range forms work:
git loom diff HEAD~3..HEAD
git loom diff main..HEAD
git loom diff ab..3c
When a Commit and a File Are Both Given
The commit is placed before -- and the file after, limiting the diff to that file at that commit.
Target Resolution
Single tokens (not ranges) are resolved in this order:
- Files — short ID or repository-relative path (checked before commits)
- Commits — short ID, partial hash, or full hash
Range endpoints use lenient resolution: if a token cannot be resolved as a short ID or hash it is passed to git as-is, allowing HEAD, branch names, and tags.
Examples
Show unstaged changes
git loom diff
# Equivalent to: git diff
Show staged changes
git loom diff --staged
# Equivalent to: git diff --staged
Show all changes, staged and unstaged
git loom diff --all
# Equivalent to: git diff HEAD
Diff a commit by short ID
git loom diff ab
# Shows the diff between commit "ab" and the working tree
Diff a file by short ID
git loom diff ma
# Shows unstaged changes to the file with short ID "ma"
Diff a commit range
git loom diff ab..d0
# Shows what changed between those two commits
Limit diff to a file at a specific commit
git loom diff ab ma
# Equivalent to: git diff <hash-of-ab> -- src/auth/login.rs
Prerequisites
- A non-bare git repository.
- For short IDs: upstream tracking configured on the current branch (same as
git loom status).
trace
Show the latest command trace — a detailed audit trail of every git operation performed by the last loom command.
Usage
git loom trace
No arguments.
What It Does
Every time you run a loom command that modifies the repository (e.g. fold, commit, drop, reword, split, absorb, branch, push, update), git-loom records a trace file to .git/loom/logs/. The trace captures:
- Every git command executed, with the full argument list
- Timing for each command (in milliseconds)
- Success/failure status
- stderr output on failure
- Rebase todo annotations — both the original git todo and the generated todo
Only the 10 most recent trace files are kept; older ones are automatically pruned.
Running git loom trace prints the latest trace file to stdout with colored output.
Output Format
[2026-03-04 14:30:00.123] git loom fold aa bb
================================================================================
[git] rebase --interactive --autostash ...abc1234 [230ms]
[original git todo]
pick abc1234 First commit
noop
[generated todo]
label onto
reset onto
pick abc1234 First commit
[git] reset --hard HEAD [5ms]
[git] commit --amend --no-edit [12ms] FAILED
[stderr]
error: could not apply abc1234
- Header — timestamp and the full command line
- Command entries — program, arguments, duration, and optional
FAILEDmarker - Annotations — rebase todo content (original and generated)
- stderr — shown only for failed commands
Colors
When output is a terminal:
- Header: bold
- Command entries: cyan (with
FAILEDin red bold) - Annotation labels: yellow
- Annotation content: dimmed
- stderr: red
Storage
Trace files are stored at .git/loom/logs/<timestamp>.log with the naming pattern YYYY-MM-DD_HH-MM-SS_mmm.log. The file path is printed at the end of the output.
Examples
After a fold operation
git loom fold aa bb
git loom trace
# Shows the full sequence: rebase, reset, commit --amend, etc.
After a failed rebase
git loom drop aa
# x Rebase failed with conflicts — aborted
git loom trace
# Shows the rebase command with FAILED status and stderr output
No trace available
git loom trace
# x No log files found
# › Run a command first to generate a log
Notes
- Read-only commands (
status,trace) do not generate trace files - The
internal-write-todosubprocess does not generate its own trace - Trace files are plain text and can be inspected directly in
.git/loom/logs/
continue
Resume a paused loom operation after resolving rebase conflicts.
Usage
git loom continue
When to Use It
When an in-scope loom command (commit, update, absorb, drop, fold) encounters a
rebase conflict, it pauses instead of aborting. The operation is saved to
.git/loom/state.json and the process exits with code 0.
The terminal output shows which files are conflicted and what to do next:
! Conflicts detected — resolve them with git, then run:
loom continue to complete the commit
loom abort to cancel and restore original state
Once you’ve resolved conflicts and staged the resolution:
# resolve conflicts in your editor, then:
git add <resolved-files>
git loom continue
What It Does
- Loads the saved state from
.git/loom/state.json - If a rebase is still in progress, runs
git rebase --continue- If that stops again (a conflict, or anything else): stays paused, keeps the state file, exits successfully
- If it only reaches the next
editstep: stays paused, keeps the state file, says so - If it completes: moves on
- If no rebase is in progress (e.g. you already ran
git rebase --continuemanually): skips to dispatch - Dispatches to the interrupted command’s post-rebase work (restoring staged patches, printing the success message, etc.)
- Deletes the state file on success
Double Conflicts
If your branch has multiple conflicting commits, each loom continue may hit
a new conflict at the next commit. Repeat the resolve-and-continue cycle as
many times as needed:
git loom commit -b feature-auth -m "add auth" zz
# ! Conflicts detected...
git add auth.rs && git loom continue
# ! Conflicts remain — resolve them and run `loom continue` again
git add shared.rs && git loom continue
# ✓ Created commit `a1b2c3d` on branch `feature-auth`
Which Commands Are Paused
| Command | Pauseable |
|---|---|
update | ✓ |
commit | ✓ |
absorb | ✓ |
drop <commit> | ✓ |
reword <commit> | ✓ |
fold (simple paths) | ✓ |
drop <branch> | — (aborts immediately) |
reword <branch> | — (a rename never rebases) |
split | — (aborts immediately) |
fold (edit/multi-phase paths) | — (aborts immediately) |
Commands Allowed While Paused
While a loom operation is paused, most commands are blocked. The following are still available:
show— inspect commitstrace— check recent command outputcontinue— resume the paused operationabort— cancel the paused operation
Without a Saved State File
If git has a rebase or merge in progress that no loom state file describes,
loom continue drives it (git rebase --continue, or git merge --continue)
and reports the outcome, including a rebase that only reached the next edit
step.
git loom continue
# ✓ Completed the rebase git had in progress (no loom state, so nothing else was done)
Only git’s own step runs: with no state file there is no command to finish off,
so no saved patch is re-staged, no temp branch removed, and no per-command
success line such as ✓ Updated branch … is printed — just the generic line
above.
Pausing at an edit Step
git rebase --continue exits successfully when it only advances to the next
edit step, so a completed run and a rebase that is still half-done look the
same from outside. Loom checks and says which one happened:
! The `loom drop` is paused at an `edit` step — finish the work there, then run:
`loom continue` to carry on
`loom abort` to cancel and restore original state
With no state file there is no command to name, so the line opens with “The
rebase is paused at an edit step”, and loom abort is offered as “to cancel it
(no loom state to roll back)” — there is nothing else to undo.
Error: No Operation in Progress
With neither a state file nor a rebase or merge in progress:
git loom continue
# error: No loom operation is in progress
See Also
abort— cancel instead of resuming
abort
Cancel a paused loom operation and restore the repository to its original state. Without a saved state file there is nothing to roll back, so only the rebase or merge git has in progress is canceled.
Usage
git loom abort
When to Use It
When a loom operation is paused due to a conflict and you decide you don’t want
to complete it, loom abort cancels the operation and rolls back all changes
made so far:
git loom commit -b feature-auth -m "add auth" zz
# ! Conflicts detected...
git loom abort
# ✓ Aborted `loom commit` and restored original state
What It Does
- Loads the saved state from
.git/loom/state.json - Aborts the active rebase (if one is in progress)
- Applies rollback:
- Hard-resets HEAD to the pre-operation state
- Restores all branch refs to their pre-operation positions
- Deletes any branches that were created during the operation
- Re-applies pre-existing staged changes (if any were saved aside)
- Re-applies working-tree changes (if any were saved)
- Deletes the state file
- Reports success
Special case: commit
After aborting a commit, the committed content is returned to the working
tree as unstaged changes (via git reset --mixed) rather than being
discarded. Your work is preserved; the commit is simply undone.
When the Abort Itself Fails
git rebase --abort (or git merge --abort) can fail, typically because
another git process holds .git/index.lock. Loom then reports the failure and
keeps the state file instead of rolling back on top of a rebase that is still
running — rerun loom abort once the repository is free.
A reset or a staged-patch restore in the rollback that follows behaves the same way: the state file stays, because it is the only record of what is left to undo, and the message names the half-applied rollback rather than git, which has finished its own abort by then. A temp branch that will not delete, or a working-tree patch that will not re-apply, only warns — the rollback carries on and the state file goes.
For that same reason, never delete .git/loom/state.json to get unstuck. Once
it is gone, loom abort can only run git’s own abort: a temp branch, a
pre-rebase commit, or a saved staged patch would all be stranded. If loom
reports the file as corrupted, move it aside rather than removing it.
Without a Saved State File
If git has a rebase or merge in progress that no loom state file describes — a
command that failed before it could save state, or one you started with raw
git — loom abort cancels it anyway:
git loom abort
# ✓ Canceled the rebase git had in progress (no loom state to roll back)
A merge is canceled the same way, with git merge --abort.
There is no loom state here, so there is nothing to roll back beyond what git itself undoes: if a loom command had already created a commit or a temp branch, or set staged changes aside, before its rebase started, none of that is undone.
Error: No Operation in Progress
With neither a state file nor a rebase or merge in progress:
git loom abort
# error: No loom operation is in progress
See Also
continue— resume instead of canceling
Configuration
Git Config Settings
| Setting | Values | Default | Description |
|---|---|---|---|
loom.remote-type | github, gitlab, azure, gerrit, plain | Auto-detected | Override the remote type for git loom push |
loom.push-remote | Any remote name | Auto-detected | Override which remote to push to (e.g., personal for fork workflows) |
loom.hideBranchPattern | Any prefix string | local- | Prefix for branches hidden from loom status by default |
loom.pruneGoneBranches | true, false | false | Let git loom update remove local branches whose remote branch is gone |
loom.remote-type
By default, git loom push auto-detects the remote type:
- GitHub — if the remote URL contains
github.com - GitLab — if the remote URL contains
gitlab - Azure DevOps — if the remote URL contains
dev.azure.com - Gerrit — if
.git/hooks/commit-msgcontains “gerrit”, or you confirm the prompt when the remote looks like Gerrit - Plain Git — otherwise
You can override this with:
git config loom.remote-type github # Force GitHub push (push + PR, stacked PRs)
git config loom.remote-type gitlab # Force GitLab push (merge request push options)
git config loom.remote-type azure # Force Azure DevOps push (push + open PR)
git config loom.remote-type gerrit # Force Gerrit push (refs/for/<branch>)
git config loom.remote-type plain # Force a plain force-with-lease push
loom.push-remote
By default, git loom push uses the integration branch’s remote for pushing. One exception: if the integration branch tracks a remote named upstream and a remote named origin also exists, pushes go to origin automatically (the standard GitHub fork convention).
For non-standard fork setups where your remotes have different names, set this explicitly:
git config loom.push-remote personal
For example, with remotes:
origin→ upstream read-only repositorypersonal→ your fork (where you push)
Now git loom push will push to personal regardless of remote names.
loom.hideBranchPattern
Branches whose names start with this prefix are hidden from loom status by default — both the branch section and its commits are suppressed. Pass --all to show them.
git config loom.hideBranchPattern "local-" # default: hide local-* branches
git config loom.hideBranchPattern "secret-" # hide secret-* branches instead
git config loom.hideBranchPattern "" # disable hiding entirely
Hidden branches remain fully accessible to the other loom commands (fold, drop, commit, etc.), except push, which never publishes a hidden branch nor a branch stacked on one.
When creating or renaming a branch to a name that matches this prefix, git-loom prints a warning.
Environment Variables
| Variable | Description |
|---|---|
NO_COLOR | Disable colored output when set (follows the NO_COLOR standard) |
TERM | Colors are automatically disabled when TERM=dumb |
CLI Flags
| Flag | Description |
|---|---|
--no-color | Disable colored output |
--theme <auto|dark|light> | Set the graph color theme (default: auto) |
--theme
Controls the color palette used for graph output.
| Value | Behavior |
|---|---|
auto | Detect the terminal background and choose dark or light automatically. Falls back to dark if detection fails or output is not a TTY. |
dark | Always use the dark theme (optimized for dark terminal backgrounds). |
light | Always use the light theme (optimized for light terminal backgrounds). |
git loom --theme light
git loom --theme dark status