我该如何查看另一个分支是从哪个分支派生的?

42
我的 Git 储存库有三个分支,`devel`、`stable` 和 `customers/acme_patches`。很久以前,`stable` 是从 `devel` 分叉出来的,并且所有的故障修复都在 `stable` 中进行。不时地,`stable` 会合并回 `devel`。`customers/acme_patches` 是一个带有一些特定于客户的补丁的分支。该分支没有合并到 `devel` 或 `stable` 中。
现在我想知道:`customers/acme_patches` 是从哪个分支分叉出来的,是 `devel` 还是 `stable`?我只知道它过去曾从其中一个分支分叉出来,但我不知道是哪个。例如,在上面的图表中,它可能是提交 1 或提交 2。
我已经尝试了 `git log --oneline --graph` 和 `gitk`,但由于 `customers/acme_patches` 是几百个提交之前分叉的,所以很难跟踪绘制的线条。是否有快速的命令(小脚本也可以),可以追溯 `customers/acme_patches` 中的提交以找到具有两个子节点的第一个提交(分叉点),然后确定该提交是在 `stable` 中还是在 `devel` 中完成的?
最理想的情况下,我只需要执行类似如下的命令(请原谅我的提示,我在 Windows 上):
C:\src> git fork-origin customers/acme_patches
stable

4
从 Git 1.9/2.0(2014年第一季度开始),可以使用git merge-base --fork-point。请参见下面的答案 - VonC
4个回答

33
使用Git 1.9/2.0(2014年第一季度)中的git merge-base --fork-point命令,您可以请求Git查找最佳公共祖先。您可以在以下链接中了解该新选项的详细信息:“如何在有人将rebase或reset推送到已发布分支后进行恢复/同步?”和“如何处理已经被rebase的公共存储库?”。请注意,保留HTML标签,但不要写出解释。

自从John Keeping (johnkeeping)提交了commit ad8261dgit rebase就可以使用相同的新选项--fork-point,如果您需要将分支customers/acme_patches合并到devel,这将非常方便。
(我不是在说这在您特定的情况下有意义)


