@rse/vcs
v0.9.4
Published
Worktree Management for Git Version Control System
Downloads
755
Maintainers
Readme
VCS
Worktree Management for Git Version Control System
Abstract
This is a small Command-Line Interface (CLI) for managing a Git
worktree-based workflow inside a basedir. The basedir directly contains
the Git repository clone (the master worktree, usually named master
or main), any number of Git worktrees of it, and a symbolic link
active, which points to either the master worktree or one of the
worktrees. The master worktree is auto-detected as the directory the
.git files of the worktrees point into (or, without any worktrees,
as the single directory with a .git directory). Worktrees are forked from a parent branch, synchronized
with it, and merged back into it, where Git conflicts are resolved
semantically and safely with the help of Claude Code (claude -p).
Installation
$ npm install -g @rse/vcsUsage
$ vcs init [-v <num>] [-d <basedir>] [-r <repo-url>]
$ vcs list [-v <num>] [-d <basedir>]
$ vcs active [-v <num>] [-d <basedir>]
$ vcs activate [-v <num>] [-d <basedir>] [<worktree>]
$ vcs fork [-v <num>] [-d <basedir>] [-b <branch>] <worktree> [<parent-branch>]
$ vcs sync [-v <num>] [-d <basedir>] [-s] [<worktree>]
$ vcs merge [-v <num>] [-d <basedir>] [-m merge|rebase|squash] [-s] [<worktree>]
$ vcs resolve [-v <num>] [-d <basedir>] [-s] [<worktree>]
$ vcs shuffle [-v <num>] [-d <basedir>] [-s] <worktree>
$ vcs clean [-v <num>] [-d <basedir>] [-i] [<worktree>]
$ vcs rename [-v <num>] [-d <basedir>] <worktree-old> <worktree-new>
$ vcs destroy [-v <num>] [-d <basedir>] <worktree>-v <num>,--verbose <num>Verbosity level:0(default) prints information and errors only,1additionally prints every executed command (as$ <command>) and every action of Claude during conflict resolution (asclaude> <tool>: <detail>), and2additionally prints a brief comment (as# <comment>) before every executed command and every comment of Claude.-d <basedir>,--basedir <basedir>Base directory. By default, it is auto-detected as the current directory or the nearest parent directory containing anactivesymbolic link (forinit: the current directory).init [-r <repo-url>]Create the basedir with a clone of the Git repository<repo-url>in the master worktree, named after the default branch of the repository, and activate it. If a master worktree already exists,-rhas to be omitted and the existing master worktree is taken as is.listList all worktrees as a table with their directory, worktree name (-if not located directly under the basedir), branch, and anXmarker for the master, the active, and the current worktree (the one containing the current directory). The master worktree is rendered in bold and the active worktree in blue.activeShow the active worktree.activate [<worktree>]Activate the worktree (default: the worktree containing the current directory, determined via its Git root) by re-pointing theactivesymbolic link.fork [-b <branch>] <worktree> [<parent-branch>]Create the worktree with the new branch<branch>(default:<worktree>), based on the parent branch<parent-branch>(default: the branch of the active worktree). The parent branch is recorded forsync,merge, anddestroy.sync [-s] [<worktree>]Fetchorigin, fast-forward the parent branch, and rebase the worktree (default: the worktree containing the current directory) onto it, resolving conflicts. If the worktree is already up-to-date with the parent branch, nothing else happens. Uncommitted changes (including untracked files) are stashed before and restored after the rebase, resolving conflicts, too. On unresolvable conflicts, the rebase or restoration is left in progress for manual resolution. For a worktree without a recorded parent branch (like the master worktree), its upstream branch (likeorigin/master) is fetched from its remote and used instead of the parent branch.merge [-m merge|rebase|squash] [-s] [<worktree>]Merge the worktree (default: the worktree containing the current directory) into its parent branch with a merge commit (merge, default), a rebase and fast-forward (rebase), or a single squashed commit (squash), resolving conflicts. On unresolvable conflicts, the merge is aborted. Uncommitted changes (including untracked files) of the worktree of the parent branch are stashed before and restored after the merge, resolving conflicts, too.resolve [-s] [<worktree>]Resolve the conflicts in the worktree (default: the worktree containing the current directory) and continue its in-progress operation.shuffle [-s] <worktree>Move all staged, unstaged, and untracked files of the worktree containing the current directory into the working copy of the worktree<worktree>, where they stay uncommitted, resolving conflicts, both with its branch and with its own uncommitted changes (temporarily committed during the operation). On success, the current worktree is cleaned (seeclean). On unresolvable conflicts, the current worktree is kept untouched for manual resolution in the other worktree.clean [-i] [<worktree>]Abort any in-progress operation of the worktree (default: the worktree containing the current directory), reset it to itsHEAD, and remove all untracked files (with-ialso all ignored files), so the worktree looks as freshly forked.rename <worktree-old> <worktree-new>Rename the worktree, both its directory and its Git worktree reference. If its branch is named after the worktree, the branch is renamed, too, and child branches are re-pointed to it. If the worktree was active, the renamed worktree is activated.destroy <worktree>Remove the worktree and its branch, if the branch has landed on its parent branch. If the worktree was active, the master worktree is activated.-s,--safeNever touch non-content conflicts (binary, submodule, modify/delete, rename, delete/delete), but always escalate them. Without this option, they are resolved only if their intent is unambiguous.
Behind the Scenes
The following outlines the major logical Git operations performed per
command (similar to the output of -v 1, but without the -C <dir>
options and the minor read-only queries). Comments name the worktree
each operation runs in and the conditions under which it happens.
init:# (only without an existing master worktree) git ls-remote --symref -- <repo-url> HEAD # determine default branch <name> git clone --quiet -- <repo-url> <name> ln -s <name> active # (atomically)list:git worktree list --porcelainactive:readlink activeactivate:git rev-parse --show-toplevel # (only without <worktree>) ln -s <worktree> active # (atomically)fork:git branch --show-current # in active worktree (only without <parent-branch>) git worktree add --quiet -b <branch> <worktree> <parent-branch> git config branch.<branch>.vcsParent <parent-branch>sync:git config --get branch.<branch>.vcsParent # determine <parent-branch> git fetch --quiet origin # (only with an "origin" remote) git merge --quiet --ff-only origin/<parent-branch> # in worktree of <parent-branch> (if behind), or git update-ref refs/heads/<parent-branch> refs/remotes/origin/<parent-branch> # (if not checked out) git for-each-ref --format=... refs/heads/<branch> # (only without <parent-branch>: determine <remote>/<upstream-branch>) git fetch --quiet <remote> # (only without <parent-branch>: use <remote>/<upstream-branch> as <parent-branch>) git merge-base --is-ancestor <parent-branch> HEAD # (stop here, if already up-to-date) git stash push --quiet --include-untracked # (only with uncommitted changes) git rebase --quiet <parent-branch> vcs resolve # (only on conflicts, see "resolve") git stash pop --quiet # (only with stashed changes) vcs resolve # (only on conflicts, see "resolve") git reset --quiet && git stash drop --quiet # (only on resolved conflicts)merge:git config --get branch.<branch>.vcsParent # determine <parent-branch> git stash push --quiet --include-untracked # in worktree of <parent-branch> (only with uncommitted changes) # mode "merge" (in worktree of <parent-branch>) git merge --quiet --no-ff --no-edit <branch> vcs resolve # (only on conflicts, see "resolve") git commit --quiet --no-edit # (only on resolved conflicts) # mode "squash" (in worktree of <parent-branch>) git merge --quiet --squash <branch> vcs resolve # (only on conflicts, see "resolve") git commit --quiet --no-edit # (only with staged changes) # mode "rebase" git rebase --quiet <parent-branch> # in <worktree> vcs resolve # in <worktree> (only on conflicts, see "resolve") git merge --quiet --ff-only <branch> # in worktree of <parent-branch> git reset --quiet --merge # (only on unresolved conflicts in modes "merge" and "squash") git rebase --abort # (only on unresolved conflicts in mode "rebase") git merge-base --is-ancestor <branch> <parent-branch> # (not in mode "squash") git stash pop --quiet # in worktree of <parent-branch> (only with stashed changes) vcs resolve # (only on conflicts, see "resolve") git reset --quiet && git stash drop --quiet # (only on resolved conflicts)resolve:git status --porcelain=v1 -z # determine and classify unmerged files cp <file> .git/.../vcs-resolve/<head>-<theirs>/ # back up all conflicted files git rm --quiet -- <file> # (only for delete/delete conflicts) git checkout --ours|--theirs -- <file> # (only for binary conflicts changed on one side only) git update-index --cacheinfo <mode>,<oid>,<file> # (only for submodule conflicts changed on one side only) claude -p <prompt> ... # (only for remaining conflicts) git add -- <file> ... # stage all fully resolved files git -c core.editor=true <operation> --continue # (only with in-progress operation; repeatedly on rebase)shuffle:# in current worktree: snapshot into a stash-like commit (current worktree stays untouched) GIT_INDEX_FILE=<tmp-index> git add --all # (on a copy of the index) GIT_INDEX_FILE=<tmp-index> git write-tree # determine <working-tree> git write-tree # determine <index-tree> git commit-tree <index-tree> -p HEAD -m "vcs shuffle: index" # determine <index-commit> git commit-tree <working-tree> -p HEAD -p <index-commit> -m "vcs shuffle: working copy" # determine <commit> # in <worktree>: apply snapshot git add --all # (only with uncommitted changes) git commit --quiet --no-verify --no-gpg-sign -m "vcs shuffle: temporary commit" # (only with uncommitted changes) git stash apply --quiet <commit> vcs resolve # (only on conflicts, see "resolve") git reset --quiet HEAD~1 # (only with temporary commit), or git reset --quiet # (only without temporary commit) # in current worktree vcs cleanclean:git <operation> --abort # (only with in-progress operation) git reset --quiet --hard HEAD git clean --quiet --force -d [-x] # ("-x" only with option "-i")rename:git worktree repair <worktree-old> git worktree move <worktree-old> <worktree-new> mv .git/worktrees/<worktree-old> .git/worktrees/<worktree-new> # (and adjust ".git" file of worktree) git branch -m <worktree-old> <worktree-new> # (only if branch is named after worktree) git config branch.<child>.vcsParent <worktree-new> # (only for child branches) ln -s <worktree-new> active # (only if worktree was active, atomically)destroy:git merge-base --is-ancestor <branch> <parent-branch> # check whether branch landed, or else git merge-tree --write-tree <parent-branch> <branch> # check whether squashed branch landed ln -s <master> active # (only if worktree was active, atomically) git worktree remove <worktree> git branch --quiet -D <branch>
Conflict Resolution
Conflicts are resolved semantically: trivial, independent, and compatible conflict hunks are resolved, while contradicting hunks are kept as-is with their conflict markers and escalated. No change is ever lost: all conflicted files are backed up before, every resolution is checked for change preservation, and files with leftover conflict markers are never staged. Only the results and one-line information on escalated conflicts are shown.
License
Copyright © 2026 Dr. Ralf S. Engelschall (http://engelschall.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
