如何在Git中寻找分支点?

584

我有一个包含主分支(master)和A分支的仓库,并且两个分支之间有很多合并活动。如何在我的仓库中找到基于主分支(master)创建分支A时的提交(commit)?

我的仓库基本上是这样的:

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)
我正在寻找修订版 A,但这不是 git merge-base (--all) 命令所能找到的。

1
使用 Git 2.36 (Q2 2022):git rev-parse $(git rev-list --exclude-first-parent-only ^main branch_A| tail -1)^。请参见下面的答案 - VonC
27个回答

628

我正在寻找同样的东西,然后我发现了这个问题。感谢你提出这个问题!

然而,我发现这里的答案似乎并没有完全回答你所问的(或者我在寻找的),它们似乎给出了 G 提交,而不是 A 提交。

因此,我创建了以下树形结构(字母按时间顺序分配),以便我可以进行测试:

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

这看起来与你的有些不同,因为我想确保获得B(指的是此图表,而不是你的),但不要A(也不要D或E)。以下是附加到SHA前缀和提交消息的字母(如果对任何人有兴趣,可以从此处克隆我的存储库):
G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

所以,目标:找到B。经过一番摸索,我找到了三种方法:


1. 使用gitk进行可视化操作:

你应该可以在可视化界面上看到一个树形结构(从主分支(master)的视角下):

gitk screen capture from master

或者从主题视角来看:

gitk screen capture from topic

在这两种情况下,我选择了图表中的提交B。一旦你点击它,它的完整SHA将显示在图表下方的文本输入框中。


2. 从终端以可视化方式查看:

git log --graph --oneline --all

(编辑/附注: 添加 --decorate 可能也很有趣; 它会添加分支名称、标签等指示。由于下面的输出未反映其使用,因此不将其添加到上面的命令行中。)

假设 git config --global color.ui auto,则显示如下:

output of git log --graph --oneline --all

或者,以纯文本形式:

*   a9546a2 从主题合并到主分支
|\  
| *   648ca35 将主分支合并到主题中
| |\  
| * | 132ee2a 主题分支上的第一次提交
* | | e7c863d 在将主分支合并到主题后在主分支上进行了提交
| |/  
|/|   
* | 37ad159 在主分支上提交了分支后提交
|/  
* 6aafd7f 在分支之前在主分支上进行了第二次提交
* 4112403 在主分支上进行了初始提交
在任何情况下,我们都将6aafd7f提交视为最低公共点,即我的图中的B,或者你的图中的A


3. 使用Shell魔法:

您在问题中没有明确指定是否需要像上面那样的内容,还是只需要一个命令就可以获取一个修订版本,无其他内容。好吧,下面是后者:

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

你也可以将其放入你的~/.gitconfig中(注意:末尾的破折号很重要;感谢Brian提醒)

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

可以通过以下(引用混乱的)命令行完成:

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

