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

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

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

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


当前回答

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

其他回答

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

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

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

git rev-list branch_a ^master | tail -1

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

这并不是一个解决问题的方法,但我认为当我有一个长寿的分支时,我使用的方法值得注意:

在创建分支的同时,我还创建了一个名称相同但后缀为-init的标记,例如feature-branch和feature-branch-init。

(这是一个很难回答的问题,这有点奇怪!)

目的:这个答案测试了在这个线程中给出的各种答案。

测试库

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

正如查尔斯·贝利指出的那样,这种解决方案非常脆弱。

如果你将branch_A合并为master,然后将master合并为branch_A,而不干预提交,那么lindes的解决方案只给你最近的第一次分歧。

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

这实际上都归结为gits缺乏hg所谓的命名分支。博主jhw在他的文章《为什么我更喜欢Mercurial而不是Git》和他的后续文章《More On Mercurial vs. Git (with Graphs!)》中称这些为谱系vs.家族。我建议人们阅读它们,看看为什么一些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 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 当发现提交排除(使用'{插入符号}')时,只跟随 第一个父节点在看到合并提交时提交。 这可用于查找主题分支中的更改集 从它与远分支的分歧点开始 任意的合并可以是有效的主题分支更改。