当前位置:  开发笔记 > 编程语言 > 正文

用Git找到分支点?

如何解决《用Git找到分支点?》经验,为你挑选了8个好方法。

我有一个存储库,其中包含分支主服务器和A以及两者之间的大量合并活动.当基于master创建分支A时,如何在我的存储库中找到提交?

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

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

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



1> lindes..:

我正在寻找同样的事情,我发现了这个问题.谢谢你的要求!

然而,我发现,我在这里看到的答案似乎并不十分给你要的答案(或者说我一直在寻找) -他们似乎放弃了G提交,而不是A提交.

所以,我创建了以下树(按时间顺序分配的字母),所以我可以测试一下:

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

这看起来与你的有点不同,因为我想确保我得到(指的是这个图,而不是你的)B,但不是A(而不是D或E).这是附加到SHA前缀和提交消息的字母(我的repo可以从这里克隆,如果这对任何人都很有趣):

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:

您应该在视觉上看到这样的树(从主视图中查看):

从主人那里获取gitk截屏

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

从主题获取gitk屏幕截图

在这两种情况下,我都选择了B图表中的提交.单击它后,其完整的SHA将显示在图表下方的文本输入字段中.


2.视觉上,但从终端:

git log --graph --oneline --all

(编辑/侧注:添加--decorate也可能很有趣;它添加了分支名称,标签等的指示.不将其添加到上面的命令行,因为下面的输出不反映它的用法.)

显示(假设git config --global color.ui auto):

git log的输出--graph --oneline --all

或者,在直文中:

*   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.使用shell魔法:

你没有在你的问题中指明你是否想要像上面这样的东西,或者只是一个命令,它只会让你获得一个修订版,而不是其他任何东西.嗯,这是后者:

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

你也可以把你的〜/的.gitconfig为(注:尾随破折号是很重要的,感谢布赖恩的将注意力集中于):

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

因此,在我的测试树中(暂时不可用,抱歉;它回来了),现在可以在master和topic上工作(分别提交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`.."'

快乐的giting!


现在有`git merge-base --fork-point ...`
@JakubNarębski@ lindes` - fork-point`基于reflog,因此只有在本地进行更改才能使用它.即使这样,reflog条目也可能已过期.它很有用,但根本不可靠.
感谢lindes,shell选项非常适合您想要找到长时间运行的维护分支的分支点的情况.当你正在寻找一个过去可能是一千次提交的修订时,视觉选项真的不会削减它.*8' )
在第三种方法中,您依赖于上下文将显示第一个未更改的行.这在某些边缘情况下不会发生,或者如果您的需求略有不同(例如,我只需要其中一个历史记录是--first-parent,并且我在脚本中使用此方法,有时可能会使用相同的两边的分支).我发现使用`diff`的if-then-else模式更安全,并从输出中删除更改/删除的行,而不是指望具有足够大的上下文.通过:`diff --old-line-format ='' --new-line-format =''<(git rev-list ...)<(git rev-list ...)| head -1`.
@JakubNarębski:你有办法让`git merge-base --fork-point ...`为这棵树提供提交B(提交`6aafd7f`)吗?当你发布它时,我正忙着其他的东西,听起来不错,但我终于尝试了,我没有让它工作......我要么得到更新的东西,要么只是一个无声的失败(没有错误)消息,但退出状态1),尝试像`git co master; git merge-base --fork-point topic`,`git co topic; git merge-base --fork-point master`,`git merge-base --fork-point topic master`(for checkout)等等.有什么东西我做错了或丢失了吗?
我认为`git log -n1 --format = format:%H $(git log --reverse --format = format:%H master..topic | head -1)〜`也可以工作
`diff ... | sed ... | 头-1`和'diff ... | tail -1`比使用这样的`comm`慢了100倍:`comm --nocheck-order -1 -2 <(git rev-list --first-parent topic)<(git rev-list - 第一父母大师)| 头-1`
`diff -U1 <(git rev-list --first-parent topic)<(git rev-list --first-parent master)| 尾巴-1` 通过强制diff上下文只有一行,你不需要grep/sed通过它.

2> Greg Hewgill..:

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

git merge-base在两次提交之间找到最佳共同祖先,以便在三向合并中使用.一个共同的祖先比另一个共同的祖先更好,如果后者是前者的祖先.没有任何更好的共同祖先的共同祖先是最好的共同祖先,即合并基础.请注意,一对提交可以有多个合并基础.


现在有`git merge-base --fork-point ...`
还要注意"`git merge-base`"的`--all`选项
这不能回答原来的问题,但是大多数人都会问这个答案更简单的问题:)
@TomTanner:我只是查看了问题历史记录,编辑了原始问题,在我的答案发布五个小时之后包含了关于`git merge-base`的说明(可能是为了回答我的回答).不过,我会保留这个答案,因为它可能对通过搜索找到这个问题的其他人有用.
他说他不是git merge-base的结果