注意:`zsh` 可以很容易地替换为 `bash`,但是 `sh` 不行——在基础的 `sh` 中不存在 `<()` 语法。(再次感谢 @conny 在本页面另一个回答中的评论提醒我!)
注:以上内容的另一版本由 liori 提供,指出 当比较相同分支时可能会出现问题,并提供了一种替代的 diff 形式,将 sed 表单从混合物中删除,并使其“更安全”(即使您将主分支与主分支进行比较,它也会返回结果(即最近的提交)):
作为 .git-config 行:
[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

从 shell 中:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

所以,在我的测试树中(由于一段时间不可用,抱歉;现在已恢复),它现在可以在主分支和专题分支上运行(分别给出提交G和B)。再次感谢liori提供的替代形式。


所以,这就是我(和liori)想出来的。对我来说似乎很有效。它还允许使用一些可能会派上用场的额外别名:

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

愉快的Git使用!


5
谢谢lindes,这个shell选项非常适用于想要查找持续时间较长的维护分支的分支点的情况。当你正在寻找一个可能在过去一千次提交之前的修订时,视觉选项真的无法胜任。*8') - Mark Booth
4
在你的第三种方法中,你依赖于上下文将显示第一行未更改。在某些特殊情况下或者如果你有稍微不同的要求(例如,我只需要一个历史记录是--first-parent,并且我在脚本中使用此方法可能有时会在两侧使用相同的分支),这种情况将不会发生。我发现使用diff的if-then-else模式并从其输出中删除已更改/已删除的行比指望有足够大的上下文更安全,方法是:diff --old-line-format='' --new-line-format='' <(git rev-list …) <(git rev-list …)|head -1 - liori
6
我认为 git log -n1 --format=format:%H $(git log --reverse --format=format:%H master..topic | head -1)~ 也可以正常工作。 - Tobias Schulte
5
@JakubNarębski:你有没有办法让 git merge-base --fork-point... 命令返回对于这个代码树来说的提交 B(提交 6aafd7f)?当你发布这条消息时,我正忙于其他事情,但它听起来不错。不过最近我试了一下,却无法让它工作……我要么得到了更近期的结果,要么就是默默失败(没有错误消息,但退出状态码为 1),我尝试了像 git co master; git merge-base --fork-point topicgit co topic; git merge-base --fork-point mastergit merge-base --fork-point topic master(针对任意一个分支的检出),等等这样的参数。我是做错了什么或者漏了什么吗? - lindes
27
--fork-point 命令基于 reflog,因此只适用于您在本地进行更改的情况。即使是在这种情况下,reflog 的条目也可能已过期。它虽然有用,但并不可靠。 - Daniel Lubarov
显示剩余29条评论

160

你可能正在寻找git merge-base:

git merge-base用于查找两个提交之间最佳的共同祖先,以在三方合并中使用。 如果后一个共同祖先是前一个祖先的祖先,则一个共同祖先比另一个共同祖先更好。 如果没有更好的共同祖先,则不具有任何更好共同祖先的共同祖先是最佳共同祖先,即合并基础。 请注意,一对提交可能有多个合并基础。


10
请注意 "git merge-base" 命令的 --all 选项。将其翻译为:请注意 "git merge-base" 命令中的 --all 选项。 - Jakub Narębski
15
这并没有回答原问题,但是大多数人提出的是更简单的问题,而这个答案正是适用于那个问题的答案 :) - FelipeC
10
他说他不想要git merge-base的结果。 - Tom Tanner
15
@TomTanner:我刚刚查看了问题历史记录,原始问题在我的答案发布五个小时后被编辑以包含有关"git merge-base"的说明(可能是为了回应我的答案)。尽管如此,我仍将保留这个答案,因为它可能对通过搜索找到这个问题的其他人仍然有用。 - Greg Hewgill
1
@sourcedelica - 你把你有用的建议发在了错误的答案中。你想要这个。谢谢! - stason
显示剩余2条评论

46

我用过 git rev-list 来进行这种事情。例如,(请注意3个点

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

将输出分支点。现在,它并不完美;因为您已经将主分支合并到A分支多次,所以这将导出一些可能的分支点(基本上是原始分支点和每个合并主分支到A分支的点)。但是,至少可以缩小可能性范围。

我已将该命令添加到我的别名中,在~/.gitconfig中:

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

所以我可以这样调用它:

$ git diverges branch-a master

注意:这似乎给出了分支上的第一个提交,而不是共同祖先。(即它给出了 G 而不是原始问题中的 A,根据图表。)我有一个可以得到 A 的答案,我很快就会发布。 - lindes
是的。在我的回答中(其中包含一个可以克隆的存储库;使用topic替换branch-a,然后运行此命令),它列出了648ca357b946939654da12aaf2dc072763f3caee37ad15952db5c065679d7fc31838369712f0b338 - 37ad159648ca35都在当前分支的祖先中(后者是topic的当前HEAD),但两者都不是分支发生之前的点。你得到了不同的结果吗? - lindes
@lindes:我无法克隆您的存储库(可能是权限问题?)。 - mipadi
抱歉!感谢您让我知道。我忘记运行git update-server-info了。现在应该没问题了。 :) - lindes
它不起作用。我尝试了test repository,并且--boundary branch_A...master甚至没有显示正确的提交,所以无论如何都不能解决这个问题=/ - FelipeC
显示剩余4条评论

