mcpField
G
gh-pr

gh-pr

github.com/dzhuneyt开发063 次安装3 周前更新
代码仓库

关于 gh-pr

gh-pr 是一项技能,用于从当前分支创建 GitHub 拉取请求,并依据完整改动编写标题和说明。它使用 `gh`,在推送前检查仓库、分支和本地工作状态。

主要功能

  1. 01检查本地仓库与分支
  2. 02评估基准分支和本地改动
  3. 03检测现有拉取请求及分支冲突
  4. 04避免强制推送
  5. 05根据改动撰写 PR 标题和正文

适用场景

  1. 01为已完成的分支创建拉取请求
  2. 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:快速本地检查

全部在本地进行,不联网、不推送,因此任何失败都能在毫秒内发现,避免耗费资源进行后续操作。顺序很重要:先检查成本最低、最根本的条件。

必须退出——后续步骤无法补救,因此应立即停止并给出明确提示:

  1. 不是 Git 仓库。 git rev-parse --is-inside-work-tree。这是最快的检查,应首先运行。
  2. 未安装 gh。 command -v gh。这是身份验证检查中快速的本地部分,应与 gh auth status 分开(后者需要联网,见预检)。「未安装」和「未登录」需要不同的提示。
  3. 没有 origin 远程仓库。 git remote get-url origin(非零退出状态表示没有 origin)。此技能假设只有一个名为 origin 的远程仓库,并会将分支推送到该仓库;没有 origin 就无处推送,也没有可供 gh 操作的目标,因此应在此停止。不处理 Fork 工作流——故意不支持将更改推送到 Fork、同时以 upstream 仓库为 PR 目标(--head owner:branch)的做法。如果唯一的远程仓库名称不同(例如 upstream),请停止并询问,而不要自行猜测。
  4. 远程仓库不是 GitHub。 git remote get-url origin,检查主机名。GitLab、Bitbucket 或其他平台 → gh 无法提供帮助,应在此停止。
  5. HEAD 分离或分支尚未诞生。 git symbolic-ref -q HEAD(输出为空表示 HEAD 分离;也能检测出没有提交的新仓库)。没有分支就无法创建 PR。

确认后停止——虽然可以继续,但几乎总是错误,因此应暂停,而不是直接拒绝:

  1. 当前位于默认分支。 从 main 向 main 提交 PR 几乎总是错误的。快速检查方式:如果 git symbolic-ref refs/remotes/origin/HEAD 能在本地解析(仅当克隆时设置了 origin/HEAD 才存在,例如通过 git remote set-head origin -a 设置),就在此处进行比较。如果不存在,则跳过此处的检查;预检阶段会权威地解析默认分支并再次检查。
  2. 操作进行中。 检查 MERGE_HEAD、.git/rebase-merge、.git/rebase-apply、.git/CHERRY_PICK_HEAD、.git/REVERT_HEAD。如果变基、合并、拣选提交或还原操作正在进行,工作区就处于半完成状态——此时创建的 PR 毫无价值。
  3. 相比基准分支没有领先提交。 这只是冒烟测试——此时尚未解析基准分支(这将在阶段 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 中的基准分支)都使用此值。回退链 — 采用第一个成功的结果:

    1. git symbolic-ref --short refs/remotes/origin/HEAD → 去掉 origin/ 前缀(本地操作,无需往返)。
    2. 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),除非更改只解决了其中一部分。

标题: 单行标题,使用祈使句描述整体更改——如果分支有五个提交,就总结所有提交,而不是只总结第 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。
  • 绝不在未获请求时添加审查者/标签/受指派人。
  • 在解决分支适配性决策前,绝不推送。

评论

支持 Markdown