3> mipadi..:

我已经习惯git rev-list了这种事情.例如,(注意3个点)

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

会吐出分支点.现在,它并不完美; 因为你已经将master合并到分支A中几次,所以会分出几个可能的分支点(基本上,原始分支点,然后是将master合并到分支A的每个点).但是,至少应该缩小可能性.

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

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

所以我可以称之为:

$ git diverges branch-a master



4> 小智..:

如果你喜欢简洁的命令,

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",并在"主",因为它是在什么祖先"大师".所以你得到了两个分支中最早的提交.

命令

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作为"分支"提交是完全合理的.这是开发人员从"主人"扩展出来的时候.当他分支时,分支"B"甚至没有合并在他的分支中!这就是本文中的解决方案.

如果您想要的是最后一次提交C,使得从原点到分支"A"上的最后一次提交的所有路径都通过C,那么您想要忽略祖先顺序.这纯粹是拓扑,并且让您了解自从何时有两个版本的代码同时出现.那时你会选择基于合并的方法,它会在我的例子中返回C1.


这是迄今为止最干净的答案,让我们把它投到最高层.建议的编辑:`git rev-list commit ^^!`可以简化为`git rev-parse commit ^`
除非我完全缺少某些东西,否则这将无法工作。示例分支中的两个方向都有合并。听起来您尝试将其考虑在内,但我相信这将导致您的答案失败。git rev-list --first-parent ^ topic master将仅带您回到从master到topic的最后合并之后的第一次提交(如果存在)。

5> Mark Booth..:

鉴于此线程中的许多答案都没有给出问题所要求的答案,这里是每个解决方案的结果摘要,以及我用来复制问题中给出的存储库的脚本.

日志

使用给定的结构创建存储库,我们得到git日志:

$ 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,然后合并masterbranch_A中间没有犯然后lindes'解决方案只为您提供最新的第一divergance.

这意味着,对我的工作流程,我想我将不得不坚持标注长时间运行的分支的分支点,因为我不能保证他们能够可靠地以后可以找到.

这真的都归结为git缺少hg调用命名分支的东西.博主JHW称这些宗族家庭在他的文章为什么我喜欢水银多的Git和他的后续文章推荐水银主场迎战Git的(使用图形!) .我会建议人们读他们明白了为什么有些善变的皈依怀念没有命名的分支机构git.

解决方案不起作用

mipadi提供的解决方案返回两个答案,I并且C:

$ 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向我展示了一种更简洁的方法来编写示例存储库.


Karl的解决方案很容易解决:`diff -u <(git rev-list branch_A)<(git rev-list master)| 尾-2 | 头-1`.感谢您提供创建回购的说明:)

6> CB Bailey..:

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

在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"


谢谢Charles,你已经说服了我,如果我想知道分支*最初发散的点*,我将不得不标记它.我真的希望`git`有一个等同于`hg`的命名分支,它会使管理长期维护分支*更容易.

7> Karl..:

怎么样的

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`^



8> 小智..:

肯定我错过了什么,但IMO,上面的所有问题都是因为我们总是试图找到历史中的分支点,并且由于可用的合并组合而导致各种问题.

相反,我采用了不同的方法,基于两个分支共享大量历史的事实,分支前的所有历史记录都是100%相同的,所以我的提议不是回头,而是向前推进(从1开始) (),寻找两个分支的第一个差异.简单地说,分支点将是找到的第一个差异的父级.

在实践中:

#!/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

它正在解决我所有常见的情况.当然有边境的没有被覆盖但是...... ciao :-)

推荐阅读
夏晶阳--艺术
这个屌丝很懒,什么也没留下!