37
如果您喜欢简洁的命令,
git rev-list $(git rev-list --first-parent ^branch_name master | tail -n1)^^! 
以下是解释。
下面的命令给出了所有在branch_name创建后在master中发生的提交列表
git rev-list --first-parent ^branch_name master 
由于您只关心其中最早的提交,因此您想要输出的是输出的最后一行:
git rev-list ^branch_name --first-parent master | tail -n1
最早的不是"branch_name"祖先的提交的父级别定义为在"branch_name"内,并且在"master"中,因为它是在"master" 中某些内容的祖先。因此,您已经获得了在两个分支中都存在的最早提交。
命令
git rev-list commit^^!
只是一种显示父提交引用的方法。您可以使用
git log -1 commit^
或其他任何方式。
PS:我不同意祖先顺序无关紧要的观点。这取决于你需要什么。例如,在这种情况下
_C1___C2_______ master
  \    \_XXXXX_ branch A (the Xs denote arbitrary cross-overs between master and A)
   \_____/ branch B
以C2作为“分支”提交完全是有道理的。这是开发人员从“master”分支分离出来的时候。当他分支时,分支“B”甚至没有合并到他的分支中!这就是本帖子中解决方案提供的内容。
如果您想要最后一个提交C,使得从起源到分支“A”上的最后一个提交的所有路径都通过C,则您需要忽略祖先顺序。这纯粹是拓扑问题,并让您了解自有两个代码版本同时运行以来的情况。那是你会采取基于merge-base的方法,它将在我的示例中返回C1。

4
这是迄今为止最简洁的答案,请投票支持。建议修改为:git rev-list commit^^! 可以简化为 git rev-parse commit^ - Russell Davis
这应该是答案! - trinth
2
这个答案很好,我只是用git rev-list --first-parent branch_name ^master替换了git rev-list --first-parent ^branch_name master。因为如果主分支比其他分支少0个提交(可以快进到它),则不会创建任何输出。使用我的解决方案,如果主分支严格领先(即已完全合并该分支),则不会创建任何输出,这正是我想要的。 - Michael Schmeißer
3
除非我完全漏掉了什么,否则这不会起作用。在示例分支中,两个方向上都有合并。听起来你试图考虑到这一点,但我认为这会导致你的答案失败。git rev-list --first-parent ^topic master 只会将你带回到最后一次从 master 合并到 topic 的最后一个提交之后的第一个提交(如果那个存在的话)。 - jerry
1
@jerry 你是正确的,这个答案是垃圾;例如,在刚进行了一次回溯合并(将主干合并到话题分支)且此后主干没有新提交的情况下,第一个 git rev-list --first-parent 命令完全不会输出任何内容。 - TamaMcGlinn

28

目的:本回答旨在测试该主题中提供的各种答案。

测试存储库

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)
$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

正确的解决方案

唯一可行的解决方案是由lindes提供的,它会正确地返回A

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

正如Charles Bailey所指出的,这种解决方案非常脆弱。

如果你将branch_A合并到master,然后再将master合并回branch_A,而没有中间提交,那么lindes的解决方案只会给出最近的第一个分歧点。

这意味着对于我的工作流程,我认为我必须坚持标记长期运行分支的分支点,因为我不能保证它们以后能够可靠地被找到。

这实际上归结为git缺乏hg所称的“命名分支”。博客作者jhw在他的文章Why I Like Mercurial More Than Git和他的后续文章More On Mercurial vs. Git (with Graphs!)中将其称为“系谱”和“家族”。我建议人们阅读这些文章,看看为什么一些Mercurial转换者在git中错过了没有命名分支

