关于 gitkit
gitkit 是一项基础技能,为其他技能或直接使用者统一 Git worktree、分支基线和同步规则。其核心操作使用原生 Git,并按仓库声明的命令执行 worktree 设置。
主要功能
- 01创建、接管、查找和移除 worktree
- 02解析基线分支
- 03通过变基或合并同步分支
- 04执行仓库声明的 worktree 设置
- 05清理分支并恢复丢失的工作
- 06构建和重排分支链
适用场景
- 01为 Issue 分支创建 worktree
- 02将功能分支同步到最新基线
- 03准备或移除 worktree
- 04恢复重置或变基后丢失的工作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mimukit/ skills --skill gitkit安装后在 Claude Code 中输入/gitkit确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
clean.mdprepare.mdrescue.mdstacks.md详细介绍
gitkit
把工具链中其他所有技能反复推导的 Git 信息集中在一处:工作树放在哪里、叫什么名字,如何创建、接管和移除,哪个分支是基准分支,以及何时变基、何时合并。
这里的核心并不复杂。所有操作都使用原生的 git worktree,不依赖任何厂商的 CLI 或 GUI,也不会在 Git 自身之外记录状态。这正是重点:装有桌面开发工具的笔记本电脑和无头 Linux 机器运行完全相同的命令,因此不必维护第二套代码路径,也无需考虑“降级模式”。
这项技能有两个分支不属于该核心,特此说明。堆叠分支会调用 GitHub 的 gh stack 扩展,因此只有这个分支需要 gh 和 GitHub 远程仓库。若不满足条件,它会退回到普通 Git 操作,而不是失败。prepare 会运行仓库为新工作树声明的命令,但不会自行选择任何命令:仓库没有声明时,只会执行其锁文件所对应的安装操作,不会做更多事情。本文档中的其他内容都不依赖这两个分支。
gitkit 是一项基础技能。它通常由另一项技能调用,而不是由人直接点名运行。
何时触发
- worktree。“为这个分支创建一个工作树”、“为 #42 建一个工作树”、“
issue-42-…的工作树在哪里”、“拆除这个工作树”、“列出我的工作树”。 - 基准引用。“这里的基准分支是什么”、“比较一下我和基准分支的差异”。
- 同步。“这里应该变基还是合并”、“将这个分支与 main 同步”、“这个分支落后了,更新到最新状态”、“让这个 PR 基于最新的基准分支,方便审查”。
- prepare。“设置这个工作树”、“安装并初始化这个工作树”、“为这个工作树创建开发数据库”、“重新准备一次”。
- clean。“清理已合并的工作树”、“哪些分支可以删除”、“这个仓库里全是废弃分支”。
- rescue。“我丢了一个提交”、“变基把我的工作弄没了”、“我重置错了分支”、“这个储藏里有什么”。
- stack。“把这个分支堆叠在 #43 上”、“从 #43 的分支开始创建 #44”、“重新堆叠这条链”、“显示堆叠分支”。
- **由另一项技能调用。**任何需要工作树、基准引用、分支名称或同步决策的技能,都会委托给这里并使用返回结果。
**不属于这项技能的内容:**提交、创建 PR、审查差异、评判代码。gitkit 只回答在哪里、如何操作,从不判断应该做什么更改。
模式
| 模式 | 用途 | 所在位置 |
|---|---|---|
worktree | 创建、接管、查找、列出和移除工作树 | 下文 |
sync | 将功能分支更新到与其远程分支一致,使其基于最新的基准分支,并使用租约强制推送 | 下文 |
prepare | 在工作树中运行仓库声明的设置命令,并在移除前运行清理命令 | prepare.md 和下文 |
clean | 清理已合并的工作树和分支,每项操作都单独确认 | clean.md |
rescue | 查找疑似丢失的工作成果,并将其恢复到新分支 | rescue.md |
stack | 通过 gh stack 构建并重新堆叠分支链 | stacks.md 和堆叠分支 |
worktree 和 sync 直接写在此处,因为几乎每次运行和每项调用技能都会用到它们。其他四种模式只会在触发时加载。
如果无法从请求中明确判断模式,就根据动词选择,并说明选择了哪种模式。 合并后说“整理一下”指的是 clean;在落后于基准分支的分支上说“整理一下”则指的是 sync。在第一句话中点明模式,能让用户在任何操作运行前纠正错误选择。
约定
路径
$WORKTREE_ROOT/<repo-basename>/<local-branch-name>
$WORKTREE_ROOT 默认值为 ~/worktrees;如果已设置该环境变量,则应遵循其值。<repo-basename> 是主检出目录的名称。
分支名称中的斜杠会转换为连字符。 分支 mimukit/6-marketing-site 对应的目录是 mimukit-6-marketing-site,而不是嵌套的 mimukit/ 父目录。这样,每个仓库都只有一层目录,因此 ls "$WORKTREE_ROOT/<repo>" 就能完整列出所有内容,无需遍历目录树。代价是目录名称无法再反向还原为分支名称,但这毫无影响,因为始终通过 git 查找,而不会通过读取路径来查找。极少数情况下,如果分支 a/b 和 a-b 都被转换为 a-b,git worktree add 会因路径已存在而失败:报告此情况,让人来选择名称,不要悄悄追加后缀。
工作树位于仓库之外,绝不放在仓库内部的 .worktrees/ 目录中。仓库内的工作树会被纳入 Docker 构建上下文、绑定挂载、文件监视器和测试 glob 中,而这些问题都会在远离根因的地方显现。
不变量:一个分支,一个工作树
一个分支最多对应一个工作树,目录以分支命名。 工作树与分支关联,而非与工作流阶段关联;因此,实现某项功能和之后审查其拉取请求会使用同一个工作树,因为它们属于同一个分支。
这不是风格偏好。Git 会强制执行此规则:
$ git worktree add ../wt-b feature-x
fatal: 'feature-x' is already used by worktree at '.../wt-a'
任何按阶段命名工作树的方案(review-…、qa-…)一旦两个阶段操作同一个分支就会彻底失败,而这很常见,并非边缘情况。
后果:
- 始终按分支查找,通过 git 查找,绝不读取或猜测目录路径。这使得上述扁平化方式毫无代价,也让位于旧根目录中的工作树可以继续正常使用,无需改动。
- 工作树的“类型”会自动随之而来,因为分支名称已经体现了它。
issue-42-auth-capability-module之所以这样命名,是因为工作开始时,分支就是这个名称。
分支命名
issue-<n>-<slug>用于从跟踪器问题开始的工作。slug 取自问题标题:如果标题符合常见的type(scope): summary格式,则去掉前缀;将其余部分转换为 kebab-case,在单词边界处截断至约 50 个字符,并去掉末尾的连字符。slug 为空时,退化为单独的issue-<n>。数字可保证唯一性,因此无需任何平局处理。hotfix-<slug>用于直接修补基准分支的紧急修复。slug 取自问题症状,转换为 kebab-case,限制在约 40 个字符。此名称不带数字,因为 hotfix 是从问题症状出发,而非从跟踪器问题出发;如果确实能追溯到某个问题,分支仍使用这种格式,而拉取请求会带有Closes #<n>链接。如果与现有分支冲突,则添加一个简短的区分词,而不是数字。分隔符是连字符:写作hotfix-<slug>,绝不能写成hotfix/<slug>。 斜杠会让 git 将hotfix视为引用命名空间中的目录,因此hotfix-restore-session和hotfix/restore-session不能同时存在;而所有根据分支名称识别类型的技能(commitkit、prkit)都只匹配连字符格式。pr-<n>-<slug>仅用于复刻仓库的拉取请求,此时本地还没有分支,必须创建一个分支来保存获取到的 head。slug 取自 head 分支名称,转换为 kebab-case,限制在约 40 个字符。- 其他情况使用人提供的分支名称或仓库约定的名称。gitkit 不会重命名分支。
Agent 自行创建分支时,必须使用上述三种格式之一。 它绝不会另创其他前缀(feature/…、fix-…、claude/…)。除此之外,只能使用人输入的名称或仓库文档中规定的名称。
对于同一仓库中的拉取请求,本地已经有分支名称:直接使用它。为已存在的分支另造一个 pr-* 名称,正是导致上述“两个工作树对应一个分支”问题的原因。
worktree
这四项操作都使用原生 git,且全部都具有幂等性,因此运行两次很正常,绝不能报错或破坏工作。
查找分支的 worktree
其他所有操作都基于这一项查找原语:
git -C "$REPO" worktree list --porcelain
每条记录都包含一行 worktree <path>;当某个分支已检出时,还会包含一行 branch refs/heads/<name>。匹配分支即可获取路径。如果 porcelain 的输出格式发生变化,请查看 git worktree list --help,不要猜测;但应将 worktree/branch 配对视为稳定格式,这正是 porcelain 输出的用途。
创建或接管
始终先查找。**如果该分支已经有 worktree,就接管它:报告路径并停止。**绝不创建第二个,也绝不报错。
如果没有:
WT="${WORKTREE_ROOT:-$HOME/worktrees}/$(basename "$REPO")/$(printf '%s' "$BRANCH" | tr / -)" # slashes flatten to dashes
git -C "$REPO" fetch origin --prune
git -C "$REPO" worktree add -b "$BRANCH" "$WT" "$BASE"
- 始终先获取更新。基于过时的基准分支进行分支创建不会立即报错,只会在之后产生冲突。
$BASE来自基准引用,绝不能硬编码为origin/main,也不能使用同级功能分支。唯一例外是堆叠分支,其基准分支就是下方的分支,由调用者明确指定。- 如果本地分支已存在(例如你已获取的同仓库 PR 分支,或之前创建的分支),则去掉
-b;git worktree add <path> <branch>会检出该分支。 - 对于 fork 拉取请求,先用
git fetch origin "pull/<n>/head:pr-<n>-<slug>"将头部分支获取到本地分支,再基于该分支添加 worktree,不要使用-b。
然后记录 gitkit 创建的内容,因为后续会话没有其他办法区分自己的 worktree 和别人的 worktree:
touch "$(git -C "$WT" rev-parse --absolute-git-dir)/gitkit-created" # gitkit made this worktree
git -C "$REPO" config "branch.$BRANCH.gitkitCreated" true # only after -b: gitkit made the branch too
- 两条记录都存储在 git 自身的存储中,因此不需要状态文件。worktree 标记位于
.git/worktrees/下的 worktree 管理目录中;git worktree remove会在移除 worktree 时将其删除。分支键位于分支的配置节中;git branch -d会在删除分支时将其删除。 - **仅当本次运行使用了
-b时才写入分支键。**已存在的分支属于创建它的人;被接管的 worktree 不会获得标记。 - **没有记录就表示不是 gitkit 创建的。**在此规则生效前创建的 worktree 或分支,以及手动创建的 worktree 或分支,都会被视为已接管;移除时会保留它们。
当调用者将 worktree 交给某个人或 Agent 使用时,无论 worktree 是新建还是已接管,都要接着运行 prepare。仅运行一条命令的临时 worktree 则跳过此步骤。
报告路径和分支。当用户直接向 gitkit 请求 worktree 时,回复末尾要附上工作区块。创建 worktree 并不意味着要在其中执行任何操作。到 $WT 路径下的 $BRANCH 分支已有 worktree 且记录已写入时,操作即告完成。
工作区块
将 worktree 交给人类使用的技能,回复末尾都要附上此区块。标签固定不变;填写对应的值。
Workspace ready
- repo: <absolute path of the main checkout>
- worktree: <absolute worktree path>
- branch: <branch>
- base: <base ref the branch was cut from or tracks>
- state: created | adopted
- for: issue #<n> | PR #<n> | none
- **此区块是与宿主的约定,而不是与工具的约定。**它不指明任何运行环境或应用。能够打开绑定到 worktree 的新会话的宿主会读取此区块;其他宿主则显示这六行纯文本。
- **它不会启动任何操作。**其中不包含提示词,也不包含要在 worktree 中运行的命令。人类自行决定如何开始工作。
- **仅面向人类,只输出一次,并放在最后。**当另一个技能调用 gitkit 时,将路径和分支返回给调用方,由调用方在自己的交接内容中输出此区块。无人值守运行不输出区块,因为它会在自己的会话中通过路径操作 worktree。
移除
test -e "$(git -C "$WT" rev-parse --absolute-git-dir)/gitkit-created" # gitkit made this worktree?
git -C "$REPO" config --get "branch.$BRANCH.gitkitCreated" # gitkit made this branch?
git -C "$REPO" worktree remove "$WT"
git -C "$REPO" branch -d "$BRANCH"
这是整个集合中唯一的移除规则。 每个会移除工作树或删除分支的技能,无论是这里的清理操作、工作区工具的清理,还是问题关闭流程,都遵循并引用此规则,而不是重述自己的规则。以下四条规则各自防止一种真实的工作丢失方式:
- 工作树有未提交内容时,停止拆除。 明确展示将会丢失的内容(未提交的更改、未跟踪的文件、未推送的提交),并让人类决定。绝不擅自使用
--force。 - 只移除标记表明由 gitkit 创建的工作树。 没有标记的工作树是被接管的:那是其他人的工作上下文,你不知道其中打开了什么。
- 只删除设置了
gitkitCreated键的分支,并使用-d删除。 这样 Git 会拒绝删除未合并的分支,因此错误判断会明确失败,而不会删除工作成果。 - 每次移除都要单独确认。 每个工作树和每个分支都是独立的决定,因此一份列表中的每一行都需要单独确认,不能一次确认整组。
- 已准备的工作树要先执行拆除流程。 当
gitkit-prepared标记存在且工作树声明了teardown:时,在检查未提交内容并获得确认后、执行git worktree remove前运行该流程。拆除流程失败时,保留工作树并报告失败。拆除流程中载有具体步骤。
压缩合并例外是 gitkit 唯一会运行 -D 的情况。 压缩合并的分支无法通过 -d 删除,因为压缩提交没有指向该分支的祖先关系。只有当 gh 报告该分支对应的拉取请求已合并,且该拉取请求的 headRefOid 与本地分支尖端一致时,才使用 -D:
gh pr list --head "$BRANCH" --state merged --json number,headRefOid --jq '.[0]'
git -C "$REPO" rev-parse "$BRANCH" # must equal headRefOid, or -D is off the table
SHA 一致,证明该分支上的每个提交都包含在已合并的拉取请求中。合并后移动过的尖端包含合并时未出现的提交,因此保留该分支,并在报告中注明。确认该行时,要写明拉取请求编号。
已合并 PR 例外会针对某个 PR 自己的工作区,解除标记规则和确认规则。 当人类要求调用方合并或关闭某个拉取请求,而调用方随后看到它已合并时,该指令即为移除该 PR 头部分支对应工作树和本地头部分支的确认。无论它们是否由 gitkit 创建,都会被移除,无需再次询问。调用方传入 PR 编号,此例外仅适用于该分支及其工作树。以下保护措施仍然有效,每项都会保留对应对象并在报告中注明:
- 人类要求保留。 “合并它,但保留工作树”表示保留工作树;“保留分支”表示保留分支。
- 工作树有未提交内容。 上述未提交内容规则仍会阻止移除。
- 拆除流程失败。 上述拆除规则仍会保留工作树。在此例外下,拆除流程无需询问即可运行。
- 分支尖端已移动。 使用
-d删除;只有尖端与已合并 PR 的headRefOid一致时,才按压缩合并例外的规定使用-D。 - 有其他分支依赖此分支。 以此分支为基础的开放 PR 会使分支保留,但工作树仍会移除。
如果要移除的工作树就是调用方当前所在的工作目录,请在主检出目录中使用 git -C "$REPO" 运行所有命令。移除后,说明会话的工作目录已不存在,并指出可在主检出目录中继续操作。
已经不存在?报告“已不存在”并视为成功。与接管操作一样,拆除流程也是幂等的。每个已确认的工作树和分支都被移除,或其无法移除的原因已报告,流程即告结束。
列出
git -C "$REPO" worktree list
纯粹的清单。用户要求整理而非查看时,应使用 clean 模式;该模式会先对每一行进行分类,再提供可执行的操作。
如果移动或还原后路径看起来不对,git worktree repair <path> 会重写反向指针。Git 会在 .git/worktrees/<name>/gitdir 和每个工作树的 .git 文件中存储绝对路径,因此手动移动工作树后总是需要修复。
基准分支
绝不要假设基准分支是 main。实际使用中,仓库的默认分支可能是 develop、trunk 或 master,选错会悄无声息地生成空差异或巨大差异。按以下顺序逐级确认:
gh repo view --json defaultBranchRef --jq .defaultBranchRef.name:如果已安装并通过身份验证的 GitHub CLI 可用,这是权威来源。git symbolic-ref --short refs/remotes/origin/HEAD:如果已设置,这是本地记录。- 运行
git remote set-head origin --auto修复未设置的origin/HEAD,然后重试上一步。大多数实现都会跳过这一步,而正是这一步能将失败转化为答案。 - 选择实际存在的
origin/main或origin/master。 - 询问用户。到这一步为止,不要再猜。
后续所有操作都使用这个答案:git diff <base>...HEAD、领先/落后提交数,以及工作树分支的切出来源。
堆栈层是唯一有意设置的例外,但仍要执行上述逐级确认。 堆栈层的基准分支是它下方的分支,而不是仓库默认分支,因此由调用方指定该分支,不对它执行上述确认。仍需通过上述步骤确定堆栈最底层所基于的主干。其他场景仍严格禁止以同级分支为基准,因为这条规则要防范的是意外情况:从碰巧处于检出状态的分支切出工作树。层的基准分支之所以合法,是因为有人明确说明了分支及其原因。请参阅堆叠分支。
变基还是合并
将功能分支与其基准分支同步时,这里只有一个默认做法,而且与是否已创建拉取请求无关:
将功能分支变基到基准分支上来完成同步,无论该分支是否已发布。合入基准分支是例外,必须说明理由并征得用户同意。
默认采用变基,是因为这样的提交历史更清晰:功能分支呈现为一连串线性的自身提交,基准分支上的工作则位于其下,而不是交织其中。每次同步都把基准分支合进来的仓库,会为每次同步累积一个合并提交;在功能分支合入之前,它自身的开发历程就会变得难以理解。
变基确实有实际代价,而且这是操作层面的,不是审美层面的:它会改写提交 SHA。一旦拉取请求已创建,其评审讨论串就会锚定到这些提交,因此强制推送会将所有讨论标记为过时,包括人工和机器人留下的发现。这项代价应该如实说明,而不是作为阻碍,因此同步完成推送后应报告受影响的数量。这也是用户通常会提出合并例外的原因。
实际操作如下:
git fetch origin
git rev-list --left-right --count "origin/$BASE"...HEAD # "<behind>\t<ahead>"; left > 0 means behind
git rebase "origin/$BASE"
- 除非有人针对这个分支另有说明,否则应选择变基。 无论没有拉取请求、已有未关闭的拉取请求,还是共享分支,建议都一样。
- 同步应直接执行到底,无论分支是否已发布。 用户要求同步,就等于同意对该分支进行变基并执行
--force-with-lease推送,因为这是针对同一个分支的一项决定,而用户的请求已经包含了这项同意。只有遇到问题时才暂停并询问:工作区不干净、出现需要裁定的冲突、门禁检查失败,或租约被拒绝。 - 推送后报告代价。 对于有未关闭 PR 的分支,请在变基前查询 GraphQL
reviewThreads连接中的未解决讨论串,并在交接说明中写明数量。“3 个评审讨论串现已过时”能提醒用户重新请求评审;“某些评论可能已过时”则没有提供任何有用信息。 - 只有用户提出请求时才采用合并例外。 未解决的讨论串不是暂停并提出合并方案的理由。用户要求合并时,请执行合并例外。
- 使用
--force-with-lease,绝不使用裸--force。 租约可以防止你覆盖他人在你变基期间推送的提交。 - 发生冲突时,按冲突处理。
冲突
Agent 会解决每个冲突并写入每项解决方案。人类绝不会编辑发生冲突的文件。每个冲突唯一需要决定的问题是:是否需要先由人类裁定;而裁定只是从 Agent 已经写出的解决方案中选一个。
无需询问的情况:冲突属于机械性冲突,也就是说,双方意图都能完整保留:
- 双方各自添加独立条目(导入、导出、路由、列表项、变更日志行)。两者都保留,并按文件中的顺序排列。
- 双方做出相同的更改。 保留一份即可。
- 一方只更改格式。 保留另一方的内容,然后运行仓库的格式化工具处理该文件。
- 锁文件或生成文件。 使用基线版本,然后用仓库自己的命令(
pnpm install --lockfile-only、代码生成脚本)重新生成。 - 冲突仅发生在上下文中。 基线修改或移动了周围的行,而分支的更改可以原样应用到新代码上。
先询问的情况:解决方案属于决策,而非合并:
- 双方以无法同时成立的方式更改了相同的行为。
- 修改/删除冲突、重命名/重命名冲突,或二进制文件冲突。
- 解决方案会丢弃任一方添加或保留的代码。
- 无法用一句话说明双方各自的意图。此检查用于发现你不理解的解决方案,因此每个冲突都要检查,包括机械性冲突。
询问时应说明文件、用一句话分别说明双方的意图、你推荐的解决方案和另一种方案,并分别展示它们会生成的代码。一次变基暂停时产生的所有询问,都应合并为一轮提问。由你亲自写入选定的解决方案。
每项解决方案都必须遵守以下要求:
- 在首次变基前保存起点:
git update-ref "refs/gitkit/pre-sync/$BRANCH" HEAD。每次同步都会覆盖它。ORIG_HEAD无法在第二次变基后保留;此引用可通过git reset --hard "refs/gitkit/pre-sync/$BRANCH"撤销整个同步。 - 根据双方内容构建每项解决方案。 整体采用某一方的策略(
-X ours、-X theirs、对源文件运行git checkout --ours)会在不留记录的情况下丢弃另一方,因此只能用于解决锁文件和生成文件冲突。 - 每次运行
git rebase --continue前都运行git diff --check。 它会标出遗留的冲突标记。 - 使用 rerere 记录解决方案。 运行
git -c rerere.enabled=true rebase …,这样下次同步或重新整理堆栈时,就会重放已解决的冲突。 - 由门禁检查结果。 解决冲突属于代码更改,因此之后会运行仓库的测试与构建门禁。已解决文件中的门禁失败由你负责修复:重新处理解决方案并再次运行门禁。只有在修复需要做出上述某项决策时才询问。
- 无人值守的调用方无法回答。 无人可问时,运行
git rebase --abort,并将每项尚未解决的询问及其推荐方案返回给调用方以便升级处理。收到裁定后恢复运行时,重新执行同步并写入相应解决方案。
sync
上述规则的可执行形式。一次同步包含 两个阶段,顺序如下:先让本地分支与其在 origin 上的对应分支保持一致,然后再更新到基分支。跳过第一阶段会导致之后的 --force-with-lease 被拒绝,或悄悄丢弃从另一台机器推送的提交。在该分支的工作树中运行。每一步都说明了结束条件。
- 确认起点。 使用基分支引用查找顺序解析基分支。运行
git -C "$WT" fetch origin --prune。确认工作树位于功能分支上,并且git status --porcelain为空。工作树有未提交更改时应停止同步:报告相关文件,让人工执行 stash 或提交。使用git update-ref "refs/gitkit/pre-sync/$BRANCH" HEAD保存起始位置。结束条件:已确认基分支名称、分支名称,且工作树干净。 - 与
origin/$BRANCH同步。 当远程分支存在时,运行git rev-list --left-right --count "origin/$BRANCH"...HEAD。落后计数为0表示无需获取提交。否则运行git pull --rebase origin "$BRANCH",将本地提交重放到远程提交之上,并按照冲突处理所有冲突。没有对应远程分支时跳过此步骤。结束条件:本地分支包含origin/$BRANCH上的所有提交。 - 计算与基分支的差距。 运行
git rev-list --left-right --count "origin/$BASE"...HEAD。落后计数为0表示分支已基于最新的基分支:如果上一步获取了提交,则运行下方的刷新已准备好的工作树步骤,推送获取的提交,报告结果,然后停止。结束条件:得到落后计数和领先计数。 - 统计评审讨论串。 当拉取请求处于打开状态时,通过 GraphQL 的
reviewThreads连接统计未解决的讨论串。此步骤不发起任何询问;统计结果会写入交接报告。结束条件:得到讨论串数量,或确认没有打开的拉取请求。 - 变基到基分支。 运行
git -c rerere.enabled=true rebase "origin/$BASE"。结束条件:变基成功,或因冲突而停止。 - 解决所有冲突。 每次停止时,使用
git diff --name-only --diff-filter=U列出文件。按照冲突对每个冲突分类,编写机械性解决方案,并针对其余冲突进行一轮提问。编写按规则确定的解决方案,运行git diff --check,暂存文件,然后运行git rebase --continue。对所有剩余的冲突重复此操作。结束条件:没有剩余冲突标记且变基完成;如果是无人值守的调用方,则以中止变基并返回未解决问题结束。 - 刷新已准备好的工作树。 当
gitkit-prepared标记存在且此次同步获取了提交时,按照 prepare.md 执行prepare的更改检查及其选定的操作块。此步骤在门禁之前运行,因为如果门禁在过时的安装或过时的架构上运行,就会因错误原因而失败。操作块失败时,应在推送前停止同步。结束条件:操作块返回退出代码、没有受监控的更改,或标记不存在。 - 验证分支仍可正常工作。 运行仓库自己的测试和构建门禁。失败时报告输出,并在推送前停止。结束条件:通过或停止。
- 使用租约推送。 运行
git push --force-with-lease origin "$BRANCH"。租约被拒绝表示有人在你变基期间推送了提交:执行 fetch,展示新提交,并在重试前征求同意。结束条件:远程分支与本地分支一致。
交接
报告从 origin/$BRANCH 获取的提交、基分支、落后和领先计数、刷新的内容、门禁结果、推送的分支,以及此次推送导致过时的评审讨论串。逐项列出已解决的文件、所属类别和一句话理由,并标明由人工裁定的文件,同时将 refs/gitkit/pre-sync/$BRANCH 作为撤销点。说明分支已是最新且没有任何更改的情况。接下来,基于新的基分支审查拉取请求差异;如果有讨论串过时,则重新请求评审。
合并例外
当用户选择合并,或明确要求合并时,它仍然是一次同步,并且会明确说明这一点:
git merge "origin/$BASE" -m "chore(repo): sync with origin $BASE"
- 绝不使用 git 的默认主题。 如果没人选择消息,
Merge branch 'main' into issue-42-tailwind-4-foundation就是 git 写入的内容,并且它会永远以这种形式出现在git log中。固定主题就是这条消息。 - 插入基准分支,不要硬编码
main。 基准引用 的优先级列表之所以存在,是因为develop和trunk仓库确实存在;在这类仓库中,主题写着main就是错误的。 - 不要使用
--no-ff。 如果同步可以快进,说明分支没有自己的内容需要保留,git 根本不会写入提交,因此也就没有主题需要修正;强行创建合并提交只会凭空制造一个空提交。 - 在提出请求时同时说明原因。 “由于这个 PR 有 3 个未解决的评审讨论,因此选择合并而不是变基”就是原因。“为了安全而合并”不算;如果你说不出变基会破坏什么,就进行变基。
绝不直接使用 git pull
当分支落后于远程分支时,git pull 默认会执行合并,并且会在未经任何人询问的情况下写入 Merge branch 'main' of github.com:owner/repo。请使用 git pull --rebase;如果希望同步失败时明确报错,而不是自动解决,则使用 git pull --ff-only。调用 git pull 来刷新基准分支时也一样:请进行配置或传入标志,绝不要让它按默认行为运行。
prepare
根据仓库在 Agent 指南文件的 ## Worktree setup 部分声明的命令,让工作树做好运行准备:首次运行时执行 setup:,监视路径发生变化时执行 update:,以及在 Remove 删除工作树前执行 teardown:。工作树管理目录中的 gitkit-prepared 标记会记录每次运行所基于的提交。只有存在该标记时才会执行 teardown,因此 gitkit 绝不会移除它未设置过的资源。
相关流程、声明格式、模板和锁文件表格都在 prepare.md 中。当运行准备工作树、同步对已准备的工作树应用了提交,以及移除操作涉及已准备的工作树时,请阅读该文件。
clean
清理已完成的工作树和分支,包括本机和 origin 上的内容。它会将每个工作树、本地分支和远程分支归入一个类别:active、adopted、dirty、reapable、orphan,并且每次只移除一个已确认的 reapable 项目。删除远程分支需要单独确认,并且要单独证明该分支的工作已落地。
有两点让它不只是一个 git branch --merged 循环,这两点都详见 clean.md:squash 合并的分支在提交祖先关系中不可见,因此“已合并”需要三种检测方式,而不是一种;逐项确认是有意为之,因为清理列表中的项目并非都同样适合删除。当运行实际执行清理时,请阅读该文件。
清理所做的每项移除操作都遵循 Remove 规则。它会逐项单独确认,仅在 squash 例外情况下使用 -D,绝不触碰没有 gitkit 标记的工作树,并且遇到有未提交更改的工作树时会停止。在 origin 上,它绝不删除基准分支,也绝不删除开放中的拉取请求的头部分支。
rescue
找回看似丢失的工作并恢复它。错误的变基、硬重置、已删除的分支、找不到的 stash:相关提交通常仍然存在,git 自己的日志也会指出它们去了哪里。
相关流程详见 rescue.md。有两条规则决定了整个流程:在恢复之前,它是只读的;恢复操作会新增分支,而不是移动现有分支,因为正是重置操作让工作丢失。该文件也会明确说明限制,而不会暗示操作绝对安全:不可达的提交会一直存在,直到 git 清理它。
救援期间,绝不要以任何模式运行 git gc、git prune 或 git reflog expire。
堆叠分支
堆叠是同一仓库中的一串分支:每个分支都从它下方的分支切出,最底层则基于 trunk。适用于当前工作依赖尚未合并的工作成果:无需等待前置工作的拉取请求合并,就可以从它切出分支并继续推进。
GitHub 原生支持这一功能,而 gh stack 扩展则负责驱动它。**这是 gitkit 中唯一需要 gh 和 GitHub 远程仓库的部分。**完整的命令接口、预检和降级路径都在 stacks.md 中;只有当一次运行实际涉及堆叠时,才需要阅读。以下内容则是每次运行都需要了解的部分。
每一层各有一个工作树
每一层都保留自己的工作树,并从下方那一层切出。一个分支对应一个工作树这一不变规则没有改变:一层就是一个分支,因此会有一个以该分支命名的目录;仍按分支查找工作树。
另一种做法是让整个堆叠共用一个工作树,再用 gh stack up 和 down 在其中的各层之间移动。这是该扩展自身导航功能所采用的方式。但本项目集合并不这样做,因为这里的每个调用方都将工作树关联到正在处理的问题,而一个堆叠包含多个问题。因此,gh stack 的导航命令不在适用范围内;up、down、switch、top 和 bottom 也不由 gitkit 执行:它们会移动一个已被另一个工作树占用的检出目录。
gitkit 在此负责什么,不负责什么
gitkit 只负责底层操作:创建堆叠、添加一层、重新堆叠、同步、检查,以及将现有分支纳入堆叠。以下两条命令则有意排除在外,因为它们越过了此技能在其他方面遵守的职责边界:
- **
gh stack submit会创建拉取请求。**gitkit 从不创建任何内容。这属于负责编写拉取请求的技能。 - **
gh stack merge会合并拉取请求。**gitkit 从不执行合并;而堆叠合并会进行级联,一次合并多个拉取请求,因此需要人工确认并逐一列出这些拉取请求。这属于负责合并的技能。
这里特意指出责任方,而非命令本身:gitkit 负责做好准备,发布则由其他人负责,无论分支是否属于堆叠。
堆叠深度的代价
堆叠每增加一层,就多一个需要审查的拉取请求,以及一个每当下方内容发生变化时都需要变基的分支。重新堆叠会级联进行,因此底层发生变化时,上方每一层都会被重写。
**这里有意不设定具体数量。**堆叠应有多深,取决于每一层的规模和审查速度,应由人来决定。他们询问时,再说明具体机制,让他们自行确定合适的深度。
容器和远程环境
Git worktree 会存储绝对路径。如果仓库在容器中的绑定挂载路径与主机上的路径不同,其中的每个 worktree 都会损坏。
- 挂载
$WORKTREE_ROOT和主检出目录两者。worktree 的实际 git 目录位于主仓库的.git/worktrees/下;只挂载 worktree 会导致检出损坏。 - 在容器内外都挂载到相同的绝对路径。这是最稳妥的方案。
- 另一道保险措施,适用于 git 2.48 及更高版本:
git config worktree.useRelativePaths true和git worktree repair --relative-paths。只要仓库和 worktree 根目录的相对位置不变,相对路径指针就能在路径映射变化后继续有效;因此最好让两者的相对位置保持固定,例如~/code/<repo>和~/worktrees/<repo>。
欢迎使用能管理 worktree 的 GUI 开发工具来发现这些 worktree。Orca 及同类工具会读取 git worktree list,通常也提供设置,以显示外部创建的 worktree。这只需在运行 GUI 的机器上进行一次 UI 配置,也是机器之间唯一的不对称之处。
任何技能都不得通过厂商 CLI 创建或移除 worktree:一旦这样做,两台机器就会出现分歧。厂商对 worktree 自有的元数据,例如卡片关联的问题、状态或评论,则属于不同层面,可以使用,因为这些内容只存在于该工具中,没有对应的 git 信息会产生分歧。只要 worktree 本身仍在这里创建和销毁,配套技能就可以协调这一层(orcakit 会为 Orca 这样做)。gitkit 绝不会调用这类工具:它必须能在未安装此类工具的机器上继续工作。
注意事项
- gitkit 是其所管理内容的唯一事实来源。 调用它的技能可以制定策略,决定何时发起请求,例如 PR 是否值得拉取,或分支是否已准备好发布,但绝不能规定 git 操作如何执行。
- 可以重述结论;重述推导过程才是问题。 每个公开技能都是独立安装到仓库中的,而这些仓库可能没有 gitkit。因此,调用方需要 git 信息时,必须携带足够的信息才能继续工作,而这项要求优先于整洁性。两者的界限在于:调用方可以陈述 gitkit 的答案以及降级备用方案(“gitkit 负责同步规则,结论是 rebase;没有 gitkit 时,就 rebase 到基准分支上”),但不能复述得出该答案的推理过程,即完整的基准引用逐级回退规则、worktree 路径公式,或 rebase 与 merge 的比较论证。答案只有一行,重命名不会悄然使它失效;推导过程才是会逐渐偏离的部分。值得参考的合规示例:prkit 和 mergekit 都会说明 rebase 这一结论及其一次确认规则,但不会重新推导;wikikit 和 designkit 都将
gh repo view --json defaultBranchRef指定为基准引用的备用方案,但不会附带五级回退规则。 - gitkit 从不实现、提交或打开任何内容。 它只负责准备环境并清理环境。
prepare执行的命令属于仓库,而非 gitkit 自身。在 worktree 中编写代码、提交和发布都属于其他技能。 - 破坏性步骤必须先预览并确认。 移除 worktree 和删除分支时,都要展示即将执行的操作并等待确认。清理工作依附于移除操作的确认。创建、接管和同步则直接执行。同步会重写已发布的分支,但请求同步的用户已经做出了决定,租约会保护远程端,而
refs/gitkit/pre-sync/$BRANCH可以撤销此操作。 - 没有可用的 Shell(例如基于浏览器的 Agent)?那就无法运行 git。根据用户提供的信息进行判断,并以代码块形式输出准确的命令供其运行;绝不要声称创建或移除了你无法实际操作的 worktree。