关于 gh-pr
gh-pr 是一项技能,用于从当前分支创建 GitHub 拉取请求,并依据完整改动编写标题和说明。它使用 `gh`,在推送前检查仓库、分支和本地工作状态。
主要功能
- 01检查本地仓库与分支
- 02评估基准分支和本地改动
- 03检测现有拉取请求及分支冲突
- 04避免强制推送
- 05根据改动撰写 PR 标题和正文
适用场景
- 01为已完成的分支创建拉取请求
- 02在推送前找出无关的本地工作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dzhuneyt/ skills --skill gh-pr安装后在 Claude Code 中输入/gh-pr确认已加载。
详细介绍
gh-pr
从当前分支创建一个拉取请求,其标题和正文应准确反映实际变更,而不只是最近一次提交消息。目标是让审阅者无需先阅读差异内容,也能理解这个 PR。
所有操作都使用 gh。它负责处理身份验证和 API 调用,并在最后返回 URL。不要编写脚本直接调用原始 API。
工作流程
按顺序分为三个阶段。决定执行顺序的硬性规则是:确认工作位于正确分支之前,不得推送任何内容。 阶段 2 完全在本地进行,且可撤销;第一个近乎不可逆的操作(推送)在阶段 3 开始时进行。
Phase 1 Can I run? local gates + gh auth
any gate fails -> stop
|
v
Phase 2 Right place? base -> assess -> branch-fit -> settle tree
all local, nothing pushed yet
|
v
Phase 3 Open the PR push -> compose -> gh pr create -> url
阶段 1 · 能否运行?
步骤 0:快速本地检查
全部在本地进行,不联网、不推送,因此任何失败都能在毫秒内发现,避免耗费资源进行后续操作。顺序很重要:先检查成本最低、最根本的条件。
必须退出——后续步骤无法补救,因此应立即停止并给出明确提示:
- 不是 Git 仓库。
git rev-parse --is-inside-work-tree。这是最快的检查,应首先运行。 - 未安装
gh。command -v gh。这是身份验证检查中快速的本地部分,应与gh auth status分开(后者需要联网,见预检)。「未安装」和「未登录」需要不同的提示。 - 没有
origin远程仓库。git remote get-url origin(非零退出状态表示没有origin)。此技能假设只有一个名为origin的远程仓库,并会将分支推送到该仓库;没有origin就无处推送,也没有可供gh操作的目标,因此应在此停止。不处理 Fork 工作流——故意不支持将更改推送到 Fork、同时以upstream仓库为 PR 目标(--head owner:branch)的做法。如果唯一的远程仓库名称不同(例如upstream),请停止并询问,而不要自行猜测。 - 远程仓库不是 GitHub。
git remote get-url origin,检查主机名。GitLab、Bitbucket 或其他平台 →gh无法提供帮助,应在此停止。 - HEAD 分离或分支尚未诞生。
git symbolic-ref -q HEAD(输出为空表示 HEAD 分离;也能检测出没有提交的新仓库)。没有分支就无法创建 PR。
确认后停止——虽然可以继续,但几乎总是错误,因此应暂停,而不是直接拒绝:
- 当前位于默认分支。 从
main向main提交 PR 几乎总是错误的。快速检查方式:如果git symbolic-ref refs/remotes/origin/HEAD能在本地解析(仅当克隆时设置了origin/HEAD才存在,例如通过git remote set-head origin -a设置),就在此处进行比较。如果不存在,则跳过此处的检查;预检阶段会权威地解析默认分支并再次检查。 - 操作进行中。 检查
MERGE_HEAD、.git/rebase-merge、.git/rebase-apply、.git/CHERRY_PICK_HEAD、.git/REVERT_HEAD。如果变基、合并、拣选提交或还原操作正在进行,工作区就处于半完成状态——此时创建的 PR 毫无价值。 - 相比基准分支没有领先提交。 这只是冒烟测试——此时尚未解析基准分支(这将在阶段 2 进行)。使用本地已解析的默认分支(步骤 0 默认分支检查中的
origin/HEAD目标;如果该引用不存在则跳过)通过git rev-list --count origin/<default>..HEAD进行比较。**将结果 0 视为退出条件之前,先检查git status --porcelain:**工作区有未提交的更改,说明工作已存在,只是尚未提交——不要退出;继续进入阶段 2,让「整理未提交的更改」将其提交。只有提交数为 0 且工作区干净,才应真正退出。阶段 2 会权威地确定基准分支(包括用户指定的基准分支),并取代此检查;此处计数非零并不保证相对于实际基准分支有提交,而且origin/<default>在本地可能已过时。
步骤 0 通过后,git branch --show-current 会给出下文所用的头分支。
预检(网络)
需要一次往返,因此只有 Step 0 通过后才会运行。
-
gh auth status— 确认 CLI 已通过身份验证。如果尚未通过,请引导用户使用gh auth login;不要替他们登录。 -
在此处解析一次默认分支。 后续所有步骤(Step 0 的默认分支检查、Phase 2 中的基准分支)都使用此值。回退链 — 采用第一个成功的结果:
git symbolic-ref --short refs/remotes/origin/HEAD→ 去掉origin/前缀(本地操作,无需往返)。gh repo view --json defaultBranchRef --jq .defaultBranchRef.name(权威结果,一次往返)。
如果 Step 0 因缺少
origin/HEAD而跳过了本地检查,现在重新检查,确认当前分支不是默认分支。
PHASE 2 · 工作是否在正确的位置?
此阶段的所有操作都在本地进行。推送前完成所有检查。
确定基准分支
使用预检中已解析的默认分支——不要再次解析。除非用户指定了其他基准分支,否则使用默认分支。堆叠 PR(一个功能分支以另一个功能分支为目标)确实存在,但很少见——用户通常会说明。若不确定,请先询问,不要自行假设是 main。
评估本地状态
一次性收集所需信息;这些信息既用于判断分支是否合适,也用于 PR 正文。
先解析一次上游分支,因为默认的首次 PR 流程中没有上游——该分支从未推送过,因此不存在 origin/<branch>,任何 origin/<branch>..HEAD 比较都会报错:
git rev-parse --abbrev-ref @{u} 2>/dev/null # upstream, or empty
- 存在上游分支 → 未推送的提交为
git log --oneline @{u}..HEAD。 - 没有上游分支(首次 PR)→ 无可比较对象;将基准分支之后的每个提交都视为未推送,并完全跳过
origin/<branch>比较。
git status --porcelain # dirty tree?
git log --reverse --format='%s%n%b' origin/<base>..HEAD # commits since base
git diff --stat origin/<base>...HEAD # files + scope
gh pr list --head <branch> --state open --json number,url,title # open PR?
仅本地的改动 = 未推送的提交 + 未提交的更改。这些工作尚未记录在任何地方——分支适配性检查正是据此判断。首次 PR 流程中,基准分支之后的每个提交都属于这些改动。
分支适配性判断
可能出现的问题:用户完成了功能 A(可能已经创建 PR),随后没有切换分支就开始做功能 B,现在准备把 B 的工作推送到 A 上。
不要以“分支名称是否与差异匹配”为判断依据。通用或个人化名称(dev、wip、dzh/misc)通常不匹配任何内容,且会频繁误报;分支的工作范围也可能合理扩展。应依据分支是否已有 PR,以及其上是否有仅本地的工作来判断:
open PR on branch? local-only delta? -> action
-----------------------------------------------------------------
no either open a fresh PR
yes none nothing new -> stop
yes some STOP and ask (below)
只有最后一行会中止常规流程。此时仅概述改动部分(而非整个分支),例如:“分支 <branch> 已有待处理的 PR #123。自那之后,你有 2 个未推送的提交和 4 个已更改文件——这些内容应放在此分支上,还是应该移到自己的分支?”
保持这一关卡的高准确率。如果它总是以“你确定自己在正确的分支上吗?”打断常规 PR 流程,用户就会学会直接略过它,它也就失去了作用。只有右下角的单元格会中止流程。
如果工作需要迁移
处理方式取决于工作是否已提交,因为两者的操作方式和风险差异很大:
move the work off this branch:
uncommitted -> git switch -c <new> origin/<base> safe, just do it
(then continue — PR opens from <new>)
committed -> git switch -c <new> origin/<base> you do this
cherry-pick the stray commits onto <new>
...then reset old branch + force-push user's call — STOP
对于已提交的情况,刻意不直接修改旧分支:清除其上的误提交会重写历史,因此需要强制推送——而此技能绝不会自动强制推送,尤其不会在已有待处理 PR 的分支上这样做。执行步骤 1–2 后,将历史重写交由用户处理。
两种处理方式中的关键点:从 origin/<base> 创建分支,而不是从当前分支顶端创建。你当前所在的位置执行 git switch -c <new> 会以旧分支为基础创建新分支,之后创建的 PR 就会包含旧分支的所有提交。
处理未提交的更改
现在工作已处于正确的分支:
git status --porcelain
如果工作区有未提交的更改,列出这些文件并停止。 不要代替用户提交——他们可能还在斟酌,而悄悄提交会掩盖他们想自行决定的事项。询问他们要提交、暂存,还是不纳入更改。如果他们要求你提交,请按照 Conventional Commit 格式编写提交说明(见阶段 3)——根据更改内容选择类型,不要一概使用 chore。
阶段 3 · 创建 PR
推送分支
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null
- 没有上游分支 →
git push -u origin <branch>。 - 已有上游分支,本地领先 →
git push。 - 绝不使用
--force或--force-with-lease。如果推送被拒绝,告知用户并让他们决定。
撰写标题和正文
可选:撰写正文前更新基分支。
运行 git fetch origin <base>,更新 origin/<base>,确保阶段 2 中的差异命令(git diff --stat origin/<base>...HEAD、git log ... origin/<base>..HEAD)反映真实的合并基点,而非可能已过时的本地副本。如果基分支自上次获取后可能已有变动,请执行此操作,例如发生过合并或变基,或共享分支上有活跃的协作者。如果时间紧迫,或确定基分支是最新的(例如刚刚拉取过),则跳过。此操作会产生一次网络往返。
先检查项目约定:
- PR 模板 — GitHub 按优先顺序从三个根目录位置查找模板:
.github/、docs/和仓库根目录。在每个位置查找pull_request_template.md、PULL_REQUEST_TEMPLATE.md或PULL_REQUEST_TEMPLATE/目录。因此,所有可能的位置为:.github/pull_request_template.md、.github/PULL_REQUEST_TEMPLATE.md、.github/PULL_REQUEST_TEMPLATE/、docs/pull_request_template.md、docs/PULL_REQUEST_TEMPLATE.md、docs/PULL_REQUEST_TEMPLATE/、pull_request_template.md、PULL_REQUEST_TEMPLATE.md、PULL_REQUEST_TEMPLATE/。如果存在模板,请填写其中的各个部分,而不是另行编造结构。维护者选择这种格式自有原因。 - 关联的问题 — 检查分支名称和提交:
- GitHub 问题,例如
#123/fixes #123。 - Linear 键,例如
eng-412,通常出现在分支名称中(dzh/eng-412-token-refresh)。PR 标题或正文中包含 Linear 键时会自动建立关联,因此请确保其中一处包含该键。 - 如果分支明显对应某个问题,但没有关闭关键词,请添加一个(
Closes #123/Fixes ENG-412),除非更改只解决了其中一部分。
- GitHub 问题,例如
标题: 单行标题,使用祈使句描述整体更改——如果分支有五个提交,就总结所有提交,而不是只总结第 5 个。默认使用 Conventional Commit 格式,但如果仓库明显有自己的约定(例如存在 commitlint 配置,或现有 PR/提交标题格式一致),则遵循该约定——在仓库已确立的风格下强行使用 CC,可能与 commitlint 或 squash merge 工具冲突。类型和范围应体现整体更改。
Conventional Commit 格式
用于 PR 标题和此技能创建的任何提交:
type(optional-scope): short imperative description
- 类型:
feat、fix、docs、style、refactor、perf、test、build、ci、chore、revert。选择最符合情况的具体类型;不要默认使用chore。 - 范围可选,但很有用——受影响的包、模块或区域,例如
feat(auth):。 - 描述使用小写、祈使语气,不以句号结尾,长度不超过约 72 个字符。
- 破坏性变更: 在类型/范围后添加
!(feat(api)!: ...);对于提交,添加BREAKING CHANGE:页脚。
如果变更涉及多种类型,选择占主导地位的类型,而不是编造复合类型。
正文(不存在模板时)——简短易读:
## What
<one or two sentences: which files, what kind of change>
## Why
<one or two sentences: what made this necessary>
## Notes for review
<the contestable parts only>
<Closes #N / Fixes ENG-N if applicable>
目标是不超过约 300 个词,且最多包含一个表格。超过这个长度的内容几乎总是在重复表述。
“审查备注”是最容易写偏的部分。它不是差异的摘要——差异就在眼前。它应简要列出审查者可能有不同意见的事项:你做出的推断、判断、你声称的数字、此分支中先前犯过的错误。如果没有这些内容,就删掉这一节。
让内容简短的规则:
- 先提出一个最重要的发现。 开头用粗体写出审查者绝不能略过的唯一要点。其余内容都应为它提供佐证。三个同等重要的粗体区块说明你还没决定重点是什么。
- 对同一影响只说明一次。 如果某个项目符号已经说明了发现的含义,就不要再添加一段解释其影响的文字。
- 指向产物,而不是复制内容。 如果差异已经包含完整细节,就简要说明并写上“完整表格见记录”。正文应引导阅读,而不是再复制一份。
- 表格的每一行应对应不同的行为,而非不同的命令。 合并结果和原因相同的行。八次验证运行产生四种行为,就用四行表格。
- **删除否定结果,**除非不写的话审查者会以为功能已损坏。
- 正文范围应与差异一致。 变更提出但未涉及的待解决问题应放入 issue,而不是写在这里。
让内容保持实用的规则。不要为了节省篇幅而删掉以下内容:
- 你在此分支中先前哪里错了,以及你做了什么更正。
- 为什么已被取代的决定在当时是正确的。“合并时记录的是非 Y,这在当时属实;之后 Y 才合入”体现的是情况变化,而不是需要追查的错误。
- 精确标识符:策略 ID、错误字符串、版本、标志。它们能告诉审查者该去哪里查看。
不要凑字数。
草稿与待审状态
默认设为待审状态。如果分支看起来尚未完成,则自动切换为 --draft——例如提交主题包含 wip、tmp、fixup! 或 squash!。始终遵从明确的请求(例如,用户在 $ARGUMENTS 中传入“draft”)。
创建
gh pr create \
--base <base> \
--head <branch> \
--title "<title>" \
--body "<body>" \
[--draft]
除非有人提出要求,否则不要添加审查者、受指派人、标签或里程碑——自动分派会给他人发送垃圾信息,自动添加标签则是在猜测你无权决定的分类体系。
如果 gh pr create 提示 PR 已存在,不要再创建第二个——显示现有 URL,并询问是否要更新它(gh pr edit)。(通常第 2 阶段会先处理这种情况。)
完成
单独一行打印 PR URL。这就是用户想要的。不要再向他们概述差异——他们刚刚才写完它。
然后在下一行打印一条自动合并提示:enable auto-merge: gh pr merge <number> --auto --squash(替换 PR 编号;选择仓库使用的合并方法——--squash、--merge 或 --rebase)。此技能绝不会运行这条命令——它只会提供选项,让用户明确选择是否启用。对于草稿 PR(尚不可合并),跳过这条提示。
硬性限制
- 绝不强制推送。
- 绝不自动合并或启用自动合并。
- 未经明确确认,绝不提交未提交的变更。
- 绝不从默认分支向其自身创建 PR。
- 绝不在未获请求时添加审查者/标签/受指派人。
- 在解决分支适配性决策前,绝不推送。