根本问题是这个问题的格式不正确:
...列出 PR 中更改的所有文件以及状态
实际上,拉取请求是以下形式的请求:请更改分行名称B
这样,当您开始更改它时,它就不会指向它所指向的任何提交,而是指向 commitC
反而。你可以随心所欲地这样做,但也许可以通过your own合并提交。这个拉取请求可能会也可能不会——很难说,因为拉取请求不是 Git 本身的一部分,而是各种 Web 服务提供的附加组件,并且每个 Web 服务都可以实现这一点they类似——包括以下形式的信息:顺便说一句,当我要求你更改 name 的值时B
哈希 IDC
, 价值was哈希IDO
.
我们暂时假设它提供了所有三项: 分支名称B
,旧的提交哈希 IDO
,以及请求的提交哈希 IDC
。我们还要声明一下B
当前的实际哈希值是A
。或许A
= O
,也许不是。 (如果O
不包含在拉取请求中,这并不是真正致命的,但是您需要找到合适的替代品,并定义它的工作方式等等。)
关于提交需要了解什么
现在,关于提交哈希 ID 的第一件事是它指定一项特定的提交,并且每次提交都是一个所有文件的完整快照。这根本不是一组变化!这只是一种状态,就像宣布今天外面的温度是 68°F (20°C) 一样。就其本身而言,这并不能告诉你昨天或上周发生了什么。要转动一个state into a change,您必须选择其他一些状态来与之比较。如果昨天温度为 77°F (25°C),change温度为 9°F (5°C)。
我们对 Git 中的提交做同样的事情:我们选择一种状态,例如C
——并将其与之前的状态进行比较。但我们刚刚说过actual之前的状态是A
,而recorded之前的状态是O
。如果你比较C
vs O
,你会得到与比较不同的答案C
vs A
,当然除非A
= O
.
但这并不是提交的唯一内容。每次提交还记录零个或多个parent提交哈希 ID — 通常正好为 1,对于合并提交,下一个最常见的是 2。 (零父母意味着提交是root犯罪;通常每个存储库只有一个,用于第一次提交。三个或以上是一个章鱼合并.) 的父母C
每个人都有自己的权利,也有父母,等等。考虑到父母,我们真正拥有的可能是这样的:
...--D--E--F--O--A <-- B
\ \
G--H--C <-- (pull requested at C)
或者,它可以更简单:
...--D--E--F--A <-- B
\
G--C <-- (pull requested at C)
where A
and O
是相同的提交。或者它甚至可能是删除某些提交的请求,尽管某些 Web 服务不允许这样做,或者如果您使用它们的“接受请求”接口,则不会采取任何操作,但让我们画出来:
...--C--O--A <-- B
(请求金额为请“倒带”B 以便提交A
and O
vanish).
如果需要再次合并怎么办?
让我们再看一下这张图:
...--D--E--F--O--A <-- B
\ \
G--H--C <-- (pull requested at C)
On GitHub, at least, if you use the default kind of "merge pull request", what GitHub will do is add a new commit—let's call it M
—whose snapshot is formed by running a new git merge
(on GitHub),1 even though C
itself is a merge commit. The snapshot in M
will be that produced by combining changes. The end product will be:
...--D--E--F--O--A--M <-- B
\ \ __/
G--H--C
但 GitHub 还提供了两种其他方式来接受此拉取请求,每种方式的作用都不同。如果提交G
, H
, and C
尚未进入your存储库,变基和合并选项会生成以下图表:
...--D--E--F--O--A--G'-H' <-- B
而挤压并合并选项产生:
...--D--E--F--O--A--S <-- B
where S
有contents that M
如果 GitHub 做了的话,就会有M
.
1The way GitHub makes a merge is not by using git merge
, at least not literally, as there is no way to handle conflicts. GitHub won't even offer to make M
if there would be merge conflicts. But the effect is as if GitHub ran a literal git merge
, so you can use this as a mental model, at least.
最简单的情况:A=O,快进的潜力
假设该图确实如下所示:
...--D--E--F--A <-- B
\
G--C <-- (pull requested at C)
所以这样current的价值B
与old的价值B
当时提出拉取请求的人已经成功了。也就是说,我们有A
= O
。如果您使用 GitHub 上默认的“合并”按钮,您将得到以下结果:
...--D--E--F--A------M <-- B
\ /
G--C
but the contents提交的M
将匹配contents提交的C
确切地说,这在几个方面都有帮助。
如果您使用变基和合并合并按钮的模式,你会得到:
...--D--E--F--A--G'-C' <-- B
哪里的G'
and C'
提交是copies拉取请求者的G
and C
提交:快照匹配,甚至父提交 IDG'
匹配的是G
;不同之处在于哈希 ID:GitHub 已复制了两者G
and C
即使制作这样的副本没有意义。 (我个人希望 GitHub 根本不制作这些副本。其他服务也可能不制作这些副本。)原始提交不会进入;相反,副本会被放入。
如果您使用挤压并合并按钮,你会得到这个:
...--D--E--F--A--S <-- B
哪里的parent of S
is A
,而content of S
匹配的是C
确切地。再次承诺G
and C
本身不进入存储库:仅C
的内容进入(作为“squash”提交中的新快照S
).
定义你想要什么
现在您知道拉取请求实际上是一个请求move分支名称,来自现在的位置(A
)到从他们请求的提交中派生的东西(C
),也许通过添加新的合并提交M
,也许通过添加新的挤压合并(普通单父非合并提交)S
,或者也许通过复制他们的部分或全部提交。
如果您接受他们的拉取请求,实际会发生什么,取决于您在 GitHub 上单击的按钮,或者您是否使用其他东西(Bitbucket?GitLab?无论它是什么),无论该系统提供什么。由你来决定会发生什么actually happen.
然后,知道实际会发生什么,就需要您再次弄清楚如何比较那些提交will进入您的存储库 - 可能实际上尚不存在的提交! - 已经存在的提交are在你的存储库中。您想比较内容吗C
给那些A
?你想比较一下吗C
的内容O
,假设哈希 ID 为O
有空吗?或者你想仔细看看什么each与它的父提交相比,新提交会做什么,即使这些提交实际上都不存在?
显然,最后一个是最难的:但是这些新提交的生成遵循一个明确定义的过程,因此要找出它们将包含什么,一旦它们确实存在,您可以简单地在您自己的存储库中自己创建它们如果您接受拉取请求,您的网络服务明天将按照同样的方式创建它们。这里需要注意的是,如果你do这样做——如果你预测他们明天会做什么——然后同意你明天会点击按钮,如果明天分支会发生什么B
指向除A
?
如果您选择最后一个选项,则基于什么接受would发生这种情况时,您将需要围绕整个操作构建某种流程,以确保发生什么would发生的事情与发生的事情一样does发生。实现这一目标的方法有很多,但没有一种是完美的。
实现你想要的
一旦你有defined你想要什么,你仍然必须achieve它。这对于 GitHub 来说相对简单。 (我不确定您会为 Bitbucket 或其他系统做什么。)您将首先使用git fetch
在您自己的存储库克隆中获取提交的副本C
任何父母都会承诺你所缺乏的。
我们假设origin
是 GitHub 存储库的名称,拉取请求是 #123。然后:
git fetch origin # update everything so that we have origin/B pointing to A, etc
git fetch origin refs/pull/123/head:refs/heads/pr123
您现在有一个名为pr123
谁的提示提交是提交C
,以及您的远程跟踪名称origin/B
指向提交A
.
现在很容易判断是否A
是的祖先C
,即最简单的情况是否有效。这个特定的测试是在 bash 中以这种方式完成的(不确定 PowerShell):
if git merge-base --is-ancestor refs/remotes/origin/B refs/heads/pr123; then
echo "it's the simple case, yay"
else
echo "it's the merge case, boo"
fi
In the simple case, regardless of which method you use to accept the pull request, the contents of final commit to which origin/B
points in the end will match the contents of commit C
, as pointed-to right now by refs/heads/pr123
.2 If you've decided that the change you want to test is the overall summary change—that is, from A
to C
, without looking at any intermediate commits—then the files that will be modified are those listed by:
git diff-tree -r --name-status [options] refs/remotes/origin/B refs/heads/pr123
但如果您还想检查中间提交,则需要做更多工作。
如果你在一个merge在这种情况下,事情会变得更加复杂,因为即使您只是将最终结果与单个提交进行比较,您现在也必须决定是否与提交进行比较A
, O
,或者完全是其他东西,例如实际的当前合并基础A
and C
,无论提交是什么。
If you do想要使用实际的合并基础,这就是三点语法有用的地方。然而,由于旧版本的 Git 中存在小错误,我建议运行git merge-base --all refs/heads/B refs/heads/pr123
在这里并收集其输出。这将列出一些哈希 ID,最好只有一个:如果您只得到一个,那就是合并基础提交。如果你得到不止一个,他们是all候选合并碱基,以及默认值git merge -s recursive
策略将首先合并所有合并基础,然后使用生成的提交作为合并的合并基础。
(通常最明智的做法,或者至少是最简单的做法,就是拒绝复杂的拉取请求,告诉作者这些请求they必须重新设计他们的拉取请求,这样它就不需要任何特殊/奇特的合并。)
2Typically, if you have remote-tracking name origin/B
and branch-name pr123
pointing to the desired commits, you can just use those names. However, the method that Git uses to turn a name into a hash ID is a six-step process, outlined in the gitrevisions documentation https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html, and "use as branch name" is way down at step four, after "use as tag name"; "use as remote-tracking" name is step five. What this means in practice is that if you have both branch pr123
and tag pr123
, the name pr123
refers to the tag, rather than the branch. For scripts, which will run with no human monitoring warning messages, it's wise to spell out the full names: refs/remotes/origin/B
and refs/heads/pr123
. You can even resolve them once, to hash IDs, and then use the hash IDs everywhere, since the hash IDs never change.