这是关于作者编写的一个脚本,它使创建 PR 稍微更方便一些,以及当作者切换到Jujutsu时它必须如何改变。这不是 Jujutsu 的介绍 - 请参阅学习资源了解。
制作 PR 的六个步骤或一个步骤
- 推送你的分支
- 在 GitHub 上找到仓库
- 转到拉取请求
- 点击“新建拉取请求”
- 找到正确的分支(你叫它什么?)
- 点击“创建拉取请求”
多亏了出色的GitHub CLI,你可以在终端中一行完成所有这些操作:
alias gprc='git push -u && gh pr create --web'
对于 git 用户,这篇文章就结束了。使用它。作者使用了 3 年。它是最好的。
在 Jujutsu 中,最后创建分支
如果使用 Jujutsu,你可以做几乎相同的事情,除了此时你还没有命名的分支。在 Jujutsu 中,你不是从一个分支开始并在该分支上工作 - 没有“当前”分支的概念。你只是在main
之上进行提交,只有在推送时才通常创建一个书签。1
如果你刚刚从 git 切换过来,这很烦人,因为我们漂亮的gprc
不再足够了:你还必须创建一个分支,命名一个分支需要一些努力,即太多努力。
许多 jj 用户通过向 jj git push
传递--change <REVSETS>
来避免命名分支,这告诉 jj 使用更改 ID,从而导致一个像push-urzrzuzsurwx
这样的分支。2 事实上,像这样的分支名称是在 GitHub 上可见的少数几个表明某人正在使用 jj 的迹象之一。
分支名称很重要,但不是那么重要
关键是,有意义的分支名称可以很有用。当你的同事想要在本地检出你的工作以便他们可以测试它时,他们可能会使用你的分支名称来找到它。你自己可能也会使用分支名称在正在进行的工作流之间切换。
分支名称不必是完美的才能在这种情况下有用。与 PR 标题和描述(告诉人们你做了什么以及为什么,并且在合并时成为提交消息)不同,分支名称只需要大致指向正确的更改。给它们命名不是什么伟大的艺术。
让我们让计算机来做。
生成分支名称
你如何自动生成一个合适的分支名称?这听起来不可能。哦,对了:在 2025 年,不可能的事情花费三美分。
嗯,这很容易。作者将差异和提交日志通过管道传输到一个 LLM CLI3,该 CLI 将其发送到 Gemini 2.5 Flash(在撰写本文时最好的快速且便宜的模型),并在半秒钟内以 0.00036 美元获得了一个分支名称。这里唯一真正的技巧是使用 LLM CLI 作为连接到 LLM 的一种非常简单的方式,并知道要使用哪个模型。4
现在让我们看看将此操作插入到一个可用的脚本中需要什么。
最终产品:jprc
你可以在我的 dotfiles 中看到完整的 jprc.ts
,它由Deno、dax和Cliffy提供支持,这是我选择的脚本工具。
它有 54 行,因为除了生成分支名称之外,还有更多需要做的事情。一半的行用于帮助用户选择基础分支,如果在main
和目标修订之间有任何分支。有时我们在一个大 PR 之上制作一个小 PR,如果我们使用自main
以来的完整差异来生成分支名称,它将不够具体到这个小更改。我们还将使用--base
将基础分支传递给gh pr create
,这样我们就不必在表单中手动将其从main
更改。
现在我们生成分支名称并提示确认:
在确认分支名称后,我们在本地创建它,推送它,并像以前一样运行gh pr create
。
这里是核心逻辑:
// 1. 选择一个基础分支
const base = await pickBase(r)
// 2. 确保基础分支在远程
const result = await $`jj bookmark list --remote origin ${base}`.text()
if (!result) throw new ValidationError(`Base '${base}' not found on origin.`)
console.log(`\nCreating PR with base %c${base}\n`, "color: #ff6565")
// 3. 打印提交日志
const range = `${base}..${r}`
await $`jj log -r ${range}`.printCommand()
// 4. 生成一个分支名称并确认它(允许编辑)
const generated = await $`jj diff -r ${range}; jj log -r ${range}`
.pipe($`ai --system "${prompt}" -m flash --raw --ephemeral`)
.text()
// 5. 确认分支名称,允许编辑
const opts = { noClear: true, default: generated.trim() }
const bookmark = await $.prompt("\nCreate branch?", opts)
// 6. 推送分支
await $`jj git push --named ${bookmark}=${r}`.printCommand()
// 7. 创建 PR
await $`gh pr create --head ${bookmark} --base ${base} --web`.printCommand()
LLM 是否过度?
很自然会想知道将 LLMs 引入这个小时刻是否过度。但作者认为这种反应部分基于一种直觉,而这种直觉已被证明是错误的:调用 LLM 是复杂的、昂贵的或有风险的。在正确的工具和场景中,LLMs 如此干净地融入 Unix 管道(输入文本,输出文本),以至于将这样一个愚蠢的想法构建到一个个人工具中是微不足道的。这种微不足道使得同样容易问“为什么要麻烦?”和“为什么不呢?”它就像在问:将awk
引入这个脚本是否过度?也许,但只有当它对你绝对没有任何作用时才是。
值得思考为什么在这种特定情况下潜在不可靠的助手的下行风险如此之小。风险很低,输出经过人工审查(或其他方式易于验证)5,并且如果分支名称不好很容易恢复:你只需创建一个不同的名称。如果这些事情中的任何一个不是真的,下行风险可能是无法忍受的。它还需要快速且便宜,并且足够好,以至于你不必经常恢复 - 如果分支名称有一半时间是坏的,那就不值得了。所有这些都是需要根据具体情况进行评估的偶然因素。
LLM 集成是一行代码,即以const generated =...
开头的那一行。如果我们取出这一行,而是提示用户输入分支名称,该脚本仍然值得用于它所做的其他事情。事实上,这就是jprc的早期版本的工作方式。有了这样一个简单的集成,很容易尝试生成分支名称,评估它是否有用,如果没有则将其取出。
你可以生成 PR 标题和正文吗?应该吗?
你也可以生成 PR 标题和正文,并通过--title
和--body
将它们传递给gh pr create
。作者不会这样做 - 作者的 PR 描述更多地是关于为什么而不是做了什么,并且这很少在差异或提交消息中体现。作者会花费更多时间阅读和重写它生成的内容,而不是从预先填充中节省时间 - 这违反了上面提到的输出易于验证的要求。
如果你包含了 PR 所关闭的问题的文本,也许模型可以解释该更改如何关闭该问题,但即使这样听起来也很冒险。像 Cursor 或 Claude Code 这样的编码代理可能更有机会根据产生差异的对话生成合适的 PR 正文。
Jujutsu 资源
- jj README - 作者实际上更喜欢这个而不是他们网站上的介绍
- Steve 的 Jujutsu 教程 - 每个人都喜欢 Steve
- Git 和 jujutsu:微型 - 让作者最终理解的帖子
- 我从 jj 中学到的 - 作者希望自己写的“jj 的酷之处”的帖子
脚注
- 在 Jujutsu 中,分支被称为书签。这个名字来自 Mercurial。jj 开发者在作者学习它时将名字从分支改为书签,出于某种原因,新名字真的帮助作者克服了基于 git 的期望。↩
- 你可以配置前缀。人们喜欢在前面加上他们的用户名,如
david-crespo/push-
。↩ - 作者在这里使用自己的 LLM CLI,但它非常精简且适合作者的需求,所以作者通常推荐 Simon Willison 的
llm
。↩ - 作者尝试了其他轻量级模型,如 Claude 3.5 Haiku、GPT-4.1 mini、Llama 3.3 70B(通过 Groq 和 Cerebras)以及 Llama 4 Maverick 和 Scout。它们大多还可以,尽管没有一个像 Gemini 2.5 Flash 那样始终表现良好。Llama 4 Scout 显然不够好:有一次它生成了分支名称
branch-names-are-hard
。↩ - 作者想到的是类似于类型检查和测试如何限制 LLM 生成的代码可能出错的方式。↩
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。