7 个版本
0.1.6 | 2020年6月22日 |
---|---|
0.1.5 | 2020年6月19日 |
#2711 在 命令行工具
每月 25 次下载
2.5MB
580 行
gh-stack
我使用这个工具来帮助管理 Github 上的堆叠拉取请求,这些请求通常难以手动管理。以下是一些示例
- https://unhashable.com/stacked-pull-requests-keeping-github-diffs-small
- https://stackoverflow.com/questions/26619478/are-dependent-pull-requests-in-github-possible
- https://gist.github.com/Jlevyd15/66743bab4982838932dda4f13f2bd02a
此工具假定
- 单个 "堆叠" 中的所有 PR 标题都具有唯一的标识符(我通常使用 Jira 工作单号作为此目的)。
- 堆叠中的所有 PR 都位于单个 GitHub 仓库中。
- 这些 PR 所表示的所有远程分支都有一个同名本地分支。
然后它会查找所有包含此标识符的 PR,并在内存中构建一个依赖图。技术上这可以支持 "分支堆叠" 而不是单个链,但我尚未真正尝试后者。构建了此图后,工具可以
- 为堆叠中每个 PR 的描述(幂等)添加一个 Markdown 表格,描述堆叠中的所有 PR。
- 将堆叠中所有 PR(+ 依赖项)的简单列表记录到 stdout。
- 在本地进行更改后自动更新堆叠并推送。
安装
目前仅可通过从源码构建来安装
# Install Rust
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Configure `PATH`
$ export PATH="$HOME/.cargo/bin:$PATH"
# Install `gh-stack`
$ cargo install gh-stack
用法
$ export GHSTACK_OAUTH_TOKEN='<personal access token>'
$ gh-stack
USAGE:
gh-stack <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
SUBCOMMANDS:
annotate Annotate the descriptions of all PRs in a stack with metadata about all PRs in the stack
autorebase Rebuild a stack based on changes to local branches and mirror these changes up to the remote
log Print a list of all pull requests in a stack to STDOUT
rebase Print a bash script to STDOUT that can rebase/update the stack (with a little help)
# Idempotently add a markdown table summarizing the stack
# to the description of each PR in the stack.
$ gh-stack annotate 'stack-identifier'
# Same as above, but precede the markdown table with the
# contents of `filename.txt`.
$ gh-stack annotate 'stack-identifier' -p filename.txt
# Print a description of the stack to stdout.
$ gh-stack log 'stack-identifier'
# Automatically update the entire stack, both locally and remotely.
# WARNING: This operation modifies local branches and force-pushes.
$ gh-stack autorebase 'stack-identifier' -C /path/to/repo
# Emit a bash script that can update a stack in the case of conflicts.
# WARNING: This script could potentially cause destructive behavior.
$ gh-stack rebase 'stack-identifier'
示例
以下是对此工具在实际应用中使用方式的快速概述。
-
编写一些代码,创建本地提交/分支
$ git checkout -b first # Write code $ git add -A; git commit -m 'first' $ git checkout -b second # Write code $ git add -A; git commit -m 'second #1' # Write code $ git add -A; git commit -m 'second #2' $ git checkout -b third # Write code $ git add -A; git commit -m 'third'
-
您的 Git 树现在看起来像
* 42315c4 U - (third) third | * 6db2c28 U - (second) second #2 | * 5746a83 U - second #1 | * e845ded U - (first) first | * 8031011 U - initial commit
-
推送每个分支
$ git push origin first:first second:second third:third * [new branch] first -> first * [new branch] second -> second * [new branch] third -> third
-
为每个新分支创建一个 PR(从
first
开始),并- 确保所有 PR 的标题中都有一个共同的标识符(我将使用
[EXAMPLE-17399]
)。此标识符(目前)必须在整个您可访问的 GitHub 仓库(包括所有公共仓库)中唯一。 - 将每个PR的
base
设置为它之前的分支。在这里,first
的PR设置为合并到master
,second
的PR设置为合并到first
,而third
的PR设置为合并到second
。
- 确保所有 PR 的标题中都有一个共同的标识符(我将使用
-
记录堆栈中的所有PR
$ gh-stack log 'EXAMPLE-13799' #1: [EXAMPLE-13799] PR for branch `first` (Base) #2: [EXAMPLE-13799] PR for branch `second` (Merges into #1) #3: [EXAMPLE-13799] PR for branch `third` (Merges into #2)
-
使用堆栈信息注释所有PR
$ gh-stack annotate 'EXAMPLE-13799' 1: [EXAMPLE-13799] PR for branch `first` 2: [EXAMPLE-13799] PR for branch `second` 3: [EXAMPLE-13799] PR for branch `third` Going to update these PRs ☝️ Type 'yes' to continue: yes Done!
这(幂等)会在堆栈中每个PR的描述中添加一个类似这样的表格:
-
修改一个以某种方式重写提交的分支(修改、删除提交、合并提交)
$ git checkout first # Do some work $ git add -A; git commit --amend -m 'amended first'
现在历史已经分叉,当
first
被(强制)推送时,这会导致与依赖PR的冲突。* e7cb9c6 U - (HEAD -> first) amended first | | * 42315c4 N - (origin/third, third) third | | | * 6db2c28 N - (origin/second, second) second #2 | | | * 5746a83 N - second #1 | | | * e845ded N - (origin/first) first |/ | * 8031011 U - (origin/master, master) initial commit
-
使用
autorebase
子命令来修复这种不一致性(它需要一个本地仓库签出的路径)$ gh-stack autorebase --repo /tmp/test EXAMPLE-13799 Checking out Commit { id: 803101159653bf4bf92bf098e577abc436458b17, summary: "initial commit" } Working on PR: "first" Cherry-picking: Commit { id: e7cb9c6cdb03374a6c533cbf1fc23a7d611a73c7, summary: "amended first" } Working on PR: "second" Cherry-picking: Commit { id: 5746a83aed004d0867d52d40efc9bd800b5b7499, summary: "second #1" } Cherry-picking: Commit { id: 6db2c2817dfed244d5fbd8cbb9b8095965ac9a05, summary: "second #2" } Working on PR: "third" Cherry-picking: Commit { id: 42315c46b42044ebc4b57a995a75b97699f4855a, summary: "third" } ["b45e5838a93b33411a5f0c9f726bc1987bc71ff5:refs/heads/first", "93170d2199ed9c2ae30d1e7492947acf477fb035:refs/heads/second", "a85a1931c44c3138d993128591af2cad2ef6c68d:refs/heads/third"] Going to push these refspecs ☝️ Type 'yes' to continue: yes Enumerating objects: 12, done. Counting objects: 100% (12/12), done. Delta compression using up to 8 threads Compressing objects: 100% (8/8), done. Writing objects: 100% (11/11), 907 bytes | 453.00 KiB/s, done. Total 11 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), done. To github.com:timothyandrew/test.git + e845ded...b45e583 b45e5838a93b33411a5f0c9f726bc1987bc71ff5 -> first (forced update) + 6db2c28...93170d2 93170d2199ed9c2ae30d1e7492947acf477fb035 -> second (forced update) + 42315c4...a85a193 a85a1931c44c3138d993128591af2cad2ef6c68d -> third (forced update) Updating local branches so they point to the new stack. + Branch first now points to b45e5838a93b33411a5f0c9f726bc1987bc71ff5 + Branch second now points to 93170d2199ed9c2ae30d1e7492947acf477fb035 + Branch third now points to a85a1931c44c3138d993128591af2cad2ef6c68d All done!
-
这会将本地历史恢复为平列表,并将每个分支的尖端推送到更新PR本身。
* a85a193 N - (HEAD, origin/third, third) third | * 93170d2 N - (origin/second, second) second #2 | * 61f64b6 N - second #1 | * b45e583 N - (origin/first, first) amended first | * 8031011 U - (origin/master, master) initial commit
-
如果遇到冲突,
autorebase
会暂停,让您在继续之前修复冲突。
-
策略
这是对autorebase
子命令使用的策略的简要总结
- 找到堆栈中第一个PR的本地分支与其合并到的分支之间的
merge_base
(通常是develop
)。这构成了初始 cherry-pick 的边界。这是一个启发式方法,并不适用于所有情况,尤其是在更改已经推送或PR直接在GitHub上合并的情况下。接受一个显式的边界来避免这里的歧义。 - 检出堆栈中第一个PR合并到的提交/引用(通常是
develop
)。我们将 cherry-pick 整个堆栈到这个提交。 - 从第一个PR cherry-pick 所有提交到
HEAD
(停止在步骤1中计算的 cherry-pick 边界)。 - 移动第一个PR的本地分支,使其指向
HEAD
。 - 第一个PR的远程跟踪分支成为下一个 cherry-pick 边界。
- 对每个后续PR重复步骤3-5,直到所有PR都已 cherry-pick。
- 通过传递多个 refspecs 到
git push -f
的单个调用,一次性推送所有引用。
免责声明
自行承担风险(并确保您的 git 仓库已备份),尤其是因为
- 此工具适用于我的特定用例,但尚未经过广泛测试。
- 我在这个时候已经写了3周的Rust。
autorebase
命令处于实验状态;可能存在我没有考虑到的边缘情况。
依赖项
~21–34MB
~610K SLoC