查看 Markdown

Git worktree

Git worktree 是工作区的一种。

工作区是任务发生的地方。当工作区由 Git worktree 支撑时,Paseo 会在单独的分支上创建一个单独的目录,让并行的 Agent 互不干扰。

本页介绍 Git 相关的细节:worktree 存放在哪里、分支如何选择,以及如何通过 paseo.json 配置 setup 钩子、脚本、终端和长时间运行的服务。

目录结构与工作流程

worktree 默认存放在 $PASEO_HOME/worktrees/ 下,按源检出路径的哈希分组。你可以用 config.json 中的 worktrees.root 修改基础目录。每个 worktree 在其工作区创建时获得一个 slug 和一个分支。

~/.paseo/worktrees/
└── 1vnnm9k3/               # hash of source checkout path
    ├── tidy-fox/           # worktree slug
    └── bold-owl/

使用自定义根目录时,Paseo 会在该目录下保持相同的哈希结构:

{
  "worktrees": {
    "root": "/mnt/fast/paseo-worktrees"
  }
}
  1. 创建一个使用 worktree 隔离的工作区,Paseo 会创建 worktree 并运行你的 setup 钩子
  2. 在该工作区中启动一个或多个 Agent
  3. 对照基础分支审查 diff
  4. 合并或归档该工作区;使用该 worktree 的最后一个工作区被归档后,Paseo 会运行 teardown 并删除 worktree

定时任务的运行默认会在结束时归档其工作区。在定时任务的应用表单中关闭 Archive on finish,即可保留运行工作区及其 worktree,直到你手动归档。

创建由 worktree 支撑的工作区

下面的示例以当前目录作为源检出。传入 --path ~/dev/my-app 可以从另一个检出创建工作区。

从基础分支分出新分支:

paseo workspace create \
  --isolation worktree \
  --mode branch-off \
  --new-branch feature/auth \
  --worktree-slug feature-auth \
  --base origin/main

请使用 origin/main 而不是 main。Paseo 会在后台获取远程引用,因此远程跟踪分支是最新的,而你本地的 main 停留在你上次 pull 时的状态。不带前缀的 main 会优先解析到这个本地分支,worktree 就会从过时的历史开始。加上远程前缀可以明确指定已获取的引用。

检出一个已有分支:

paseo workspace create \
  --isolation worktree \
  --mode checkout-branch \
  --branch feature/existing \
  --worktree-slug existing-copy

或者在独立的工作区中打开一个 Pull Request:

paseo workspace create \
  --isolation worktree \
  --mode checkout-pr \
  --pr-number 2186

当 Paseo 无法从源检出推断代码托管平台时,添加 --forge <name>。

paseo.json

在仓库根目录放一个 paseo.json。Paseo 从你所选基础分支的已提交版本中读取它,因此其他分支中未提交的改动不会生效。

{
  "worktree": {
    "setup": "npm ci",
    "teardown": "rm -rf .cache"
  },
  "scripts": {
    "test": { "command": "npm test" },
    "web": { "command": "npm run dev", "type": "service", "port": 3000 }
  }
}

Setup 与 teardown

setup 在 worktree 创建后运行一次。新的 worktree 没有安装依赖,也没有被忽略的文件(例如 .env),因此请用 setup 安装和复制你需要的内容。teardown 在归档期间、目录被删除之前运行。

{
  "worktree": {
    "setup": "npm ci\ncp \"$PASEO_SOURCE_CHECKOUT_PATH/.env\" .env\nnpm run db:migrate",
    "teardown": "npm run db:drop || true"
  }
}

两个字段都接受多行 shell 脚本或命令数组;无论哪种形式,命令都按顺序运行。

命令以 worktree 作为 cwd 运行。使用 $PASEO_SOURCE_CHECKOUT_PATH 访问原始检出中的文件(未跟踪的配置、本地缓存等)。

脚本与服务

scripts 是可以在 worktree 中按需运行的具名命令。把其中一个标记为 service,Paseo 就会把它作为长时间运行的进程进行监管,为它分配端口,并通过 daemon 的反向代理把 HTTP 流量路由给它。

可以在应用中运行它们,也可以在自动化中用 paseo script 和工作区脚本 MCP 工具管理它们。

普通脚本

{
  "scripts": {
    "test": { "command": "npm test" },
    "lint": { "command": "npm run lint" },
    "generate": { "command": "npm run codegen" }
  }
}

服务

