Edit:我已经用本地命令和 GitHub 的实际输出对此进行了增强,现在我无法解释他们的拉取请求比较中显示的内容at all。这完全是无稽之谈,就像他们在网页上显示的许多其他内容一样。 GitHub 显示显示的提交是已经存在于目标分支中,就好像合并将进行实际上不会进行的更改一样。也就是说,对于“将分支 'a' 合并到 b”,唯一的提交将是added是那些尚未包含在“b”中的提交,但 GitHub 的显示显示了两个已包含在“b”中的提交。
TL;DR
通常(但请参阅长答案):
git diff $base...$head
where $base
and $head
来自下面的长答案。Edit: 嗯,他们就是这样的should展示。事实证明这不是他们想要的actually show.
使用您的示例存储库
您设置了一个存储库https://github.com/kenocrates/ex1
,我克隆的:
$ git clone https://github.com/kenocrates/ex1
...
$ cd ex1
为了获得refs/pull
然后,您修改了配置,使其部分内容如下:
[remote "origin"]
url = https://github.com/kenocrates/ex1
fetch = +refs/heads/*:refs/remotes/origin/*
fetch = +refs/pull/*/head:refs/remotes/origin/pr/*
我也这么做了。然后:
$ git fetch origin
From https://github.com/kenocrates/ex1
* [new ref] refs/pull/1/head -> origin/pr/1
我现在使用的命令来查找正确的合并基础或基础,是:
$ git merge-base --all refs/remotes/origin/pr/1 refs/remotes/origin/b
587593749ee46806ed2c9fd06cf8b904bbce255a
since my通过合并拉取请求而合并的提交的全名是refs/remotes/origin/pr/1
, and myGitHub 所谓的“基础分支”的全称是refs/remotes/origin/b
。请注意,我们还可以使用原始哈希 ID 或更短的名称:
$ git rev-parse origin/pr/1
1cef243f9efe6e94c9926f7992efb6c093188b8c
$ git rev-parse origin/b
48728bc19480e0c1cc9e3a399634a5f389881c47
(离开了refs/remotes/
,其中 - 如中所述gitrevisions 文档 https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html,将由 Git 通常用来解析名称的六步过程中的第五步假设)。
$ git merge-base --all 1cef243f9efe6e94c9926f7992efb6c093188b8c 48728bc19480e0c1cc9e3a399634a5f389881c47
587593749ee46806ed2c9fd06cf8b904bbce255a
因此,可以通过以下方式获得正确的差异(完整的,而不是一次提交一个):
$ git diff 587593749ee46806ed2c9fd06cf8b904bbce255a origin/b
或更短的:
$ git diff origin/pr/1...origin/b
其中任一输出:
diff --git a/file b/file
index 4fc2681..186222c 100644
--- a/file
+++ b/file
@@ -1,6 +1,7 @@
Section A
Section B
+line 1
Section C
line 1
这就是 GitHub 上的内容should显示如果我们接受拉取请求,它将被合并的内容以及它将产生的效果。他们实际展示的内容是不同的。 Per https://github.com/kenocrates/ex1/pull/1/commits https://github.com/kenocrates/ex1/pull/1/commits,我们看到的是四个提交,其中两个已经包含在目标分支中:
1cef243f9efe6e94c9926f7992efb6c093188b8c
587593749ee46806ed2c9fd06cf8b904bbce255a
23c2ff68c02207a2f172090566d7b2c75b6f1c16
b21d3c4067261aa295319f177ad1629b5ae12818
这是 GitHub 上存储库中的实际内容,包括拉取请求(尽管不是合并),由于我们正在使用,因此通常会进行名称更改our存储库的副本,我们还没有将我们的存储库设置为纯粹的镜像。 (制作纯镜像将使我们自己的本地存储库无法执行任何操作work in.)
$ git log --all --decorate --oneline --graph
* 48728bc (origin/b) Merge branch 'm' into b
|\
* | f365142 added line to section B
| | * 1cef243 (HEAD -> a, origin/pr/1, origin/a, origin/HEAD) Merge branch 'm' into a
| | |\
| | |/
| |/|
| * | 5875937 (origin/m) Merge branch 'p' into m
| |\ \
|/ / /
| * | 23c2ff6 added line to section C
|/ /
| * b21d3c4 added line to section A
|/
* 1ff1a3c added file
该图非常混乱且难以阅读,但是,如果我们要实际执行此提议的合并,这些是我们通过另一条腿添加的提交,这些提交尚未启用origin/a
:
$ git log --decorate --oneline --graph $(git merge-base origin/pr/1 origin/b)..origin/b
* 48728bc (origin/b) Merge branch 'm' into b
* f365142 added line to section B
这就是为什么我们看到我们所看到的local差异。由于某种原因,GitHub 选择的基础不正确。
让我们再尝试一个实验——获取 GitHub 所做的实际合并提交,看看它的样子git log --all --decorate --graph --oneline
:
$ git config --add remote.origin.fetch '+refs/pull/*/merge:refs/remotes/origin/pr-merge/*'
$ git fetch
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/kenocrates/ex1
* [new ref] refs/pull/1/merge -> origin/pr-merge/1
$ git log --all --decorate --graph --oneline
* f53dc81 (origin/pr-merge/1) Merge 1cef243f9efe6e94c9926f7992efb6c093188b8c into 48728bc19480e0c1cc9e3a399634a5f389881c47
|\
| * 1cef243 (HEAD -> a, origin/pr/1, origin/a, origin/HEAD) Merge branch 'm' into a
| |\
| * | b21d3c4 added line to section A
* | | 48728bc (origin/b) Merge branch 'm' into b
|\ \ \
| | |/
| |/|
| * | 5875937 (origin/m) Merge branch 'p' into m
| |\ \
| | |/
| |/|
| | * 23c2ff6 added line to section C
| |/
* | f365142 added line to section B
|/
* 1ff1a3c added file
这正是我们所期望的:一个合并提交,其第一个父级origin/pr-merge/1^1
is 48728bc19480e0c1cc9e3a399634a5f389881c47
aka origin/b
,以及其第二个父母origin/pr-merge/1^2
is 1cef243f9efe6e94c9926f7992efb6c093188b8c
aka HEAD
, a
, origin/pr/1
, origin/a
, and origin/HEAD
.
Long
答案 [edit:除非这不是 GitHub 实际使用的答案] 嵌入在短语中:
“头部分支的尖端与头部上次与基础分支同步的提交之间的比较”
为了理解(并重现)这一点,我们必须定义每个术语。
The 树枝的尖端(名称)只是分支名称解析到的提交。例如,查找分支尖端的哈希IDmaster
你可以运行:
git rev-parse master
一般来说,对于大多数命令,使用分支名称与使用此分支提示哈希 ID 具有相同的效果,因此我们甚至不必费心git rev-parse
这里。 (Git 中此规则有一些例外:有时名称不仅仅意味着原始哈希 ID。)
接下来我们有头支 and 基础分支。在这里,定义这些术语的是 GitHub,而不是 Git。这总行在这种情况下,是您正在执行拉取请求的分支,而基础是您所说的“进入”的分支:请将feature-X拉入master表示 head = feature-X 且 base = master;所以
如果 PR 是将分支 a 拉入分支 b...
那么“头”是a
和“基地”是b
,所以你可以做head=a
and base=b
并使用$head
and $base
以下。 (对于偏执狂,请获取适当的参考并使用refs/heads/$N/head
或其哈希 ID,如下所述。)
最后,我们有这样一句话...上次同步的提交...。该短语部分由 GitHub 定义,因为 Git 不使用该短语,但它的真正含义是作为这两个提交的合并基础的(理想情况下单个)提交。合并基础是通过提交图定义的,因此您需要足够的图才能找到它。整个图总是足够的,因此如果您有拉取请求提交,其哈希值存储在名为的引用中refs/pull/N/head
- 以及基本分支哈希,存储在refs/heads/base
-跑步:
git merge-base --all refs/pull/$N/head refs/heads/$base
或者更简单地说:
git merge-base --all $head $base
将生成合并基础的哈希值。
(如果有多个,Git 中的实际合并基础将通过合并合并基础来创建,在您运行时git merge -s recursive
。注意-s recursive
是默认策略。 GitHub 是否也这样做,在什么条件下,我不知道。请注意,依赖于$base
决心refs/heads/$base
是相当可靠的,但如果你使用的话就会失火same标签和分支的名称!)
因此,差异来自合并基础——上面的哈希输出git merge-base
命令——GitHub 称之为“头分支”。你可以运行git merge-base
命令并验证它是否恰好生成一个散列:
hash=$(git merge-base $head $base) # slightly sloppy
... check that $hash is just one hash ...
git diff $hash $head
or rely on the special git diff
syntax using three dots: git diff A...B
means:1 find the merge base(s) between A and B; pick one at random, and run git diff
from that one to B.
这就是上面的简短版本的来源。如上所述,如果您有一个使用相同名称的分支和标签,$base
or $head
可能会错误地解析为标签而不是分支,因此如果您很偏执,请拼写出完整的引用。
1There's a long-standing but minor bug in git diff
with three-dot merge syntax, when there are multiple merge bases. Multiple merge bases are pretty rare and you may not care.