用Git找到一个分支点?
我有一个分支机构主和A的仓库和大量的两个合并活动。 当分支A基于主机创建时,如何在我的存储库中找到提交?
我的资料库基本上是这样的:
-- X -- A -- B -- C -- D -- F (master) \ / \ / \ / \ / G -- H -- I -- J (branch A)
我正在寻找修订版A,这不是什么git merge-base (--all)
发现。
我正在寻找同样的东西,我发现了这个问题。 谢谢你的问候!
但是,我发现我在这里看到的答案似乎并没有给出你要求的答案(或者我正在寻找的答案) – 他们似乎给了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:
你应该从视觉上看到这样一棵树(从主视角看):
或在这里(从主题看):
在这两种情况下,我在图中选择了B
的提交。 一旦你点击它,它的全部SHA就会出现在图形正下方的文本输入框中。
2.视觉上,但从终端:
git log --graph --oneline --all
其中显示(假设git config --global color.ui auto
):
或者,连续的文字:
* a9546a2从主题合并回主 | \ | * 648ca35合并到主题 | | \ | * | 132ee2a首先在主题分支上提交 * | | 主合并到主题后,e7c863d在主服务器上提交 | | / | / | * | 37ad159后分支提交在主 | / * 6aafd7f在分支之前在master上进行第二次提交 * 4112403初始提交到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中(注意:拖尾短划线很重要;感谢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形式,并使这个“更安全”(即返回一个结果(即最近的提交),即使你比较主对主):
作为.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'\'' -'
所以,在我的测试树(这是一段时间,不好意思,它回来了),现在对主和主题(分别提交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 merge-base
:
git merge-base找到两个提交之间最好的共同祖先,以便在三路合并中使用。 如果后者是前者的祖先,则一个共同的祖先比另一个共同的祖先更好 。 没有更好的共同祖先的共同祖先是最好的共同祖先 ,即合并基础 。 请注意,一对提交可以有多个合并基础。
我已经用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
如果你喜欢简洁的命令,
git rev-list $(git rev-list --first-parent ^ branch_name master | tail -n1)^^!
这是一个解释。
以下命令为您提供了在创建branch_name之后发生的所有主内提交的列表
git rev-list --first-parent ^ branch_name master
由于您只关心那些提交中最早的那个,所以您需要输出的最后一行:
git rev-list ^ branch_name --first-parent master | 尾巴-n1
不是“branch_name”的祖先的最早的提交的父项根据定义在 “branch_name”中,并且在“master”中,因为它是“master”中的某个祖先的祖先。 所以你在这两个分支都有最早的提交。
命令
git rev-list commit ^^!
只是一种显示父提交引用的方法。 你可以使用
git log -1 commit ^
管他呢。
PS:我不同意祖先秩序不相关的论点。 这取决于你想要什么。 例如,在这种情况下
_C1___C2_______高手 \\ _XXXXX_分支A(X表示主控和A之间的任意交叉) \ _____ /分支B
输出C2作为“分支”提交是非常有意义的。 这是开发人员从“主人”分支出来的时候。 当他分支时,分支“B”甚至没有合并在他的分支! 这是这篇文章的解决方案。
如果你想要的是最后一个提交C,从原点到分支“A”上的最后一个提交的所有路径都经过C,那么你想忽略祖先的顺序。 这是纯粹的拓扑,并给你一个想法,因为当你有两个版本的代码在同一时间。 那么当你使用基于合并的方法时,它会在我的例子中返回C1。
鉴于这个线程中的许多答案都没有给出问题所要求的答案,下面是每个解决方案的结果摘要,以及用于复制问题中给出的存储库的脚本。
日志
按照给定的结构创建一个仓库,我们得到以下的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
,然后将master
并入branch_A
而不进行提交,那么lindes的解决方案只会为您提供最近的第一个分支 。
这意味着对于我的工作流程,我想我将不得不坚持标记长时间运行的分支的分支点,因为我不能保证以后能够可靠地找到它们。
这真的都归结为git
缺乏什么hg
电话命名分支机构 。 博主jhw在他的文章“ 为什么我喜欢Mercurial不仅仅是Git”和他的后续文章“ 更多关于Mercurial vs. Git(带图表! 我会建议人们阅读他们,看看为什么一些mercurial转换思念没有命名分支 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向我展示了一个更简洁的脚本示例库。
一般来说,这是不可能的。 在分支历史记录中,在命名分支之前的分支和合并被分支,并且两个命名分支的中间分支看起来是相同的。
在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"
感觉如何?
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`^
我最近还需要解决这个问题,最后为此写了一个Ruby脚本: https : //github.com/vaneyckt/git-find-branching-point
当然,我错过了一些东西,但海事组织,所有上述问题的原因是我们总是试图找到历史上的分支点,并由于可用的合并组合导致各种问题。
相反,我采用了不同的方法,基于两个分支共享很多的历史,分支之前的所有历史都是100%相同的,所以我的提议不是返回,而是提前(从第一个提交),寻找两个分支的第一个差异。 分支点就是找到的第一个差异的父亲。
在实践中:
#!/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 🙂
这是我以前回答以前的答案的改进版本。 它依靠合并提交消息来查找分支最初创建的位置。
它适用于这里提到的所有仓库,我甚至解决了在邮件列表中产生的一些棘手的问题。 我也为此写了测试 。
find_merge () { local selection extra test "$2" && extra=" into $2" git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1 } branch_point () { local first_merge second_merge merge first_merge=$(find_merge $1 "" "$1 $2") second_merge=$(find_merge $2 $1 $first_merge) merge=${second_merge:-$first_merge} if [ "$merge" ]; then git merge-base $merge^1 $merge^2 else git merge-base $1 $2 fi }
经过大量的研究和讨论,很明显没有什么灵丹妙药可以适用于所有情况,至少在目前的Git版本中是如此。
这就是为什么我写了几个补丁,添加了tail
分支的概念。 每次创建分支时,也会创建一个指向原始点的指针,即tail
引用。 每次分支重新分配时,这个ref都会被更新。
要找出devel分支的分支点,你所要做的就是使用devel@{tail}
,就是这样。
要从分支点查找提交,可以使用这个。
git log --ancestry-path master..topicbranch
以下命令将显示Commit A的SHA1
git merge-base --fork-point A
我似乎正在得到一些快乐
git rev-list branch...master
你得到的最后一行是分支上的第一个提交,所以这是一个获取父项的问题。 所以
git rev-list -1 `git rev-list branch...master | tail -1`^
似乎为我工作,并不需要差异等(这是有益的,因为我们没有该版本的差异)
更正:如果您在主分支上,这不起作用,但是我正在脚本中这样做,所以这不是一个问题
问题似乎是在两边的分支机构之间找到最近的单一承诺,另一方面最早的共同祖先(可能是回购的初始承诺)。 这符合我对“分支”点的直觉。
记住,使用普通的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的解决方案在大多数情况下可能工作的很好。 但是,由于必须遍历回购的整个历史,所以我的速度要慢两倍。 🙂
有时候实际上是不可能的(有些地方你可能会有幸获得更多的数据),而且这里的解决方案也不行。
Git不保存ref历史(包括分支)。 它只存储每个分支(头部)的当前位置。 这意味着你可以随着时间的推移在Git中丢失一些分支历史。 例如,当你分支时,它会立即丢失哪个分支是原来的分支。 所有分支做的是:
git checkout branch1 # refs/branch1 -> commit1 git checkout -b branch2 # branch2 -> commit1
你可能会认为第一个承诺是分支。 这往往是这种情况,但并不总是如此。 在上述操作之后,没有任何东西阻止你先进行分支。 另外,git时间戳不保证是可靠的。 直到你承诺,他们真正成为分支机构。
在图中,我们倾向于在概念上提交数字,当提交树分支时,git没有真正稳定的序列概念。 在这种情况下,您可以假设数字(表示顺序)由时间戳确定(当您将所有时间戳设置为相同时,查看git UI如何处理事情可能很有趣)。
这是人们在概念上期望的:
After branch: C1 (B1) / - \ C1 (B2) After first commit: C1 (B1) / - \ C1 - C2 (B2)
这是你实际得到的:
After branch: - C1 (B1) (B2) After first commit (human): - C1 (B1) \ C2 (B2) After first commit (real): - C1 (B1) - C2 (B2)
你会认为B1是最初的分支,但实际上它可能只是一个死支(有人做了checkout -b但从来没有投入)。 直到你承诺,你在git中得到一个合法的分支结构:
Either: / - C2 (B1) -- C1 \ - C3 (B2) Or: / - C3 (B1) -- C1 \ - C2 (B2)
你总是知道C1出现在C2和C3之前,但是你不可能知道C2是否在C3或者C3出现在C2之前(因为你可以在你的工作站上设置任何时间)。 B1和B2也是误导,因为你无法知道哪个分支最先出现。 在很多情况下,你可以做出非常好的,通常准确的猜测。 这有点像赛道。 所有的事情通常与赛车相同,那么你可以假设一辆落后一圈的赛车落后了一圈。 我们也有一些非常可靠的约定,例如主人几乎总是代表最长的分支,尽管我曾经看到过这样的情况,即使情况并非如此。
这里给出的例子是一个保存历史的例子:
Human: - X - A - B - C - D - F (B1) \ / \ / G - H ----- I - J (B2) Real: B ----- C - D - F (B1) / / \ / - X - A / \ / \ / \ / G - H ----- I - J (B2)
真正的这里也是误导性的,因为我们作为人类从左到右阅读,从根到叶(ref)。 Git不这样做。 我们在哪里(A-> B)在我们的头上做(A <-B或B-> A)。 它从ref读到根。 参考可以在任何地方,但往往是叶子,至少对于活跃的分支。 裁判指出一个提交和提交只包含一个喜欢他们的父母,而不是他们的孩子。 当一个提交是一个合并提交时,它将有多个父代。 第一个父项始终是合并的原始提交。 其他父母总是将提交合并到原始提交中。
Paths: F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X))))) J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
这不是一个非常有效的表示,而是表示所有的路径git可以从每个参考(B1和B2)。
Git的内部存储看起来更像这样(不是A作为父节点出现两次):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
如果你转储一个原始的git提交,你会看到零个或多个父域。 如果为零,则表示没有父项,提交是根(实际上可以有多个根)。 如果有一个,这意味着没有合并,这不是一个根提交。 如果有多于一个,则意味着提交是合并的结果,而第一个合并后的所有父母都是合并提交。
Paths simplified: F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X Paths first parents only: F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X J->(I->(H->(G->(A->X))) | J->I->H->G->A->X Or: F->D->C | J->I | I->H | C->B->A | H->G->A | A->X Paths first parents only simplified: F->D->C->B->A | J->I->->G->A | A->X Topological: - X - A - B - C - D - F (B1) \ G - H - I - J (B2)
当双方都打A时,他们的连锁将会是一样的,在此之前他们的连锁将会完全不同。 第一个犯下另外两个犯的共同点是共同的祖先,并从中分离出来。 术语commit,branch和ref可能会有些混淆。 你实际上可以合并一个提交。 这是什么合并真的。 一个ref只是指向一个提交,一个分支只不过是文件夹.git / refs / heads中的一个ref,文件夹的位置决定了ref是一个分支,而不是其他的标签。
Where you lose history is that merge will do one of two things depending on circumstances.
考虑:
/ - B (B1) - A \ - C (B2)
In this case a merge in either direction will create a new commit with the first parent as the commit pointed to by the current checked out branch and the second parent as the commit at the tip of the branch you merged into your current branch. It has to create a new commit as both branches have changes since their common ancestor that must be combined.
/ - B - D (B1) - A / \ --- C (B2)
At this point D (B1) now has both sets of changes from both branches (itself and B2). However the second branch doesn't have the changes from B1. If you merge the changes from B1 into B2 so that they are syncronised then you might expect something that looks like this (you can force git merge to do it like this however with –no-ff):
Expected: / - B - D (B1) - A / \ \ --- C - E (B2) Reality: / - B - D (B1) (B2) - A / \ --- C
You will get that even if B1 has additional commits. As long as there aren't changes in B2 that B1 doesn't have, the two branches will be merged. It does a fast forward which is like a rebase (rebases also eat or linearise history), except unlike a rebase as only one branch has a change set it doesn't have to apply a changeset from one branch on top of that from another.
From: / - B - D - E (B1) - A / \ --- C (B2) To: / - B - D - E (B1) (B2) - A / \ --- C
If you cease work on B1 then things are largely fine for preserving history in the long run. Only B1 (which might be master) will advance typically so the location of B2 in B2's history successfully represents the point that it was merged into B1. This is what git expects you to do, to branch B from A, then you can merge A into B as much as you like as changes accumulate, however when merging B back into A, it's not expected that you will work on B and further. If you carry on working on your branch after fast forward merging it back into the branch you were working on then your erasing B's previous history each time. You're really creating a new branch each time after fast forward commit to source then commit to branch. You end up with when you fast forward commit is lots of branches/merges that you can see in the history and structure but without the ability to determine what the name of that branch was or if what looks like two separate branches is really the same branch.
0 1 2 3 4 (B1) /-\ /-\ /-\ /-\ / ---- - - - - \-/ \-/ \-/ \-/ \ 5 6 7 8 9 (B2)
1 to 3 and 5 to 8 are structural branches that show up if you follow the history for either 4 or 9. There's no way in git to know which of this unnamed and unreferenced structural branches belong to with of the named and references branches as the end of the structure. You might assume from this drawing that 0 to 4 belongs to B1 and 4 to 9 belongs to B2 but apart from 4 and 9 was can't know which branch belongs to which branch, I've simply drawn it in a way that gives the illusion of that. 0 might belong to B2 and 5 might belong to B1. There are 16 different possibilies in this case of which named branch each of the structural branches could belong to. This is assuming that none of these structural branches came from a deleted branch or as a result of merging a branch into itself when pulling from master (the same branch name on two repos is infact two branches, a separate repository is like branching all branches).
There are a number of git strategies that work around this. You can force git merge to never fast forward and always create a merge branch. A horrible way to preserve branch history is with tags and/or branches (tags are really recommended) according to some convention of your choosing. I realy wouldn't recommend a dummy empty commit in the branch you're merging into. A very common convention is to not merge into an integration branch until you want to genuinely close your branch. This is a practice that people should attempt to adhere to as otherwise you're working around the point of having branches. However in the real world the ideal is not always practical meaning doing the right thing is not viable for every situation. If what you're doing on a branch is isolated that can work but otherwise you might be in a situation where when multiple developers are working one something they need to share their changes quickly (ideally you might really want to be working on one branch but not all situations suit that either and generally two people working on a branch is something you want to avoid).
The following implements git equivalent of svn log –stop-on-copy and can also be used to find branch origin.
途径
- Get head for all branches
- collect mergeBase for target branch each other branch
- git.log and iterate
- Stop at first commit that appears in the mergeBase list
Like all rivers run to the sea, all branches run to master and therefore we find merge-base between seemingly unrelated branches. As we walk back from branch head through ancestors, we can stop at the first potential merge base since in theory it should be origin point of this branch.
笔记
- I haven't tried this approach where sibling and cousin branches merged between each other.
- I know there must be a better solution.
You could use the following command to return the oldest commit in branch_a, which is not reachable from master:
git rev-list branch_a ^master | tail -1
Perhaps with an additional sanity check that the parent of that commit is actually reachable from master…
You can examine the reflog of branch A to find from which commit it was created, as well as the full history of which commits that branch pointed to. Reflogs are in .git/logs
.
I believe I've found a way that deals with all the corner-cases mentioned here:
branch=branch_A merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1) git merge-base $merge^1 $merge^2
Charles Bailey is quite right that solutions based on the order of ancestors have only limited value; at the end of the day you need some sort of record of "this commit came from branch X", but such record already exists; by default 'git merge' would use a commit message such as "Merge branch 'branch_A' into master", this tells you that all the commits from the second parent (commit^2) came from 'branch_A' and was merged to the first parent (commit^1), which is 'master'.
Armed with this information you can find the first merge of 'branch_A' (which is when 'branch_A' really came into existence), and find the merge-base, which would be the branch point 🙂
I've tried with the repositories of Mark Booth and Charles Bailey and the solution works; how couldn't it? The only way this wouldn't work is if you have manually changed the default commit message for merges so that the branch information is truly lost.
For usefulness:
[alias] branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'
Then you can do ' git branch-point branch_A
'.
Enjoy 😉