错误的解决方案

mipadi提供的解决方案返回两个答案:IC

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

Greg Hewgill提供的解决方案返回I

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

Karl提供的解决方案返回X:

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

测试库复制

创建一个测试库的步骤:

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

我的唯一补充是标签,它明确了我们创建分支的时间点和我们希望找到的提交。

我怀疑Git版本对此没有太大影响,但是:

$ git --version
git version 1.7.1

感谢Charles Bailey向我展示了一种更简洁的脚本示例库的方法。


2
Karl的解决方案很容易修复:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1。感谢提供创建repo的指导 :) - FelipeC
我想你的意思是“Karl提供的经过清理的解决方案返回X”。原始版本也可以正常工作,只是有点丑陋 :-) - Karl
不好意思,您的原始版本不能正常工作。虽然变体的效果更糟糕,但添加选项 --topo-order 可以使您的版本工作 :) - FelipeC
@felipec - 请看我在 Charles Bailey的答案中的最后一条评论。不幸的是,我们的 chat(以及所有旧评论)现在已被删除。我会尽快更新我的答案。 - Mark Booth
有趣。我有点以为拓扑是默认的。我真傻 :-) - Karl
显示剩余2条评论

13

一般来说,这是不可能的。在一个分支历史中,一个命名分支被分离出来之前的分支和合并,以及两个命名分支之间的中间分支看起来相同。

在git中,分支只是历史记录部分的末端的当前名称,它们没有真正的强标识。

通常这不是一个大问题,因为两个提交的合并基(参见Greg Hewgill的答案)通常更有用,给出了两个分支共享的最近提交。

依赖提交的父操作顺序的解决方案显然在分支的历史中已经完全集成的情况下不起作用。

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

如果使用父分支相反的方向进行集成合并(例如,使用临时分支将测试合并到主分支中,然后将其快进到功能分支以进一步构建),则此技术也会失败。

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"

让我们在聊天室里继续这个讨论:http://chat.stackoverflow.com/rooms/9641/discussion-between-mark-booth-and-charles-bailey。 - Mark Booth
4
谢谢 Charles,你已经说服我了,如果我想知道分支的���初分叉点,我将不得不对其进行标记。我真希望 git 有类似于 hg 命名分支的功能,这将使维护长期分支变得更加容易 - Mark Booth
2
在Git中,分支只是历史记录部分末端的当前名称。它们并没有真正的强身份。这是一个令人担忧的说法,让我相信我需要更好地理解Git分支 - 谢谢(+1)。 - dumbledad
在分支历史中,在命名分支被创建之前进行了一次分支和合并,并且两个命名分支的中间分支看起来相同。没错。+1。 - user1071847
是的,Git并不认为这很重要。它让我继续使用Hg,因为这是软件开发历史中非常关键的一部分。 - O'Rooney

7

Git 2.36 提出了一个更简单的命令:

(branch_A_tag)
     |
--X--A--B--C--D--F  (master) 
      \   / \   /
       \ /   \ /
        G--H--I--J  (branch A)

vonc@vclp MINGW64 ~/git/tests/branchOrigin (branch_A)
git log -1 --decorate --oneline \
  $(git rev-parse \
     $(git rev-list --exclude-first-parent-only ^main branch_A| tail -1)^ \
   )
 80e8436 (tag: branch_A_tag) A - Work in branch main
  • git rev-list --exclude-first-parent-only ^main branch_A 会给你 J -- I -- H -- G
  • tail -1 会给你 G
  • git rev-parse G^ 会给你它的第一个父节点: A 或者 branch_A_tag

使用测试脚本:

