Worktrunk 封装Git Worktrees 的CLI工具
最早出现在Claude Code issues里面, 现在作者已经发布到:https://worktrunk.dev/
这不是一份 Bug 报告——而是我与 Claude 深度磨合数周后,沉淀出的一套开发范式。我觉得这东西很有价值,值得分享。现在,凡是能用到 Claude Code 的开发场景,我基本都用这套流程。
这套打法的核心优势在于:它通过封装 Git worktree 的核心操作,允许你在零开销的情况下运行任意数量的 Claude 实例。
顺便提一下,这些函数本身也是我用 Claude Code 辅助编写的,甚至连这份 Issue 的摘要也是它帮我生成的。
无需保持此 Issue 开启状态。
Git Worktree 工作流
我使用的整体工作流如下:
- 用
git worktree-llm feature-name起手,开启新功能开发。 - 在 Claude 的辅助下推进功能开发。
- 准备合并时,执行
git worktree-merge。 - 变更被提交,worktree 关闭,代码经变基(rebase)后合并。
这创造了一种干净、高效的 AI 辅助开发流,并自动处理了所有 Git 操作,确保历史记录清爽整洁。
git worktree-llm
此函数旨在创建 Git worktree 并即刻启动 LLM 会话。这是 AI 辅助开发的极简流:
function git worktree-llm -d "Create a git worktree and start claude"
# Create the worktree, then run setup in background and start claude
if git worktree-create $argv
task setup-worktree </dev/null >/dev/null 2>&1 & disown
claude
end
end
函数解析:
- 创建一个新的 worktree,并透传所有参数给
git worktree-create。 - 后台运行初始化任务 (
task setup-worktree)。这是每个项目自定义的任务,通常包括:
- 安装依赖:如
uv sync - 启动守护进程:如
uv run dmypy run --timeout=3600 - 加载 Claude 配置:比如
allowedTools,处理那些没有全局配置的项目。
- 立即启动
claude(无需等待后台任务完成)。
(目前我们仍需敲两次回车,一次批准 MCP,一次批准加载父目录的 CLAUDE.md。希望未来 claude 能优化掉这一步。)
开始搬砖! 接下来就是正式干活了。
git worktree-merge
当工作完成时,执行:
git worktree-merge
此函数用于收尾:结束当前分支的工作并将其并入主干。
function git worktree-merge --description "Finish worktree and merge branch into main"
# Save the branch name before finishing the worktree
set branch (git branch --show-current)
set default_branch (git default-branch)
# Skip if already on default branch (let git worktree-finish handle this normally)
if test "$branch" = "$default_branch"
return 0
end
# Add all changes first
git add .
# Finish the worktree first
git worktree-finish; or return
# Rebase branch onto default branch first
set_color cyan
echo "⚙️ Rebasing $branch onto $default_branch"
set_color normal
git rebase $default_branch $branch; or return
git switch $default_branch; or return
# Now merge the branch (don't delete it)
set_color cyan
echo "🔄 Merging $branch into $default_branch"
set_color normal
git merge --no-edit $branch; or return
end
函数解析:
- 捕获当前分支名称。
- 将所有变更加入 Git 索引(git add)。
- 结束 Worktree(返回主仓库)。
- 将功能分支变基(Rebase)到默认分支之上。
- 切换回默认分支。
- 合并功能分支,无需手动输入提交信息。
辅助函数 (Supporting Functions)
上述核心逻辑依赖于以下几个辅助函数:
git worktree-create
为分支创建一个新 worktree 并跳转进去。如果分支不存在则新建,存在则直接复用。
function git worktree-create -d "Create a new git worktree and navigate to it"
set -l branch_name $argv[1]
# Try to switch to existing worktree or create a new one
# Silence errors because we'll handle branch creation below
if git worktree-switch $branch_name 2>/dev/null
# If success and already in worktree, just return
return 0
end
# No existing worktree, create one
set -l git_common_dir (git rev-parse --git-common-dir)
set -l repo_root (dirname $git_common_dir)
set -l worktree_path "$repo_root/.worktrees/$branch_name"
# Check if branch exists
if git show-ref --verify --quiet refs/heads/"$branch_name"
# Branch exists, don't use -b flag
git worktree add "$worktree_path" "$branch_name"
else
# Branch doesn't exist, create it
git worktree add "$worktree_path" -b "$branch_name"
end &&
cd "$worktree_path"
end
git worktree-switch
通过分支名跳转到现有的 worktree;如果分支存在但还没建 worktree,则自动创建。
function git worktree-switch --description "Navigate to a git worktree by branch name, creating it if needed"
if test (count $argv) -eq 0
echo "Error: Branch name required." >&2
return 1
end
set -l branch $argv[1]
# Check if inside a git repository
if not git rev-parse --is-inside-work-tree >/dev/null 2>&1
echo "Error: Not in a git repository." >&2
return 1
end
# Try to find existing worktree with this branch
set -l current_path ""
for line in (string split \n (git worktree list --porcelain | string collect))
if string match -q "worktree *" $line
set current_path (string replace "worktree " "" $line)
else if string match -q "branch refs/heads/$branch" $line
# Found matching branch worktree
cd $current_path
set_color green; echo -n "Switched to worktree for "; set_color --bold yellow; echo "$branch"; set_color normal
return 0
end
end
# No worktree found - check if branch exists and create if so
if git show-ref --verify --quiet refs/heads/$branch
# Always use the common git directory's parent as the repository root
# This works reliably whether we're in the main repo or a worktree
set -l git_common_dir (git rev-parse --git-common-dir)
set -l repo_root (dirname $git_common_dir)
# Use absolute path for the new worktree
set -l worktree_path "$repo_root/.worktrees/$branch"
if git worktree add "$worktree_path" "$branch"
cd "$worktree_path"
set_color green; echo -n "Created and switched to worktree for "; set_color --bold yellow; echo "$branch"; set_color normal
return 0
else
echo "Error: Failed to create worktree for branch '$branch'." >&2
return 1
end
else
echo "Error: No worktree found for branch '$branch' and branch does not exist." >&2
return 1
end
end
git worktree-finish
完成 worktree 的工作:提交变更,返回主仓库,并移除该 worktree。
function git worktree-finish --description "Finish work on a git worktree or branch without merging"
git diff --quiet HEAD || git commit-llm || return 1
# Save current branch and path for possible future use
set branch (git branch --show-current)
set default_branch (git default-branch)
set current_path (pwd)
set_color cyan
echo "🔀 Branch: $branch → $default_branch"
set_color normal
# Skip if already on default branch
test "$branch" = "$default_branch" && return 0
# Move back to main repo and remove worktree
set git_dir (git rev-parse --git-dir)
set common_dir (git rev-parse --git-common-dir)
if test "$git_dir" != "$common_dir"
# In worktree: go to main repo, remove worktree
set_color cyan
echo "📂 Moving to main repo"
set_color normal
cd (git rev-parse --git-common-dir)/..
git checkout $default_branch
set_color cyan
echo "🗑️ Removing worktree at $current_path"
set_color normal
git worktree remove "$current_path" 2>/dev/null
if test $status -ne 0
set_color yellow
echo "⚠️ Could not remove worktree at $current_path"
set_color normal
end
else if git checkout $default_branch 2>/dev/null
# Regular branch with available default branch, just switch
end
# Return the branch name as status so it can be captured
return 0
end
git commit-llm
基于暂存区的变更,利用 LLM 生成智能的提交信息(Commit Message)。
function git commit-llm -d "Create a commit with an LLM-generated message"
# Only add everything if there's nothing in the index
if test -z "$(git diff --cached --name-only)"
git add -A
end
# Get branch name for context
set branch_name (git branch --show-current)
set context "Write a concise, clear git commit message for branch '$branch_name'"
# Generate message using shared function and preserve newlines using -z
git llm-message "$context" | read -z msg || return 1
# Commit with the generated message
git commit -m "$msgCo-authored-by: Claude <no-reply@anthropic.com>"
end
git llm-message
使用结构化的 git diff 和分支数据,通过 LLM 生成格式规范的提交信息。
function git llm-message -d "Generate a commit message using LLM from diff and provided custom instruction"
# This function uses an XML format to structure data for the LLM:
# - <git-info> contains branch and recent commit metadata
# - <git-diff> contains the staged changes
# The LLM uses this structured format to generate a well-formatted commit message.
argparse debug -- $argv
or return # Exit if argparse finds an invalid option
# Show informative message
set files_count (git diff --cached --name-only | count)
set stat_summary (git diff --cached --shortstat)
# IMPORTANT: Using inline color codes in echo statements that are already
# redirected to stderr ensures that color codes never contaminate stdout
echo (set_color cyan)"📝 Processing $files_count files. $stat_summary. Generating commit message..."(set_color normal) >&2
# ... [Implementation details omitted for brevity, logic follows setting up temp file and calling LLM] ...
# (Please refer to the original English text for the full implementation logic of this function)
# [Use existing implementation from source]
# ...
cat $input_file | llm prompt -m gemini-2.5-flash-preview-04-17 \
-o temperature 0 \
-o google_search 0 \
-o thinking_budget 0 \
--no-log \
--system "$user_instruction" \
| read -z msg \
|| return 1
# Show generated message to user with colors
# Keep all color codes on stderr so they never contaminate stdout
echo (set_color green)"🧠 Generated message: \"$msg\""(set_color normal) >&2
# Return just the message to stdout for piping to other tools
# No color code cleanup needed because we never let them touch stdout
printf "%s" "$msg"
end
git default-branch
获取当前仓库的默认分支名(main 或 master)。
function git default-branch --description "Get the default branch (main or master) for this repo"
# Try to get the default branch from the origin remote
# This handles cases where the repo uses a non-standard default branch name
set -l remote_default_branch (git remote show origin 2>/dev/null | grep "HEAD branch" | sed 's/.*: //')
if test -n "$remote_default_branch"
echo $remote_default_branch
return 0
end
# If we can't get it from remote, check if main or master exists locally
if git show-ref --verify --quiet refs/heads/main
echo "main"
else if git show-ref --verify --quiet refs/heads/master
echo "master"
else
# Default to main if we can't determine
echo "main"
end
end