NAME
Langertha::Raider::Worktree - Internal git worktrees of raider: create, lock, inspect and remove them with the git command line
VERSION
version 0.504
SYNOPSIS
my $git = Langertha::Raider::Worktree->new;
my $top = $git->repository_top($root); # { top => '/repo' } or { error => '...' }
my $base = $git->base_ref( $root, 'fresh' ); # { ref => 'origin/HEAD', commit => 'a1b2...' }
my $made = $git->create(
source => '/repo', # the working tree the copy comes from
path => '/repo/.claude/worktrees/agent-1',
branch => 'worktree-agent-1',
base => $base->{commit},
); # { path, branch, base, copied => [ '.env' ] }
$git->lock( $made->{path}, 'raider agent agent_1' );
...
$git->unlock( $made->{path} );
my $changes = $git->changes( $made->{path}, $base->{commit} ); # { status => [ '?? new.txt' ], commits => 0 }
$git->remove( dir => '/repo', path => $made->{path}, branch => $made->{branch} )
unless @{ $changes->{status} } || $changes->{commits};
DESCRIPTION
Internal module. Its interface may change without notice.
The git work behind worktree isolation (ADR 0032 §1): subagents with isolation: "worktree" (Langertha::Raider::Subagent) and the enter_worktree / exit_worktree tools share it. Everything goes through the git command line ("git") with a fixed argument list -- git -C DIR ..., no shell -- and the variables GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE and GIT_COMMON_DIR removed, so a raider started from a git hook still works on the directory it names.
Every method answers with a hash reference; a failure is a value, never an exception: { error => 'git worktree add: fatal: ...' } (the git command and the first line git wrote to standard error). A caller tests $r->{error}.
git
The git program. Default git, found on PATH.
toplevel
my $r = $git->toplevel($dir); # { top => '/repo' } or { error => ... }
The top of the working tree $dir is in (git rev-parse --show-toplevel): the main checkout, or the worktree when $dir is inside one. An error when $dir is not inside a git working tree.
repository_top
my $r = $git->repository_top($dir); # { top => '/repo' } or { error => ... }
The main checkout of the repository $dir is in: the parent of the absolute git rev-parse --git-common-dir, so a $dir inside a linked worktree finds the same top as one in the main checkout. An error when $dir is not inside a git working tree.
base_ref
my $r = $git->base_ref( $dir, 'fresh' ); # { ref => 'origin/HEAD', commit => '...' }
my $r = $git->base_ref( $dir, 'head' ); # { ref => 'HEAD', commit => '...' }
The base a new worktree branches from (ADR 0032 §2, Claude Code's worktree.baseRef): fresh (the default for any other value) is origin/HEAD when it resolves to a commit, else the HEAD of $dir; head is the HEAD of $dir -- inside a worktree that worktree's. Nothing is fetched. An error when HEAD does not resolve (a repository without a commit).
branch_exists
my $yes = $git->branch_exists( $dir, 'worktree-agent-1' );
True when the repository of $dir has a local branch of that name.
create
my $r = $git->create( source => $tree_top, path => $path, branch => $branch, base => $commit );
# { path, branch, base, copied => [ ... ], copy_errors => [ ... ] } or { error => ... }
A new worktree at path on a new branch branch from base (a commit or ref; git worktree add --no-track -b BRANCH PATH BASE, run in source), the parent directories made first. Then the .worktreeinclude of source (.gitignore syntax) is honoured: the untracked files it matches (git ls-files --others --ignored --exclude-from=.worktreeinclude) that the repository ignores (git check-ignore) are copied to the same relative path, with their mode; copied lists them, copy_errors what could not be copied (the worktree is made either way). Without the file nothing is copied.
checkout
my $r = $git->checkout( dir => $top, path => $path, branch => $branch ); # { path, branch } or { error }
An existing branch checked out into a new worktree at path (git worktree add PATH BRANCH, run in dir): a removed worktree of a resumed agent comes back on its branch. No .worktreeinclude copy -- only a new worktree gets one.
lock
my $r = $git->lock( $path, 'raider agent agent_1' ); # {} or { error }
unlock
my $r = $git->unlock($path); # {} or { error }
git worktree lock --reason REASON PATH and git worktree unlock PATH, run in the worktree's parent: a locked worktree is not pruned and not removed without --force twice.
changes
my $r = $git->changes( $path, $base ); # { status => [ ' M lib/A.pm', '?? new.txt' ], commits => 2 }
What the worktree at $path has that its base has not (ADR 0027 §7): the lines of git status --porcelain -- untracked files count, ignored ones do not -- and the number of commits on its HEAD beyond $base (git rev-list --count BASE..HEAD). Without $base only the status is asked and commits is 0.
remove
my $r = $git->remove( dir => $top, path => $path, branch => $branch ); # {} or { error }
my $r = $git->remove( dir => $top, path => $path, branch => $branch, force => 1 );
Removes the worktree (git worktree remove [--force] PATH, run in dir) and then deletes its branch (git branch -D BRANCH). A worktree git refuses to remove keeps its branch; the error says which step failed.
list
my $r = $git->list($dir);
# { worktrees => [ { path => '/repo', head => '...', branch => 'main', main => 1 },
# { path => '/repo/.claude/worktrees/agent-1', branch => 'worktree-agent-1', locked => 'raider agent agent_1' } ] }
The worktrees of the repository of $dir (git worktree list --porcelain), the main checkout first (main 1): path, head, branch (without refs/heads/), and when git says so detached, bare, locked (the reason, or 1) and prunable.
SEE ALSO
SUPPORT
Issues
Please report bugs and feature requests at https://github.com/Getty/langertha-raider/issues.
IRC
Join #langertha on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.