mkdir branchOrigin
cd branchOrigin
git init
git commit --allow-empty -m "X - Work in branch main"
git commit --allow-empty -m "A - Work in branch main"
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch main"
git switch -c branch_A branch_A_tag
git commit --allow-empty -m "G - Work in branch_A"
git switch main
git merge branch_A       -m "C - Merge branch_A into branch main"
git switch branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge main         -m "I - Merge main into branch_A"
git switch main
git commit --allow-empty -m "D - Work in branch main"
git merge branch_A       -m "F - Merge branch_A into branch main"
git switch branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

这将给你:

vonc@vclp MINGW64 ~/git/tests/branchOrigin (branch_A)
$ git log --oneline --decorate --graph --branches --all
* a55a87e (HEAD -> branch_A) J - Work in branch_A branch
| *   3769cc8 (main) F - Merge branch_A into branch main
| |\
| |/
|/|
* |   1b29fa5 I - Merge main into branch_A
|\ \
* | | e7accbd H - Work in branch_A
| | * 87a62f4 D - Work in branch main
| |/
| *   7bc79c5 C - Merge branch_A into branch main
| |\
| |/
|/|
* | 0f28c9f G - Work in branch_A
| * e897627 B - Work in branch main
|/
* 80e8436 (tag: branch_A_tag) A - Work in branch main
* 5cad19b X - Work in branch main

这是什么:

(branch_A_tag)
     |
--X--A--B--C--D--F  (master) 
      \   / \   /
       \ /   \ /
        G--H--I--J  (branch A)

在 Git 2.36 (Q2 2022) 中,"git log"(man) 和相关工具学会了一个选项 --exclude-first-parent-only,它可以仅沿着第一父节点链传播 UNINTERESTING 标志,就像 --first-parent 选项只显示缺少 UNINTERESTING 标志的提交记录沿着第一父节点链。

请查看提交9d505b7(2022年1月11日)由Jerry Zhang (jerry-skydio)提交。
(由Junio C Hamano -- gitster --合并于提交708cbef,2022年2月17日)

git-rev-list:添加--exclude-first-parent-only标志

签名作者:Jerry Zhang

It is useful to know when a branch first diverged in history from some integration branch in order to be able to enumerate the user's local changes.
However, these local changes can include arbitrary merges, so it is necessary to ignore this merge structure when finding the divergence point.

In order to do this, teach the "rev-list" family to accept "--exclude-first-parent-only", which restricts the traversal of excluded commits to only follow first parent links.

-A-----E-F-G--main
  \   / /
   B-C-D--topic

In this example, the goal is to return the set {B, C, D} which represents a topic branch that has been merged into main branch.
git rev-list topic ^main(man) will end up returning no commits since excluding main will end up traversing the commits on topic as well.
git rev-list --exclude-first-parent-only topic ^main(man) however will return {B, C, D} as desired.

Add docs for the new flag, and clarify the doc for --first-parent to indicate that it applies to traversing the set of included commits only.

rev-list-options现在在其手册页面中包括:

--first-parent

在查找要包含的提交时,仅跟随第一个父提交。

当查看特定主题分支的演变时,此选项可以提供更好的概述,因为合并到主题分支的合并通常只涉及不时地调整更新的上游,而此选项允许您忽略由此类合并带入历史记录的单个提交。

rev-list-options现在在其手册页面中包括:

--exclude-first-parent-only

当查找要排除的提交(使用 '{caret}')时,只追踪第一个父提交。

这可用于确定主题分支中从分叉点开始的更改集,因为任意合并都可以是有效的主题分支更改。


正如anarcat评论中所指出的,如果你的分支不是从master派生而来,而是从mainprod或其他任何分支派生而来,你可以使用以下命令:

git for-each-ref --merged="$local_ref" --no-contains="$local_ref" \
    --format="%(refname:strip=-1)" --sort='-*authordate' refs/heads

philb评论中 也提到了 --boundary 选项 (输出排除边界提交。边界提交以-为前缀):

git rev-list --exclude-first-parent-only --boundary ^main  branch_A | tail -1 

to get A directly, without needing an additional git rev-parse G^