{
  "scripts": {
    "web": {
      "type": "service",
      "command": "npm run dev -- --port $PASEO_PORT",
      "port": 3000
    },
    "api": {
      "type": "service",
      "command": "npm run api -- --port $PASEO_PORT"
    }
  }
}

省略 port 可以让 Paseo 自动分配端口。让你的进程绑定 $PASEO_PORT,而不是写死端口;每个 worktree 获得不同的端口,因此同一服务的多个副本可以共存。

动态端口分配

默认情况下,Paseo 向操作系统请求一个可用的临时端口。可以在 ~/.paseo/config.json 中全局配置端口范围,也可以在 paseo.json 中按项目配置:

// ~/.paseo/config.json
{
  "worktrees": {
    "servicePorts": { "range": "3000-4000" }
  }
}
// paseo.json
{
  "worktree": {
    "servicePorts": { "range": "3000-4000" }
  }
}

范围包含两端。项目的 servicePorts 块会替换全局块。服务上显式设置的 port 总是优先于这两种设置。

如果使用外部分配器,请改为配置 portScript:

{
  "worktree": {
    "servicePorts": { "portScript": "/usr/bin/portmake" }
  }
}

Paseo 在工作区目录中运行该可执行文件,并传入四个参数:服务名、工作区 ID、分支名和 worktree 路径。由于脚本是直接执行的、不经过 shell,portScript 必须指向一个真正的可执行文件(例如编译后的二进制文件,或带有正确 shebang 行如 #!/bin/bash 的脚本),而不是内联的 shell 命令或管道。如果需要 shell 求值或管道,请把它们包装在一个小的可执行脚本中。没有分支时会传入空字符串。相同的值也可以通过 PASEO_SCRIPTNAME、PASEO_WORKSPACE_ID、PASEO_BRANCH_NAME 和 PASEO_WORKTREE_PATH 获取。它必须向 stdout 输出一个有效的 TCP 端口。同一块中 portScript 优先于 range。Paseo 信任外部分配器,因此返回的端口可能已经被占用,例如被 Paseo 将要连接的某个服务占用。

反向代理

每个服务都可以通过 daemon 以一个确定的主机名访问:

http://<script>--<branch>--<project>.localhost:<daemon-port>

# on the default branch, the branch label is dropped:
http://<script>--<project>.localhost:<daemon-port>

在现代系统上,*.localhost 解析为 127.0.0.1,因此这些 URL 开箱即用。代理支持 WebSocket 升级。

服务之间的访问

从同一工作区启动的服务可以看到彼此的端口和代理 URL。以上面的 web 和 api 为例,每个进程都会得到:

PASEO_PORT=3000                         # this service's port
PASEO_URL=http://web--my-app.localhost:6767  # this service's proxy URL
PASEO_SERVICE_API_PORT=51732
PASEO_SERVICE_API_URL=http://api--my-app.localhost:6767
PASEO_SERVICE_WEB_PORT=3000
PASEO_SERVICE_WEB_URL=http://web--my-app.localhost:6767

脚本名会转为大写,非字母数字字符变为 _。让你的前端指向 $PASEO_SERVICE_API_URL,而不是写死端口。

终端

在 worktree 创建时自动打开终端。适合查看日志,或预先准备好一个 REPL。

{
  "worktree": {
    "terminals": [
      { "name": "logs", "command": "tail -f dev.log" },
      { "name": "shell", "command": "bash" }
    ]
  }
}

环境变量

Setup、teardown、脚本和服务都能看到:

  • $PASEO_SOURCE_CHECKOUT_PATH,原始仓库根目录
  • $PASEO_WORKTREE_PATH,worktree 目录
  • $PASEO_BRANCH_NAME,worktree 的分支
  • $PASEO_WORKTREE_PORT,旧版的每个 worktree 一个的端口(在服务中优先使用 $PASEO_PORT)

服务还会额外得到:

  • $PASEO_PORT,该服务分配到的端口
  • $PASEO_URL,该服务的代理 URL
  • $PASEO_SERVICE_<NAME>_PORT / _URL,其他服务的端口和 URL
  • $HOST,仅本地访问的 daemon 为 127.0.0.1,daemon 绑定所有网络接口时为 0.0.0.0

管理工作区

paseo workspace ls
paseo run --workspace <workspace-id> "implement auth"
paseo workspace rename <workspace-id> "Auth rework"
paseo workspace archive <workspace-id>

对于常见情况,paseo run --new-workspace worktree --worktree-mode branch-off --new-branch feature/auth --base origin/main "implement auth" 会同时创建工作区和它的第一个 Agent。

赞助商