注意:Git 2.16(2018年第一季度)确实澄清和增强了“merge-base --fork-point”的文档,因为它清楚地说明了计算的内容,但不清楚其原因/用途。
请参见commit 6d1700b(由Junio C Hamano(gitster于2017年11月9日提交)。
(由Junio C Hamano -- gitster --commit 022dd4a中合并,于2017年11月27日)

merge-base --fork-point doc: 澄清示例和失败模式

用于解释--fork-point模式的图示历史从最旧到最新命名了三个关键点提交B3、B2和B1,这很难阅读。
将它们重新标记为B0、B1、B2。
还要说明使用了--fork-point功能后重置基础后的历史记录。

文本已经提到了引用日志的使用,但描述不清楚我们试图通过使用引用日志获得什么好处
澄清它是为了找到已知在远程跟踪分支末端的提交
这反过来需要用户知道基本假设的影响,即,引用日志条目的过期将使确定哪些提交位于远程跟踪分支末端成为不可能,而当存在疑问时我们会失败(而不是给出随机且不正确的结果,甚至没有警告)。
另一个限制是,如果您不是从远程跟踪分支的末端而是从中间派生的,那么它将没有用处。
对它们进行描述。

因此,文档现在的内容如下:

在使用git checkout -b topic origin/master创建topic分支并进行操作后,远程追踪分支origin/master的历史记录可能已经被倒回和重建,导致这种形式的历史记录:

                 o---B2
                /
---o---o---B1--o---o---o---B (origin/master)
        \
         B0
          \
           D0---D1---D (topic)

origin/master 指向提交 B0、B1、B2 时,现在它指向 B,你的 topic 分支是建立在它指向 B0 的时候开始的,并且你在上面构建了三个提交 D0、D1 和 D。现在你想将你在 topic 上的工作变基到更新的 origin/master 上。
这种情况下,git merge-base origin/master topic 将返回上图中 B0 的父提交,但是 B0^..D 不是你想要重放在 B 之上的提交范围(它包括了 B0,但你没有写过它;它是另一方在将其提示从 B0 移动到 B1 时丢弃的提交)。
git merge-base --fork-point origin/master topic 是为了帮助这种情况而设计的。它不仅考虑 B,还考虑 B0、B1 和 B2(即你的存储库 reflog 知道的远程跟踪分支的旧提示)来查看你的主题分支是在哪个提交上建立的,并找到 B0,使你只能重放你的主题上的提交,而排除其他方后来丢弃的提交。
$ fork_point=$(git merge-base --fork-point origin/master topic)

将找到B0,并且
$ git rebase --onto origin/master $fork_point topic

“将 D0、D1 和 D 放在 B 之上,以创建此形状的新历史记录:”
         o---B2
        /
---o---o---B1--o---o---o---B (origin/master)
    \                   \
     B0                  D0'--D1'--D' (topic - updated)
      \
       D0---D1---D (topic - old)

一个警告是你仓库中较老的reflog条目可能会被git gc过期。
如果B0不再出现在远程跟踪分支origin/master的reflog中,那么--fork-point模式显然无法找到它并且失败,避免给出随机和无用的结果(例如没有--fork-point选项的相同命令的父级如B0)。
此外,您使用--fork-point模式的远程跟踪分支必须是您的主题从其尖端分叉出来的那个。
如果您从比尖端旧的提交中分叉,此模式将无法找到分叉点(想象一下在上面的示例历史记录中B0不存在,origin/master从B1开始,移动到B2然后B,当您的主题在origin/master^处分叉时,origin/master是B1;历史记录的形状与上面相同,没有B0,而B1的父级是git merge-base origin/master topic正确找到的,但--fork-point模式不会找到它,因为它不是曾经在origin/master的顶部的提交之一)。

7

嗯,对于这个问题可能没有完美的解决方案。我的意思是,据我所知,在git中没有fork-origin等效物。 因为stable分支已合并到devel中,所以您的acme_patches(来自1)在develstable分支上都存在。

您可能可以尝试以下方法:

git branch --contains $(git merge-base customers/acme_patches devel stable)

如果你有稳定版本而没有开发版,或者有开发版而没有稳定版本,那么你就知道它来自哪里了。
例如,在第二种情况下,你会有:
$ git branch --contains $(git merge-base customers/acme_patches devel stable)
customers/acme_patches
devel

在情况1中,您将会有:
$ git branch --contains $(git merge-base customers/acme_patches devel stable)
customers/acme_patches
devel
stable

由于从稳定分支合并到开发分支,现在它已经存在于两个分支中。


+1:这非常接近我需要的! 不幸的是,第一种情况仍然需要修复。 是否有可能“回到过去”,确定由git merge-base标识的提交点时,提交只在一个分支上完成? 有点像运行git branch --contains,但使用提交时的树状态。 也许可以通过临时使用git reset --hard来实现? 嗯,听起来很暴力... - Frerich Raabe
你可以轻松地“回到过去”到那个提交,只需使用 git checkout ... 命令即可。但这并不会告诉你父提交所在的分支的名称。尽管我记得惯例是首先命名的父提交是你所在的分支,其他提交是合并的分支。 - araqnid

3

好的,git merge-base customers/acme_patches stable 应该显示这两个分支的共同祖先。

你可以尝试使用 gitk --left-right customers/acme_patches...stable 命令(注意三个点!)。这将显示那些在这些分支中而不在合并基础中的所有提交。使用 --left-right 将根据它们所在的分支为每个提交打上左箭头或右箭头- 如果它们在 customers/acme_patches 分支中,则为左箭头;如果它们在 stable 分支中,则为右箭头。

可能还需要添加 --date-order,我发现有时这有助于理解输出结果。

(你可以将此语法与 git log --graph 一起使用,而不是使用 gitk,但我认为在这种情况下,视觉图形显示是一个很大的改进)。


2

我不确定它是否涵盖了所有情况,但这是我想出的函数:

git_branch_contains() {
    local b=$1
    local c=$2
    IFS_=$IFS
    IFS=$'\n'
    local branches=($(git branch --contains "$c" | sed -E 's/^(\*| ) //'))
    IFS=$IFS_
    for b2 in "${branches[@]:+${branches[@]}}"; do
        if [ "$b2" = "$b" ]; then
            return 0
        fi
    done
    return 1
}

git_upstream_branch() {
    local b=$1
    local c1=$(git merge-base --fork-point master "$b") || local c1=
    local c2=$(git merge-base --fork-point dev "$b") || local c2=
    if ! [ "$c1" ]; then
        echo dev
        return
    fi
    if ! [ "$c2" ]; then
        echo master
        return
    fi
    local fp
    if git merge-base --is-ancestor "$c1" "$c2"; then
        fp=$c2
    else
        fp=$c1
    fi
    if git_branch_contains master "$fp" && ! git_branch_contains dev "$fp"; then
        echo master
    else
        echo dev
    fi
}

以下是用于测试它们的脚本(git-upstream-branch-test.sh):

#!/usr/bin/env bash
set -eu

. git-upstream-branch.sh

git_commit() {
    if ! [ "${commit_i:-}" ]; then
        commit_i=0
    fi
    (( commit_i++ )) || true
    echo "$commit_i" > "$commit_i"
    git add "$commit_i"
    git commit -qm "c$commit_i"
}

git_merge() {
    if ! [ "${merge_i:-}" ]; then
        merge_i=0
    fi
    (( merge_i++ )) || true
    git merge -m "$merge_i" $1
}

A_TOPOLOGY=${1:-}

mkdir git-upstream-branch-test-repo
cd git-upstream-branch-test-repo
git init -q
if [ "$A_TOPOLOGY" = 10 ]; then
    git_commit
    git_commit
    git checkout -qb dev
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    c=$(git rev-parse HEAD)
    git_commit
    git_commit
    git checkout -q dev
    git checkout -qb t1
    git_commit
    git_commit
    git checkout -q dev
    git_commit
    git_commit
    git rebase --onto "$c" dev t1
elif [ "$A_TOPOLOGY" = 11 ]; then
    git_commit
    git_commit
    git checkout -qb dev
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    git checkout -q dev
    c=$(git rev-parse HEAD)
    git_commit
    git_commit
    git checkout -q master
    git checkout -qb t1
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    git rebase --onto "$c" master t1
else
    git_commit
    git_commit
    git checkout -qb dev
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    if [ "$A_TOPOLOGY" = 4 ] || [ "$A_TOPOLOGY" = 5 ] || [ "$A_TOPOLOGY" = 6 ]; then
        git_merge dev
        git_commit
        git_commit
        git checkout -q dev
        git_commit
        git_commit
        git checkout -q master
    elif [ "$A_TOPOLOGY" = 7 ] || [ "$A_TOPOLOGY" = 8 ] || [ "$A_TOPOLOGY" = 9 ]; then
        git checkout -q dev
        git_merge master
        git_commit
        git_commit
        git checkout -q master
        git_commit
        git_commit
    fi
    git checkout -qb t1
    git_commit
    git_commit
    git checkout -q master
    git_commit
    git_commit
    if [ "$A_TOPOLOGY" = 2 ] || [ "$A_TOPOLOGY" = 5 ] || [ "$A_TOPOLOGY" = 8 ]; then
        git_merge dev
    elif [ "$A_TOPOLOGY" = 3 ] || [ "$A_TOPOLOGY" = 6 ] || [ "$A_TOPOLOGY" = 9 ]; then
        git checkout -q dev
        git_merge master
    fi
fi
git --no-pager log --oneline --graph --decorate --all
git_upstream_branch t1

像这样使用它:

$ rm -rf git-upstream-branch-test-repo && ./git-upstream-branch-test.sh NUMBER

NUMBER是一个数字,范围从1到11,用于指定要测试的案例(拓扑结构)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接