forcepush is a project-local CLI for creating, setting up, checkpointing, archiving, restoring, and operating Git worktrees.
Its archive model is deliberately small: Git already has a shared object store, so a worktree's source changes are checkpointed there before the checkout is deleted. Ignored dependency and build directories disappear with the checkout. Restoring recreates the worktree, turns the checkpoint back into uncommitted changes when it is safe to do so, and runs setup again.
Node.js 20 or newer and Git are required.
npm install --global forcepushTo build from source instead:
npm install
npm run build
npm linkThe package installs forcepush, the compatibility command force-push, and the shorter fp command.
From the main clone or any linked worktree:
forcepush init --yes
forcepush create billing
forcepush run billing -- npm install
forcepush session billing
forcepush archive billing
forcepush unarchive billingInitialization creates:
$FORCE_PUSH_HOME/settings.yaml user-level defaults and shared setup rules
.agents/force-push.yaml committed project configuration
.force-push/state.json local managed-worktree registry
.force-push/archives/ local archive manifests and opt-in ignored data
.force-push/worktrees/ default checkout location
FORCE_PUSH_HOME defaults to $XDG_STATE_HOME/force-push or ~/.local/state/force-push. The local registry, archives, and default checkout directory are added to .gitignore. The .agents configuration is intentionally not ignored so a team can share its setup recipe.
There are two configuration layers. User-level settings.yaml holds personal setup rules and session profiles across projects. Project-level .agents/force-push.yaml or TOML is committed with the repository. Global setup, cleanup, and ignored-path rules run first; project rules are appended. Project session profiles override global profiles with the same name.
The generated project configuration looks like:
version: 1
worktrees:
directory: .force-push/worktrees
branchPrefix: work/
setup:
files:
- from: .env
to: .env
mode: symlink
optional: true
- from: .npmrc
to: .npmrc
mode: copy
optional: true
scripts:
- npm install
cleanup:
scripts:
- npm run stop-dev || true
archive:
includeIgnored:
- .local-data
session:
command:
- /bin/sh
- -l
profiles:
review:
- /bin/sh
- -lc
- npm test && exec $SHELL -lRun forcepush init --toml to create .agents/force-push.toml instead.
Setup sources are resolved from the main clone. Destinations are constrained to the worktree. A rule may copy or symlink a file or directory. Setup scripts run in order after creation and restoration with these variables:
Relative setup sources in user settings resolve from the directory containing settings.yaml; relative sources in project configuration resolve from the main clone.
FORCE_PUSH_MAIN_ROOTFORCE_PUSH_WORKTREEFORCE_PUSH_WORKTREE_NAMEforrunandsession
Copy/symlink destinations and generated setup output should normally be ignored by Git. Otherwise they are real worktree changes and will be included in a checkpoint.
archive.includeIgnored is an explicit list of relative files or directories that are precious despite being ignored, such as a local development database. These paths are copied into the archive directory and restored after setup. It is not a glob list. Leave it empty for metadata-only archives.
| Command | Purpose |
|---|---|
init [--toml] |
Create project configuration and local storage |
settings [--show] |
Print/create the user-level settings path or contents |
create NAME |
Create a branch/worktree, register it, and run setup |
adopt PATH |
Register an existing worktree |
list [--json] |
List managed, archived, main, and unmanaged worktrees for this project |
list --all [--json] |
List managed worktrees across registered projects |
status [NAME] [--json] |
Show Git changes or archived checkpoint state |
path [NAME] |
Print a managed worktree path |
setup [NAME] |
Re-run configured setup |
run NAME -- COMMAND... |
Run a non-interactive command in a worktree |
session [NAME] |
Start the configured interactive command, or a login shell |
archive [NAME] |
Checkpoint changes, verify reachability, and remove the checkout |
unarchive NAME |
Recreate the checkout and restore its WIP |
remove [NAME] |
Remove a clean worktree without archiving |
lock / unlock |
Protect or unprotect a worktree with Git's native lock |
prune [--dry-run] |
Prune stale Git worktree administrative entries |
doctor [--json] |
Validate config, registry, checkouts, manifests, and refs |
config [--show] |
Print the config path or contents |
archives [--json] |
List active and restored archive history |
restore-data ID |
Retry restoring opt-in ignored data to an active worktree |
forget ID --force |
Permanently delete archive metadata and its safety ref |
Most commands accept a managed name, UUID, or absolute path. When run from a managed worktree, commands with an optional worktree argument target the current one.
forcepush create api # branch work/api from HEAD
forcepush create api --branch feature/api # custom branch
forcepush create review --start-point v2.0.0 # custom starting ref
forcepush create inspect --detach # detached worktree
forcepush create api --no-setup # skip setup onceIf a requested branch already exists and is not checked out elsewhere, it is reused. Worktree names cannot contain path separators.
session starts session.command from the project config. With no configured command, it starts the user's login shell. Named commands under session.profiles can be selected with --profile.
forcepush session api
forcepush session api --profile review
forcepush run api -- npm test
forcepush run api -- git status --shortExit codes from the child command are preserved.
Archive is ordered so the checkout is never deleted before its source state is provably reachable:
- Run optional cleanup scripts.
- Save explicitly configured ignored paths.
- Read the current HEAD, branch, and Git status.
- If dirty, run
git add -Aand create a local checkpoint commit withForce-Push-ArchiveandForce-Push-Parenttrailers. - Pin the clean HEAD or checkpoint under
refs/force-push/archives/<archive-id>. - Verify both the ref and commit object.
- Write
.force-push/archives/<archive-id>/manifest.jsonatomically. - Remove the worktree and prune Git's administrative entry.
- Mark the registry entry archived.
The branch is never deleted. Checkpoints are local; pushing is not required for local durability.
Unarchive performs the inverse:
- Verify that the branch still points to the checkpoint, or contains it as an ancestor.
- Recreate the worktree at its prior path (or
--path). - If the branch still points exactly at the checkpoint, run
git reset --mixed <original-head>. Files stay on disk and become modified/untracked again. - Mark the worktree active, run setup, and restore configured ignored paths.
If the branch advanced and still contains the checkpoint, restoration succeeds but keeps the checkpoint committed. If the branch diverged and no longer contains it, restoration refuses to proceed. The manifest and safety ref remain intact, and doctor verifies them.
For a clean archive, branch movement is also explicit: restoration refuses to substitute a different tree unless --latest is passed. If setup fails after source restoration, the checkout stays active, configured ignored data is still restored, and forcepush setup NAME can retry setup without repeating the archive restore. If copying opt-in ignored data itself fails, the source checkout still becomes active and the archive remains retained; fix the destination and use forcepush restore-data ARCHIVE_ID.
The checkpoint uses git add -A, so the distinction between staged and unstaged changes is flattened. File contents and untracked source files are restored, but all restored WIP is unstaged. Ignored files are discarded unless explicitly listed in archive.includeIgnored.
Every archived manifest records snapshotSha, originalHead, and archiveRef. If normal restoration is blocked, inspect them with:
forcepush status NAME --json
git show refs/force-push/archives/ARCHIVE_IDDo not run forget --force until the work is merged, pushed, or otherwise preserved. Use forcepush archives to obtain the archive ID. Forgetting an actively archived worktree also removes its registry entry; forgetting retained history for an active worktree leaves the worktree registered. The command removes the safety ref and may make a detached or unmerged checkpoint unreachable.
npm run check
npm test
npm pack --dry-runThe integration suite creates real temporary Git repositories and tests only through the compiled CLI and Git's public interface.