Contents

Worktrunk 封装Git Worktrees 的CLI工具

最早出现在Claude Code issues里面, 现在作者已经发布到:https://worktrunk.dev/

这不是一份 Bug 报告——而是我与 Claude 深度磨合数周后,沉淀出的一套开发范式。我觉得这东西很有价值,值得分享。现在,凡是能用到 Claude Code 的开发场景,我基本都用这套流程。

这套打法的核心优势在于:它通过封装 Git worktree 的核心操作,允许你在零开销的情况下运行任意数量的 Claude 实例。

顺便提一下,这些函数本身也是我用 Claude Code 辅助编写的,甚至连这份 Issue 的摘要也是它帮我生成的。

无需保持此 Issue 开启状态。


Git Worktree 工作流

我使用的整体工作流如下:

  1. git worktree-llm feature-name 起手,开启新功能开发。
  2. 在 Claude 的辅助下推进功能开发。
  3. 准备合并时,执行 git worktree-merge
  4. 变更被提交,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

函数解析:

  1. 创建一个新的 worktree,并透传所有参数给 git worktree-create
  2. 后台运行初始化任务 (task setup-worktree)。这是每个项目自定义的任务,通常包括:
  • 安装依赖:如 uv sync
  • 启动守护进程:如 uv run dmypy run --timeout=3600
  • 加载 Claude 配置:比如 allowedTools,处理那些没有全局配置的项目。
  1. 立即启动 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

函数解析:

  1. 捕获当前分支名称。
  2. 将所有变更加入 Git 索引(git add)。
  3. 结束 Worktree(返回主仓库)。
  4. 将功能分支变基(Rebase)到默认分支之上。
  5. 切换回默认分支。
  6. 合并功能分支,无需手动输入提交信息。

辅助函数 (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