worktree-dev · diff
git:20261009.f0bd5e3 to git:20261013.2cee0af
94 added, 64 removed. Audit B to A.
---
name: worktree-dev
description: 基于 Git Worktree 的隔离开发工作流。在 worktree 中开发新功能、修复缺陷或做验证时使用;分支合并回主分支(多 worktree 并发合并无需排队加锁)见文末
user-invocable: true
---
# Git Worktree 开发工作流
- ## 端口必须申请,禁止硬编码(worktree 内)
+ 基于 Git Worktree 提供完全隔离的独立工作区,支持多任务并发开发、独立服务调试与安全合入。
- 本 skill 自带 `acquire-port`。**worktree 内启动的任何监听端口的进程**——dev server、e2e 实例、带 `--port` 的 CLI、mock 服务——都必须先向它申请端口,禁止写死端口号(如 `3000`):
+ ---
- ```bash
- PORT=$({skill_dir}/scripts/acquire-port --wait) # Linux
- ```
+ ## 一、生命周期速查
- ```powershell
- $PORT = & "{skill_dir}\scripts\acquire-port.ps1" -Wait # Windows
```
+ [1. 创建 Worktree] ──> [2. 环境初始化 & 启动] ──> [3. 独立开发 & 提交] ──> [4. 合入主分支] ──> [5. 收尾清理]
+ git worktree add init/dev-worktree.sh git commit merge-to-main.sh git worktree remove
+ ```
- - 无状态、不用释放:服务起了端口自然占着,停了自然空出来。
- - 放行只看内存余量,没有并发槽位上限。
- - 机制、退出码、`--list` / `--exec`、环境变量见 `references/acquire-port.md`。
+ ---
- 本约定只约束 worktree 内的服务启动,不是全机器范围的端口门禁。
+ ## 二、规范约定(命名与路径)
- ## 生命周期脚本
+ - **分支命名**:`feat/<name>` 或 `fix/<name>`
+ - **工作区路径**:仓库根目录下的 `.worktrees/<name>`(`<name>` 为分支名末段,**不含斜杠**)
+ - **Git 忽略**:仓库根目录 `.gitignore` 必须包含 `.worktrees/`
- worktree 内有两个约定脚本:
+ ---
- | 脚本 | 职责 |
- |---|---|
- | `scripts/init-worktree.sh` | 依赖安装/同步、环境初始化 |
- | `scripts/dev-worktree.sh` | 用本 skill 的 `acquire-port` 取端口 → 跑服务 → 输出访问地址 |
+ ## 三、标准执行流程
- 请根据项目情况新建、提炼、更新,避免每次手写通用流程。
+ ### 1. 创建工作树
- ### dev-worktree.sh 典型结构
+ 在仓库根目录下切出独立分支与对应 worktree:
```bash
- #!/usr/bin/env bash
- set -euo pipefail
- PORT=$(acquire-port --wait) # 安装说明见 references/acquire-port.md
- echo "Starting on http://127.0.0.1:${PORT}"
- exec npm run dev -- --port "$PORT"
+ # 从主分支(master / main)创建隔离工作区
+ git worktree add -b feat/<name> .worktrees/<name> master
+ cd .worktrees/<name>
```
- `dev-worktree.sh` 既用于 agent 的 e2e 测试(跑起来后验证),也用于暴露给用户 validate 和体验。
+ ### 2. 环境初始化与服务启动
- ## 命名与路径
+ worktree 内建议维护两个约定脚本(根据项目实际情况在 `scripts/` 中创建或提炼):
- - 分支名:`feat/<name>` 或 `fix/<name>`
- - worktree 路径:仓库根的 `.worktrees/<name>`(`name` 用分支名末段,**不含斜杠**)
- - 仓库根 `.gitignore` 必须包含 `.worktrees/`
+ | 脚本 | 职责与约定 |
+ |---|---|
+ | `scripts/init-worktree.sh` | 依赖安装、环境与配置初始化 |
+ | `scripts/dev-worktree.sh` | 动态申请端口 → 启动服务 → 输出访问地址(供 E2E 验证及用户体验) |
- ## 开发与验证规范
+ #### 端口分配原则(严禁硬编码)
- - 所有文件编辑、服务运行、测试严格限制在当前 worktree。
- - 起服务前先申请端口。
- - 若项目或宿主自带隔离的 e2e 实例管理方式(一条命令起停、自带端口与实例隔离),走它,不用自己管端口和实例。
- - 严禁向主工作区或线上实例写入未声明的产物。
- - worktree 内环境已隔离,不会有他人改动混入,可直接全量提交。
- - 用户显式同意后,方可合入主分支。
+ worktree 内启动任何监听端口的服务(开发服务器、E2E 实例、Mock 服务等),**必须通过 `acquire-port` 动态申请端口**,禁止硬编码端口号(如 `3000`):
- ## 合并回主分支(无锁 CAS)
+ ```bash
+ # 方式 1:脚本变量捕获(最常用)
+ PORT=$(acquire-port --wait)
+ npm run dev -- --port "$PORT"
- 多个 worktree 合回主分支**不需要排队加锁**:`git merge --ff-only` 本身就是 compare-and-swap —— 只有本分支真包含主分支当前提交时才推得进去,失败即“主分支被别的 worktree 推进了”,重新变基再来一轮。
+ # 方式 2:包装执行(自动注入 $PORT / $PORTS 环境变量)
+ acquire-port --wait --exec npm run dev -- --port "$PORT"
+ ```
- 用 skill 自带的脚本,不要手抄 rebase/merge 循环(`{skill_dir}` 指本 skill 的安装目录;脚本是 bash,Windows 上在 Git Bash 里跑):
+ > **说明**:
+ > - `acquire-port` 为全局无状态分配器,仅根据物理可用内存余量($\ge 512\text{MB}$)放行,服务启动自然占用、退出自然释放,无需手动释放。
+ > - 完整机制、参数及 Windows 用法详见 [acquire-port 参考手册](references/acquire-port.md)。
+ > - 若项目自带统一的隔离测试实例管理机制(如 `dsh-e2e`),优先遵循其指令。
+ ### 3. 独立开发与验证
+
+ - **严格边界**:所有文件编辑、服务运行与测试严格限制在当前 worktree 目录下,严禁向主工作区或线上目录写入未声明产物。
+ - **独立提交**:worktree 内环境完全隔离,不存在他人修改混入风险,可直接全量提交:
+ ```bash
+ git add .
+ git commit -m "feat(<scope>): <description>"
+ ```
+
+ ### 4. 合入主分支(无锁 CAS)
+
+ **必须在获得用户显式同意后,方可合入主分支。**
+
+ 多 worktree 并行开发时,合入主分支使用本 skill 自带的 `merge-to-main.sh` 脚本:
+ - **分支历史推进**:基于 `git merge --ff-only`(Compare-And-Swap),仅在本分支真包含目标分支当前提交时才落地;若主分支已被其他任务推进,脚本会自动重新变基并重试,**不需要人为排队加锁**。
+ - **主工作区保护**:合入需目标分支本地工作区干净(无未提交的已跟踪改动)。若被占用,使用 `--wait` 自动排队等待目标工作区变干净。
+
```bash
- {skill_dir}/scripts/merge-to-main.sh # 变基到 master/main 后合入
- {skill_dir}/scripts/merge-to-main.sh --into main # 指定目标分支
- {skill_dir}/scripts/merge-to-main.sh --strategy merge # 保留分支历史(产生 merge commit)
- {skill_dir}/scripts/merge-to-main.sh --retries 20 # 重试上限(默认 10)
+ # 常用命令({skill_dir} 指本 skill 安装目录;Windows 请在 Git Bash 中运行)
+ {skill_dir}/scripts/merge-to-main.sh # 自动探测 master/main,变基后 fast-forward 合入
+ {skill_dir}/scripts/merge-to-main.sh --into main # 指定目标分支
+ {skill_dir}/scripts/merge-to-main.sh --strategy merge # 保留分支历史(产生 merge commit)
+ {skill_dir}/scripts/merge-to-main.sh --wait # 目标工作区有改动时排队等待(默认最多 3600 秒)
+ {skill_dir}/scripts/merge-to-main.sh --wait=600 # 指定等待上限(如 600 秒,--wait 600 亦可)
```
- 脚本在**目标分支的 home worktree 内**执行 ff-only merge(主分支未被 checkout 时退回 `git update-ref <ref> <new> <old>` 原子 CAS)。为什么不用别的写法:`git push . HEAD:master` 会被 `receive.denyCurrentBranch` 拒绝,`update-ref` 到已 checkout 的分支会让那个工作区变脏。
+ #### 退出码与对策
| 退出码 | 含义 | 对策 |
- |---|---|---|
- | 0 | 成功 | — |
- | 1 | 用法/环境错误(不在 worktree、工作区脏、已在目标分支) | 按提示修正 |
- | 2 | 变基/合并冲突 | 脚本打印冲突文件后 `--abort` 还原分支;按下面「撞上冲突」流程解决后重跑 |
- | 3 | 重试耗尽 | 主分支被高频推进,稍后重跑或加大 `--retries` |
- | 4 | 目标 worktree 有未提交改动 | 先去主工作区处理(脚本不代劳 stash) |
+ |:---:|---|---|
+ | `0` | 合并成功 | 进入收尾阶段 |
+ | `1` | 用法或环境错误 | 检查是否在 worktree 内、当前 worktree 是否有未提交的已跟踪改动 |
+ | `2` | 变基 / 合并冲突 | 脚本会自动 `--abort` 还原现场并打印冲突文件。按下方指引手动解决 |
+ | `3` | CAS 重试耗尽 | 主分支推进过于频繁,可调大 `--retries` 后重试 |
+ | `4` | 目标工作区有未提交改动 | 去主工作区处理改动,或追加 `--wait[=秒]` 等待其清空 |
- ### 撞上冲突
+ #### 冲突处理流程
- 脚本只做机械动作,不替 agent 解冲突:冲突时它会打印冲突文件、`--abort` 把分支还原到调用前的状态(HEAD、工作区、rebase 中间态全部清掉),然后 exit 2。
+ 脚本仅执行机械合并,不代劳解决冲突。退出码为 `2` 时:
```bash
- cd <worktree>
- git rebase master # 重放冲突现场,git status 显示 UU 标记
- # 编辑冲突文件 → git add <file> → git rebase --continue
- <skill_dir>/scripts/merge-to-main.sh # 解决完重跑,通常一次过
+ # 1. 在当前 worktree 中重放变基,复现冲突现场
+ git rebase master
+
+ # 2. 编辑冲突标记文件,标记已解决并继续变基
+ git add <conflicted-file>
+ git rebase --continue
+
+ # 3. 冲突解决完成后,重新运行合入脚本
+ {skill_dir}/scripts/merge-to-main.sh
```
- 解决冲突**不需要串行**——每个 worktree 各解各的,只有最后 CAS 落地那一步是串行的。
+ > **注意**:各 worktree 仅在本地解决自己的冲突,彼此互不阻塞;仅在最终 CAS 落地那一步按需排队。
- - 调用方 worktree 与目标 worktree 都必须没有未提交的**已跟踪**改动。
- - 并发合并是安全的:后到者被 CAS 挡下后自动重试(日志会打 `master moved, retry N/10`)。
- - 脚本只动本地分支,不 push、不删 worktree;推送与善后由用户决定。
+ ### 5. 收尾清理
- ## 收尾
+ 合并成功后,必须及时清理资源:
+ 1. 停止在当前 worktree 中启动的所有后台服务和测试进程;
+ 2. 切出工作树目录(例如回到仓库根目录),移除工作树及临时分支:
- 当且仅当合并成功后,停止过程中启动的所有服务器和测试,并删除你创建的工作树。
+ ```bash
+ # 移除工作树目录
+ git worktree remove .worktrees/<name>
- ## 例外
+ # 删除已合入的本地分支
+ git branch -d feat/<name>
+ ```
- 如果你的工作树是系统(如 Codeg,Kandev 等)为你创建的,则优先遵循系统给你的指令。
+ ---
+
+ ## 四、例外说明
+
+ 若当前 worktree 是由系统宿主或外层调度器(如 Codeg、Kandev 等)自动创建并管理的,优先遵循系统注入的生命周期指令,不强求手动执行移除。