如何在从主分支合并后查找分支上的更改?

9

我从 master 分支派生了一个分支。偶尔我会将 master 合并到我的分支中以获取更新。

       /--B--D--G--H--J--L--M--> dev (HEAD)
      /        /     /
-----A--C--E--F--I--K---> master

如何只显示我的分支上的更改,排除合并所带来的更改?即只显示提交BDHLM的差异。 git diff A不起作用,因为它包括了从master合并来的更改。
顺便说一句,如果有人知道一种快速找到A的方法而不必不断向下滚动日志,我将非常感激。
1个回答

7
我不太清楚您具体需要什么,但请注意:
- 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:
  1. 提取提交 A 的快照,然后
  2. 提取提交 B 的快照,然后
  3. 比较这两个快照。
同样,提交 M 只有一个直接祖先(父),那就是提交 L。 所以,执行命令 git show M:
  1. 提取提交 L 的快照,然后
  2. 提取提交 M 的快照,然后
  3. 比较这两个快照。
如果这是你想要的,那么有趣的问题变成了:如何找到每个提交ID的 B, D, H, LM 序列? 这个答案有点复杂,但关键的命令是git rev-list,它本质上与git log相同。这些命令(git loggit 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-listgit 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将:
  1. 提取提交H的快照,然后
  2. 提取提交K的快照,然后
  3. 提取提交J的快照(即合并提交),最后
  4. 生成Git称之为组合差异的内容。
步骤4中的组合差异试图以紧凑的方式显示从提交H和提交K到提交J的更改。在压缩变更时,Git会丢弃任何文件,在这些��件中,HK版本与J版本匹配。
也就是说,假设文件README.txtH变为J。但是,假设README.txtKJ中是相同的。换句话说,在进行git merge时,您正在获取来自K的更改以制作J,而且来自合并另一侧对于README.txt没有任何更改。这意味着README.txt完全匹配一个“传入方”,因此组合差异完全忽略了该文件
这意味着,即使合并捕获了一些更改,组合差异通常也根本不显示任何内容。要查看这些更改,您必须进行两个差异,而不仅仅是一个。您必须从HJ进行一次差异,然后再从KJ进行另一次差异,而不能依赖组合差异。
使用git log -p时,还可以通过将-c--cc添加到选项中,查看合并的组合差异。但是,如果您没有要求这样做,Git实际上做的是相当简单的:它根本不显示差异
因此,这导致了答案2c: git log -p --first-parent ^A dev。我们只是删除了--no-merges:现在我们将看到每个合并的日志消息,但不会看到差异。
这个^A是什么?
这也与您的另一个问题有关:
“顺便说一下,如果有人知道一种快速找到A而不需要不断向下滚动日志的方法,我会很感激。”
答案是为提交A创建一个符号名称。找到其ID后,选择一个名称,如devmaster(但不要使用这些,因为它们正在使用中)。
那就是devmaster的全部内容:它们只是提交的符号名称,再加上作为分支名称的附加属性,您可以git checkout符号名称并最终“在”分支上。您也可以给A指定分支名称。您需要确保不要git checkout此分支并在其中进行提交,因为如果您这样做,您将会增加一个新分支,而不仅仅是让分支名称指向提交A

或者,您可以创建一个指向提交 A 的标签名称。这几乎与分支名称完全相同。两个区别是:

  1. 它是一个标签名称:您无法将其作为分支检出,因此无法意外更改它。
  2. 它是一个标签名称:如果您运行 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 loggit rev-list,但对于git diff意义完全不同。)

我误解了'diff'的工作方式,并假设'git diff master'只会返回L和M。感谢您提供了详细的解释并涵盖了我问题可能涉及的所有情况。这些都很有用。 - kc2
所以实际上我不再需要第二部分了。使用标签即可。顺便提一下,我想知道是否有类似于“git branch-origin dev”的命令可以给我提交A... - kc2
1
@kc2: Git不存储分支的起点。(我认为Git开发人员的主要原因是坚信试图这样做意味着您错误地使用了Git,其次是因为技术上很困难,但也许反过来说。 :-) 分支名称的reflog会存储创建事件,直到该reflog条目过期,因此如果创建事件仍然存在,则可以从中获取哈希值。) - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接