我无法确认这是否有效。我使用了上面的测试。git rev-list返回J、I、H和G(按顺序)。git rev-list ...|head -1返回J。git rev-parse ...给我F。我假设此答案中的“A”是“分支 A”。在分支A上运行git版本2.36.1.windows.1进行了测试。 - user5534993
@user5534993 我同意。我现在已经完全测试了那种情况,并修复了 git rev-parse/rev-list 命令。如果修改后的答案对您更好,请告诉我。 - VonC
1
是的,我可以确认当前版本输出了我期望的结果。非常感谢您的跟进! - user5534993
所有这里的答案都假设您是从“master”分支,但这并不一定是正确的(您可以从“prod”或“main”或其他分支中进行分支)。我使用上面的命令代替“main”:git for-each-ref --merged="$local_ref" --no-contains="$local_ref" --format="%(refname:strip=-1)" --sort='-*authordate' refs/heads - anarcat
可能可以添加 --boundary 参数:git rev-list --exclude-first-parent-only --boundary ^main branch_A | tail -1,以便直接获取 A,而无需额外使用 git rev-parse G^ - philb

7

git log --graph中使分支点更容易看到的简单方法是使用选项--first-parent

例如,以接受答案中的 repo 为例:

$ git log --all --oneline --decorate --graph

*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

现在添加--first-parent选项:

$ git log --all --oneline --decorate --graph --first-parent

* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) merging master onto topic
| * 132ee2a first commit on topic branch
* | e7c863d commit on master after master was merged to topic
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

这使得事情变得更加容易!

注意,如果仓库有很多分支,你需要指定两个要比较的分支,而不是使用--all

$ git log --decorate --oneline --graph --first-parent master origin/topic

7
像这样的东西怎么样?
git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^

1
Git别名等效命令:diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'(无临时文件) - conny
这似乎给了我分支上的第一个提交,而不是共同祖先。(即它给出了 G 而不是原问题中的图表中的 A。)不过我想我已经找到了答案,很快就会发布。 - lindes
不必使用“git log --pretty=oneline”,您可以直接使用“git rev-list”,这样您就可以跳过切割步骤,而且这还会给出分歧点的父提交,所以只需使用“tail -2 | head 1”即可。因此:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1 - FelipeC
嗯,看起来Karl和我清理过的版本都需要--topo-order才能工作:diff -u <(git rev-list --topo-order branch_A) <(git rev-list --topo-order master) | tail -2 | head -1 - FelipeC
据我所知,tail -2 依赖于两个上下文行的统一差异输出,但对我来说是三行,因此我得到了错误的提交(在 OP 示例中为 X),所以我需要使用 tail -3。除非这种方法在我的情况下有很多提交和功能分支左右时会出现问题。可以使用 diff -U 2 来设置它,使其更加稳定,还可以使用 cut 去掉前导空格:diff -U 2 <(git rev-list --topo-order branch_A) <(git rev-list --topo-order master) | tail -2 | head -1 | cut -c 2- - Alexander Klimetschek
显示剩余2条评论

6
我觉得我可能漏掉了什么,但是我认为上述所有问题都是因为我们总是试图寻找回溯历史的分支点所导致的,由于可用的合并组合方式,这会导致各种问题。

相反,我的方法是基于这样一个事实:两个分支共享大量历史记录,即分支之前的所有历史记录都完全相同。因此,我的建议不是找回溯点,而是从第一次提交开始向前寻找两个分支中的第一个差异。分支点将是找到的第一个差异的父提交。

实际操作:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

它解决了我所有通常的问题。当然还有一些未涉及到的边界问题,但是......再见 :-)


diff <( git rev-list "${1:-master}" --first-parent ) <( git rev-list "${2:-HEAD}" --first-parent) -U1 | tail -1 - Alexander Bird
我发现这个更快(2-100倍):comm --nocheck-order -1 -2 <(git rev-list --reverse --topo-order topic) <(git rev-list --reverse --topo-order master) | head -1 - Andrei Neculau

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