我不太清楚您具体需要什么,但请注意:
- Commits 存储快照。这意味着提交(commit)K 有一个完整的源代码树,独立于提交A、B、C等。同样,提交J 也有一个完整的快照,独立于提交A、K或任何其他提交。(这里的“独立”是指如果您要求Git检索提交J,则修改提交K后创建J不会有任何影响。虽然像git commit --amend似乎可以更改提交,但实际上不行。)
- 在两个特定提交上使用 git diff 命令,Git 提取每个快照并进行比较。
因此,git diff K M将显示当前master分支的最新提交(commit K)和dev分支的最新提交(commit M)之间的差异。
您还可以只输入git diff master dev。
这可能是您想要看到的内容(再次强调,我不确定)。因此,答案1: git diff master dev。
另一方面,您可能想要显示一个提交B的差异,一个提交D的差异,一个提交H的差异,一个提交L的差异以及一个提交M的差异。也就是说,您想单独查看每个非合并提交与其(单个)父提交相比较的结果。
您可以执行git diff A B,git diff B D等操作。但是,您还可以执行git show B,git show D等操作。这将显示提交作为更改,而不是作为快照。
如果提交存储快照而不是更改,您可能会想知道这是如何实现的(因为大多数其他版本控制系统实际上都存储更改)。这个看似矛盾的问题的答案在于git show查找的是您绘制的相同的图形。
再看一下提交
B
。 它前面有哪个提交?也就是说,它左侧是哪个提交,在向左移动行?提交
B
只有一个可能的祖先,那就是它的父提交
A
。因此,执行命令
git show B
:
- 提取提交
A
的快照,然后
- 提取提交
B
的快照,然后
- 比较这两个快照。
同样,提交
M
只有一个直接祖先(父),那就是提交
L
。 所以,执行命令
git show M
:
- 提取提交
L
的快照,然后
- 提取提交
M
的快照,然后
- 比较这两个快照。
如果这是你想要的,那么有趣的问题变成了:如何找到每个提交ID的
B
,
D
,
H
,
L
和
M
序列? 这个答案有点复杂,但关键的命令是
git rev-list
,它本质上与
git log
相同。这些命令(
git log
和
git rev-list
)都可以
遍历提交图。也就是说,你选择一些起始点 - 在这种情况下,是开发分支的
M
提交点,并告诉Git向后遍历提交,查看每个提交的父级。
问题在于,当你遇到合并提交时,例如提交
J
,Git将向后遍历其
所有 父级。你想要限制Git仅查找在进行合并提交时是分支
dev
的最新提交。Git有一个标志用于此,拼写为
--first-parent
。这会告诉
git rev-list
仅跟随每个合并提交的
第一个父提交。
我们还想跳过合并提交,所以我们可以添加
--no-merges
(这不影响向后遍历过程,只是限制打印的修订ID,以排除合并)。
这就导致了
答案2a:git rev-list --first-parent --no-merges ^A dev
(稍后我们将讨论“非 A”部分)。
现在,如果要使用
git rev-list
来实现这个功能会有些困难,因为我们需要获取每一个提交的ID并运行
git show
命令。然而,有一种更加简单的方法,因为
git rev-list
和git log
本质上是相同的命令,而
git log -p
会将每个提交作为
补丁显示出来,完成了与
git show
几乎相同的操作。
这导致了
git log -p --first-parent --no-merges ^A dev
这个答案2b。这里不一定需要使用
--no-merges
参数,请参见下文。
合并提交和组合差异
git show
所做的特殊处理是,相比于
git diff
,它能够处理显示一个
合并提交的情况,例如提交
J
。提交
J
有
两个父提交,即提交
H
和提交
K
(顺便提一下,这意味着您在进行提交
J
之前已经进行了提交
K
:-))。如果您运行
git diff H J
,Git将提取提交
H
和提交
J
的快照,并进行比较。如果您运行
git diff K J
,Git将提取提交
K
和提交
J
的快照,并进行比较。但是,如果您运行
git show J
,Git将:
- 提取提交
H
的快照,然后
- 提取提交
K
的快照,然后
- 提取提交
J
的快照(即合并提交),最后
- 生成Git称之为组合差异的内容。
步骤4中的组合差异试图以紧凑的方式显示从提交
H
和提交
K
到提交
J
的更改。在压缩变更时,Git会
丢弃任何文件,在这些��件中,
H
或
K
版本与
J
版本匹配。
也就是说,假设文件
README.txt
从
H
变为
J
。但是,假设
README.txt
在
K
和
J
中是相同的。换句话说,在进行
git merge
时,您正在获取来自
K
的更改以制作
J
,而且来自合并另一侧对于
README.txt
没有任何更改。这意味着
README.txt
完全匹配一个“传入方”,因此组合差异
完全忽略了该文件。
这意味着,即使合并捕获了一些更改,组合差异通常也根本不显示任何内容。要查看这些更改,您必须进行
两个差异,而不仅仅是一个。您必须从
H
到
J
进行一次差异,然后再从
K
到
J
进行另一次差异,而不能依赖组合差异。
使用
git log -p
时,还可以通过将
-c
或
--cc
添加到选项中,查看合并的组合差异。但是,如果您没有要求这样做,Git实际上做的是相当简单的:
它根本不显示差异。
因此,这导致了
答案2c: git log -p --first-parent ^A dev
。我们只是删除了
--no-merges
:现在我们将看到每个合并的日志消息,但不会看到差异。
这个
^A
是什么?
这也与您的另一个问题有关:
“顺便说一下,如果有人知道一种快速找到A而不需要不断向下滚动日志的方法,我会很感激。”
答案是为提交
A
创建一个
符号名称。找到其ID后,选择一个名称,如
dev
或
master
(但不要使用这些,因为它们正在使用中)。
那就是
dev
和
master
的全部内容:
它们只是提交的符号名称,再加上作为分支名称的附加属性,您可以git checkout
符号名称并最终“在”分支上。您也可以给
A
指定分支名称。您需要确保不要
git checkout
此分支并在其中进行提交,因为如果您这样做,您将会增加一个新分支,而不仅仅是让分支名称指向提交
A
。
或者,您可以创建一个指向提交 A
的标签名称。这几乎与分支名称完全相同。两个区别是:
- 它是一个标签名称:您无法将其作为分支检出,因此无法意外更改它。
- 它是一个标签名称:如果您运行
git push --tags
命令,它将作为标签发送到上游,然后其他人也会拥有它。
在这种情况下,点1对您有利,点2可能不利,因此取决于您是否认为优势(无法意外更改它)值得风险(可能会意外发布它)。
如果您拥有 A
的 ID,则可以执行以下操作:
$ git tag A <id-of-commit-A>
现在你有一个名为
A
的
名称。(你可以稍后使用
git tag -d A
删除它,但如果你不小心发布了它,你可能会继续从上游获取它。)
回到
git log
命令中
^A
字符串的问题上,所有
^A
所做的就是告诉
git rev-list
(因此也告诉
git log
)在到达提交
A
时停止遍历提交图。该提交也不会显示出来(
git rev-list
不打印,
git log
不在日志输出中显示)。前缀符号
^
代表"非",即"获取所有从
dev
可达的提交,但
不可达从
A
可达的提交"。添加
--first-parent
使Git只遍历每个合并的
第一个父级,这样我们就不会进入从
master
合并的提交。
(
^A dev
语法也可以拼写成
A..dev
。请注意,这适用于
git log
和
git rev-list
,但对于
git diff
意义完全不同。)