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.

https://raudssus.de/