我有一个带有master和a分支的存储库,在这两个分支之间有很多合并活动。当分支A基于master创建时,我如何在我的存储库中找到提交?

我的存储库基本上是这样的:

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

我正在寻找修订A,这不是git merge-base(——all)找到的。


当前回答

使用reflog似乎解决了这个问题git reflog <branchname>显示了所有的分支提交,包括创建分支。

这是来自一个分支,该分支在合并回主节点之前有2次提交。

git reflog june-browser-updates
b898b15 (origin/june-browser-updates, june-browser-updates) june-browser-updates@{0}: commit: sorted cve.csv
467ae0e june-browser-updates@{1}: commit: browser updates and cve additions
d6a37fb june-browser-updates@{2}: branch: Created from HEAD

其他回答

我也在寻找同样的东西,我发现了这个问题。谢谢你的提问!

然而,我发现我在这里看到的答案似乎并没有完全给出你所要求的答案(或者我正在寻找的答案)——它们似乎给出了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上看):

或者在这里(从主题来看):

在这两种情况下,我都选择了提交图中的B。一旦单击它,它的完整SHA就会显示在图形下方的文本输入字段中。


2. 从视觉上看,但从终端来看:

Git日志—图形—一行—全部

(编辑/旁注:添加—装饰也可以很有趣;它添加了分支名称、标记等的指示。没有将它添加到上面的命令行,因为下面的输出没有反映它的使用。)

它显示(假设git配置-global颜色。ui汽车):

或者,直接说:

*   a9546a2 merge from topic back to master
|\  
| *   648ca35 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

在任何一种情况下,我们都将6aafd7f提交视为最低公共点,即在我的图中是B,或者在你的图中是A。


3.用贝壳魔法:

您没有在问题中指定您想要的是类似上述的东西,还是只提供一个修订版本的单一命令,而不是其他任何命令。下面是后者:

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

你也可以把它放到~/中。Gitconfig as(注意:后面的破折号很重要;谢谢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不能工作——<()语法在vanilla sh中不存在。(再次感谢@conny,让我在本页另一个答案的评论中意识到它!)

注:上述备选版本:

感谢liori指出,在比较相同的分支时,上面的内容可能会失败,并提出了一个替代的diff表单,从混合中删除sed表单,并使其“更安全”(即,即使在比较master和master时,它也会返回一个结果(即最近的提交):

作为.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' -

从壳上:

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'\'' -'

所以,在我的测试树中(不好意思,它暂时不可用;它回来了),现在对master和topic都有效(分别给出提交G和B)。再次谢谢你,利奥里,给我另一种形式。


所以,这就是我(和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-ing快乐!

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

^main branch_A给出的是J—I—H—G -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位的提交一样。

参见Jerry Zhang (Jerry -skydio)提交的9d505b7 (11 Jan 2022)。 (由Junio C Hamano—gitster—在commit 708cbef中合并,2022年2月17日)

Git-rev-list: add——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现在包括在它的手册页:

——“首席家长 当找到要包含的提交时,只遵循第一个 父节点在看到合并提交时提交。 这个选项 能给一个更好的概述时,查看的演变 一个特定的主题分支,因为合并到一个主题中 分支倾向于只调整到更新的上游 时不时地,这个选项可以让你忽略 个人的承诺被带入你的历史 一个合并。

Rev-list-options现在包括在它的手册页:

——exclude-first-parent-only 当发现提交排除(使用'{插入符号}')时,只跟随 第一个父节点在看到合并提交时提交。 这可用于查找主题分支中的更改集 从它与远分支的分歧点开始 任意的合并可以是有效的主题分支更改。

问题似乎是在一边的两个分支之间找到最近的单次提交切割,在另一边找到最早的共同祖先(可能是回购的初始提交)。这符合我对“分支”点的直觉。

记住,使用普通的git shell命令来计算这一点并不容易,因为git rev-list——我们最强大的工具——不允许我们限制提交到达的路径。我们拥有的最接近的是git rev-list——boundary,它可以给我们一组“阻塞我们的方式”的所有提交。(注意:git rev-list——ancestry-path很有趣,但我不知道如何让它在这里有用。)

下面是脚本:https://gist.github.com/abortz/d464c88923c520b79e3d。它相对简单,但由于循环,它的复杂程度足以保证要点。

请注意,这里提出的大多数其他解决方案不可能在所有情况下都有效,原因很简单:git rev-list—first-parent在线性化历史时不可靠,因为两种顺序都可能存在合并。

另一方面,Git rev-list -topo-order非常有用——用于按地形顺序进行提交——但执行差分是很脆弱的:对于给定的图,有多种可能的地形顺序,因此您依赖于排序的某种稳定性。也就是说,strongk7的解决方案可能在大多数时候都非常有效。然而,它比我的慢,因为必须遍历整个回购历史…两次。: -)

你可以使用下面的命令返回branch_a中最老的提交,master无法访问它:

git rev-list branch_a ^master | tail -1

也许有一个额外的健全性检查,该提交的父节点实际上可以从master访问…

下面的命令将显示Commit A的SHA1

git merge-base -fork-point A