可视化 git 分支依赖

Visualize git branch dependency

有什么工具可以告诉我一个特定的分支被合并到了哪些分支中?比如"A"已经合并到"B"和"C",但没有合并到"D",那么如何输出"B"和"C"?我只需要分支名称。

如果您指的是图形工具,gitk 似乎是一个很好的起点。

或者,您可能更喜欢 CLI,它对某些需求非常有用,请尝试一下:

git log --oneline --decorate --simplify-by-decoration --graph --all

并更具体地解决问题 "How to know which branches are merged into branch A?"

git branch --contains A

重要的是要意识到分支 names 实际上没有任何意义。

分支名称只是一个指向提交的标签或指针。将它们视为可以粘贴到提交上的黄色便签之一,甚至 sticky arrows。重要的是提交。提交的标识是它的哈希 ID。提交是永久1且不可更改的,并且每个提交记录其直接parent(大多数提交)或parents(合并2个或更多)的哈希ID犯罪)。这就是生成 commit graph 的原因,除了您始终可以 add 之外,它与提交一样永久且不可更改他们自己。

图本身决定了存储库的实际 "branchiness":

(older commits towards the left, newer ones towards the right)

          o--A
         /
...--o--o
         \
          o--o--B

图表的这个特定部分显示了两个分支,以两个 提示提交 AB 结束。添加一个新的提交过去 A 给我们 C,它记得 A 作为它的 parent:

          o--A--C
         /
...--o--o
         \
          o--o--B

如果我们现在添加一个合并提交——我们称它为M for "merge"——它的parent是两者 C B,我们得到:

          o--A--C
         /       \
...--o--o         M
         \       /
          o--o--B

它需要两个标签——两个 sticky-note 箭头——来记住 AB,或者同时记住 CB,但是现在我们有 M,我们可以处理其中一个标签,只保留一个 sticky-note 箭头指向 M 本身:

          o--A--C
         /       \
...--o--o         M   <-- branch
         \       /
          o--o--B

所以这就是分支名称的含义和作用:它是一个指针,指向 一个单一的 提交,Git 将认为是 提示 一些backwards-looking 提交链的提交。由于 Git 向后工作,从这个提示提交开始,Git 将遍历合并的 all 段,以找到可以通过向后工作达到的所有提交。由于 M 有两个 parents CB,Git 将访问提交 C 然后 A 等等,还有 B 然后所有提交到 B.

的左侧

当然,我们可以让标签指向 A and/or B and/or C:

                .... <-- tag: v1.0
               .
              .
          o--A--C
         /       \
...--o--o         M   <-- branch
         \       /
          o--o--B   <-- feature

如本图所示,标签不必是 分支 名称。分支名称的特殊功能是通过获取 "on" 分支,使用 git checkout <em>name</em>,任何 new 我们所做的提交将自动更新名称以指向新的提交。新提交将指向之前的提交。

这也是 git branch --contains 背后的概念。因为提交 B 可以从 提交 M 到达 ,从 M 开始并向后退一步,git branch --contains <em>specifier-for-<code>B 将打印名称 branch。该名称指向 MM 到达 B。更一般地说,Git 反复询问和回答问题:

  • 是提交 X 提交 Y 的祖先吗?

With git branch --contains <em>anything that finds some commit</em>, Git 询问关于每个 branch-tip 提交每个分支名称:

target=$(git rev-parse $argument) || exit 1

git for-each-ref --format='%(refname)' refs/heads |
    while read fullbranchname; do
        branchtip=$(git rev-parse $fullbranchname)
        if git merge-base --is-ancestor $branchtip $target; then
            echo "branch ${fullbranchname#refs/heads/} contains $argument"
        fi
    done

如果$argument是像branch这样的分支名称,将其变成hash ID的操作选择commit M的hash ID。然后把refs/heads/feature变成hash ID的操作选择了B的hash ID,git merge-base --is-ancestor回答了问题is commit B an ancestor of commit M(确实如此)。2

您要问的有点复杂:对于每个分支名称,您不仅要确定该分支的尖端提交是否在某个选定分支的尖端提交之前(如 git branch --contains 所做的那样),还有每个这样的分支名称 ,哪些是 more-ancestor-y,哪些是 less-ancestor-y。这个问题可以回答,但是只在某些情况下

例如,考虑这张图:

...--o--o--o--o---M1--M2-o   <-- master
      \     \    /   /
       \     o--o   /  <-- feature1
        \          /
         o--o--o--o   <-- feature2

我认为您希望 feature2 被提及为 "after" feature1,因为引入 feature2 提示的合并提交在合并提交之后带来 feature1.3 的提示,但合并并不总是将 两个 提交在一起。4 考虑:

          o--o--o   <-- feature1
         /       \
...--o--o--o--o---M1--o   <-- master
         \       /
          o--o--o   <-- feature2

现在没有明显的顺序:feature1feature2 通过一次提交同时引入。

(当然,如果我们删除两个feature名字之一,就解决了问题。如果我们删除两个名字,那就是even-more解决了!)


1您可以删除提交,方法是删除提交的所有访问权限。 Git 从已知名称(分支名称和标记名称,以及所有其他形式的引用)开始,并通过图表向后工作。如果此 working-backwards 达到提交,则必须保留该提交。如果不是,则提交符合删除条件。还有额外的ames,除了那些明显的,它们通常使提交至少多活 30 天。

2我们可以消除 git rev-parse 调用,因为 git merge-base 接受分支名称代替哈希 ID。当给定一个名称时,它只会找到提交哈希,就像 git rev-parse 一样。我把它们放在这里主要是为了说明。

3您必须编写自己的代码来生成这种排序。请注意,这并不像比较各个分支的尖端提交那么简单;我们需要知道哪些合并(如果有的话)引入了这些提示,并比较这些合并。我们还必须考虑通过将其尖端嵌入 non-merge-y 子图中而形成的分支:

             ..... <-- branch3
            .
...--o--o--o--o    <-- branch1
         .
          ........ <-- branch2

在这里,我想我们要声明branch2 < branch3

4即使在 pair-at-a-time 合并的情况下,你可以重复合并一些提交和一些 main-line,这给问题带来了麻烦(有 种可能的顺序,但您必须选择某种图形遍历算法才能在此处选择总顺序)。本质上,请记住,当您使用 DAG, you're dealing with a poset.