用 Git 寻找分支点?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1527234/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 07:10:39  来源:igfitidea点击:

Finding a branch point with Git?

gitbranch

提问by Jochen

I have a repository with branches master and A and lots of merge activity between the two. How can I find the commit in my repository when branch A was created based on master?

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

My repository basically looks like this:

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

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

I'm looking for revision A, which is not what git merge-base (--all)finds.

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

回答by lindes

I was looking for the same thing, and I found this question. Thank you for asking it!

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

However, I found that the answers I see here don't seem to quitegive the answer you asked for (or that I was looking for) -- they seem to give the Gcommit, instead of the Acommit.

但是,我发现我在这里看到的答案似乎并没有完全给出您要求的(或我正在寻找的)答案——它们似乎给出了G提交,而不是A提交。

So, I've created the following tree (letters assigned in chronological order), so I could test things out:

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

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

This looks a little different than yours, because I wanted to make sure that I got (referring to this graph, not yours) B, but not A (and not D or E). Here are the letters attached to SHA prefixes and commit messages (my repo can be cloned from here, if that's interesting to anyone):

这看起来与你的有点不同,因为我想确保我得到(指的是这张图,不是你的)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

So, the goal: find B. Here are three ways that I found, after a bit of tinkering:

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



1. visually, with gitk:

1.视觉上,用gitk:

You should visually see a tree like this (as viewed from master):

您应该在视觉上看到这样的树(从 master 看):

gitk screen capture from master

来自 master 的 gitk 屏幕截图

or here (as viewed from topic):

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

gitk screen capture from topic

来自主题的 gitk 屏幕截图

in both cases, I've selected the commit that is Bin my graph. Once you click on it, its full SHA is presented in a text input field just below the graph.

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



2. visually, but from the terminal:

2. 视觉上,但从终端:

git log --graph --oneline --all

git log --graph --oneline --all

(Edit/side-note: adding --decoratecan also be interesting; it adds an indication of branch names, tags, etc. Not adding this to the command-line above since the output below doesn't reflect its use.)

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

which shows (assuming git config --global color.ui auto):

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

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

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

Or, in straight text:

或者,直接文本:

*   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

in either case, we see the 6aafd7f commit as the lowest common point, i.e. Bin my graph, or Ain yours.

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



3. With shell magic:

3.带壳魔法:

You don't specify in your question whether you wanted something like the above, or a single command that'll just get you the one revision, and nothing else. Well, here's the latter:

你没有在你的问题中指定你是否想要像上面那样的东西,或者一个只会给你一个修订版的命令,而不是别的。好吧,这是后者:

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

Which you can also put into your ~/.gitconfig as (note: trailing dash is important; thanks Brianfor bringing attention to that):

你也可以把它放在你的 ~/.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' -

Which could be done via the following (convoluted with quoting) command-line:

这可以通过以下(复杂的引用)命令行来完成:

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

Note: zshcould just as easily have been bash, but shwill notwork -- the <()syntax doesn't exist in vanilla sh. (Thank you again, @conny, for making me aware of it in a comment on another answer on this page!)

注:zsh可以很容易地一直bash,但sh不会工作-在<()语法不存在香草sh。(再次感谢@conny,让我在对本页另一个答案的评论中意识到这一点!)

Note: Alternate version of the above:

注意:以上的替代版本:

Thanks to liorifor pointing outthat the above could fall down when comparing identical branches, and coming up with an alternate diff form which removes the sed form from the mix, and makes this "safer" (i.e. it returns a result (namely, the most recent commit) even when you compare master to master):

由于liori用于指出在比较相同分支当上述可以落下,并想出替代的diff形式其去除从所述混合物中的sed形式,并且使这种“安全”的(即,它返回一个结果(即,最近的提交),即使您将 master 与 master 进行比较):

As a .git-config line:

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

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

So, in my test tree (which was unavailable for a while, sorry; it's back), that now works on both master and topic (giving commits G and B, respectively). Thanks again, liori, for the alternate form.

所以,在我的测试树中(有一段时间不可用,抱歉;它回来了),现在适用于 master 和 topic(分别提供提交 G 和 B)。再次感谢 liori 的替代形式。



So, that's what I [and liori] came up with. It seems to work for me. It also allows an additional couple of aliases that might prove handy:

所以,这就是我 [和 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`.."'

Happy git-ing!

快乐的 git-ing!

回答by Greg Hewgill

You may be looking for git merge-base:

您可能正在寻找git merge-base

git merge-basefinds best common ancestor(s) between two commits to use in a three-way merge. One common ancestor is betterthan another common ancestor if the latter is an ancestor of the former. A common ancestor that does not have any better common ancestor is a best common ancestor, i.e. a merge base. Note that there can be more than one merge base for a pair of commits.

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

回答by mipadi

I've used git rev-listfor this sort of thing. For example, (note the 3dots)

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

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

will spit out the branch point. Now, it's not perfect; since you've merged master into branch A a couple of times, that'll split out a couple possiblebranch points (basically, the original branch point and then each point at which you merged master into branch A). However, it should at least narrow down the possibilities.

会吐出分支点。现在,它并不完美。由于您已经多次将 master 合并到分支 A 中,这将拆分出几个可能的分支点(基本上,原始分支点,然后是您将 master 合并到分支 A 的每个点)。但是,它至少应该缩小可能性。

I've added that command to my aliases in ~/.gitconfigas:

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

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

so I can call it as:

所以我可以称之为:

$ git diverges branch-a master

回答by Lionel

If you like terse commands,

如果你喜欢简洁的命令,

git rev-list $(git rev-list --first-parent ^branch_name master | tail -n1)^^! 

Here's an explanation.

这是一个解释。

The following command gives you the list of all commits in master that occurred after branch_name was created

以下命令为您提供了在创建 branch_name 后发生的 master 中所有提交的列表

git rev-list --first-parent ^branch_name master 

Since you only care about the earliest of those commits you want the last line of the output:

由于您只关心最早的提交,因此您需要输出的最后一行:

git rev-list ^branch_name --first-parent master | tail -n1

The parent of the earliest commit that's not an ancestor of "branch_name" is, by definition, in"branch_name," and is in "master" since it's an ancestor of something in "master." So you've got the earliest commit that's in both branches.

根据定义,不是“branch_name”祖先的最早提交的父提交“branch_name”中,并且在“master”中,因为它是“master”中某物的祖先。所以你有两个分支中最早的提交。

The command

命令

git rev-list commit^^!

is just a way to show the parent commit reference. You could use

只是显示父提交引用的一种方式。你可以用

git log -1 commit^

or whatever.

管他呢。

PS: I disagree with the argument that ancestor order is irrelevant. It depends on what you want. For example, in this case

PS:我不同意祖先顺序无关紧要的论点。这取决于你想要什么。例如,在这种情况下

_C1___C2_______ master
  \    \_XXXXX_ branch A (the Xs denote arbitrary cross-overs between master and A)
   \_____/ branch B

it makes perfect sense to output C2 as the "branching" commit. This is when the developer branched out from "master." When he branched, branch "B" wasn't even merged in his branch! This is what the solution in this post gives.

将 C2 输出为“分支”提交是非常有意义的。这是开发人员从“master”分支出来的时候。当他分支时,分支“B”甚至没有合并到他的分支中!这就是这篇文章中给出的解决方案。

If what you want is the last commit C such that all paths from origin to the last commit on branch "A" go through C, then you want to ignore ancestry order. That's purely topological and gives you an idea of since when you have two versions of the code going at the same time. That's when you'd go with merge-base based approaches, and it will return C1 in my example.

如果您想要的是最后一次提交 C,以便从原点到分支“A”上最后一次提交的所有路径都经过 C,那么您想要忽略祖先顺序。这纯粹是拓扑的,并且可以让您了解何时同时运行两个版本的代码。那时你会使用基于合并的方法,它会在我的示例中返回 C1。

回答by Mark Booth

Given that so many of the answers in this thread do not give the answer the question was asking for, here is a summary of the results of each solution, along with the script I used to replicate the repository given in the question.

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

The log

日志

Creating a repository with the structure given, we get the git log of:

使用给定的结构创建一个存储库,我们得到以下 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

My only addition, is the tag which makes it explicit about the point at which we created the branch and thus the commit we wish to find.

我唯一添加的是标签,它明确说明了我们创建分支的点以及我们希望找到的提交。

The solution which works

有效的解决方案

The only solution which works is the one provided by lindescorrectly returns A:

唯一有效的解决方案是lindes提供的解决方案正确返回A

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

As Charles Baileypoints out though, this solution is very brittle.

但正如Charles Bailey指出的那样,这个解决方案非常脆弱。

If you branch_Ainto masterand then merge masterinto branch_Awithout intervening commits then lindes' solution only gives you the most recent first divergance.

如果你branch_A进入master,然后合并masterbranch_A中间没有提交,然后lindes'解决方案只为您提供最新的第一divergance

That means that for my workflow, I think I'm going to have to stick with tagging the branch point of long running branches, since I can't guarantee that they can be reliably be found later.

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

This really all boils down to gits lack of what hgcalls named branches. The blogger jhw calls these lineagesvs. familiesin his article Why I Like Mercurial More Than Gitand his follow-up article More On Mercurial vs. Git (with Graphs!). I would recommend people read them to see why some mercurial converts miss not having named branchesin git.

这实际上都归结为git缺乏hg所谓的命名分支。博主 jhw在他的文章Why I Like Mercurial More Than Git和他的后续文章More On Mercurial vs. Git (with Graphs!) 中称这些血统家庭。我会建议人们读他们明白了为什么有些善变的皈依怀念没有命名的分支机构在。git

The solutions which don't work

不起作用的解决方案

The solution provided by mipadireturns two answers, Iand C:

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

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

The solution provided by Greg Hewgillreturn I

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

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

The solution provided by Karlreturns X:

Karl提供的解决方案返回X

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

The script

剧本

mkdir 
cd 
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"

I doubt the git version makes much difference to this, but:

我怀疑 git 版本对此有很大的不同,但是:

$ git --version
git version 1.7.1

Thanks to Charles Baileyfor showing me a more compact way to script the example repository.

感谢Charles Bailey向我展示了一种更紧凑的方式来编写示例存储库的脚本。

回答by CB Bailey

In general, this is not possible. In a branch history a branch-and-merge before a named branch was branched off and an intermediate branch of two named branches look the same.

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

In git, branches are just the current names of the tips of sections of history. They don't really have a strong identity.

在 git 中,分支只是历史部分提示的当前名称。他们真的没有很强的身份。

This isn't usually a big issue as the merge-base (see Greg Hewgill's answer) of two commits is usually much more useful, giving the most recent commit which the two branches shared.

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

A solution relying on the order of parents of a commit obviously won't work in situations where a branch has been fully integrated at some point in the branch's history.

依赖于提交的父级顺序的解决方案显然不适用于分支在分支历史中的某个时间点已完全集成的情况。

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"

This technique also falls down if an integration merge has been made with the parents reversed (e.g. a temporary branch was used to perform a test merge into master and then fast-forwarded into the feature branch to build on further).

如果在父级颠倒的情况下进行了集成合并(例如,使用临时分支执行测试合并到主分支,然后快进到功能分支以进一步构建),则该技术也会失败。

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"

回答by Karl

How about something like

怎么样的东西

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

回答by stronk7

surely I'm missing something, but IMO, all the problems above are caused because we are always trying to find the branch point going back in the history, and that causes all sort of problems because of the merging combinations available.

当然我遗漏了一些东西,但是 IMO,上面的所有问题都是因为我们总是试图找到历史上的分支点,并且由于可用的合并组合而导致各种问题。

Instead, I've followed a different approach, based in the fact that both branches share a lot of history, exactly all the history before branching is 100% the same, so instead of going back, my proposal is about going forward (from 1st commit), looking for the 1st difference in both branches. The branch point will be, simply, the parent of the first difference found.

相反,我采用了不同的方法,基于这样一个事实,即两个分支共享很多历史,分支之前的所有历史完全 100% 相同,所以我的建议是向前推进(从 1st提交),寻找两个分支中的第一个差异。分支点将是找到的第一个差异的父节点。

In practice:

在实践中:

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

And it's solving all my usual cases. Sure there are border ones not covered but... ciao :-)

它解决了我所有的常见情况。当然有边界的没有被覆盖,但是...... ciao :-)

回答by Reck

I recently needed to solve this problem as well and ended up writing a Ruby script for this: https://github.com/vaneyckt/git-find-branching-point

我最近也需要解决这个问题,最终为此编写了一个 Ruby 脚本:https: //github.com/vaneyckt/git-find-branching-point

回答by FelipeC

After a lot of research and discussions, it's clear there's no magic bullet that would work in all situations, at least not in the current version of Git.

经过大量的研究和讨论,很明显没有什么灵丹妙药可以适用于所有情况,至少在当前版本的 Git 中不是。

That's why I wrote a couple of patches that add the concept of a tailbranch. Each time a branch is created, a pointer to the original point is created too, the tailref. This ref gets updated every time the branch is rebased.

这就是为什么我写了几个补丁来添加tail分支的概念。每次创建一个分支时,也会创建一个指向原始点的指针,即tailref。每次分支重新定位时,此 ref 都会更新。

To find out the branch point of the devel branch, all you have to do is use devel@{tail}, that's it.

要找出 devel 分支的分支点,只需使用devel@{tail},就可以了。

https://github.com/felipec/git/commits/fc/tail

https://github.com/felipec/git/commits/fc/tail