我怎样才能看到另一个分支是从哪个分支分叉出来的?

发布于 2024-10-14 11:57:40 字数 1235 浏览 2 评论 0原文

我的 git 存储库有三个分支:develstablecustomers/acme_patches。很久以前,stable 是从 devel 分叉出来的,所有的错误修复都发生在 stable 中。 stable 时不时地会合并回 develcustomers/acme_patches 是一个包含一些特定于客户的补丁的分支。该分支未合并到 develstable 中。

用一点 ASCII 艺术来说明这个场景:

            o---o---o          customers/acme_patches?
           /
  o---o---1---o---o---o        stable
 /     \           \
o---o---o---2---o---o---o---o  devel
             \
              o---o---o        customers/acme_patches?

现在我想知道:

customers/acme_patches 是从 develstable 分叉出来的?我只知道它过去是从其中一个分叉出来的,但我不知道是哪一个。例如,上图中可能是提交 12

我一直在使用 git log --oneline --graphgitk 但自从 customers/acme_patches 在几百次提交前被分叉,很难遵循所绘制的线条。

是否有一个快速命令(一个小脚本也可以)可以以某种方式向后跟踪 customers/acme_patches 中的提交,找到带有两个子项(分叉点)的第一个提交,然后确定是否该提交是在 stable 中还是在 devel 中完成的?

在最好的情况下,我可以执行类似的命令(请原谅提示,我使用的是 Windows):

C:\src> git fork-origin customers/acme_patches
stable

My git repository has three branches, devel, stable and customers/acme_patches. A long time ago, stable was forked from devel, and all the bugfixing takes place in stable. Every now and then, stable is merged back into devel. customers/acme_patches is a branch with a few customer-specific patches. The branch wasn't merged into either of devel and stable.

A bit of ASCII art to illustrate the scenario:

            o---o---o          customers/acme_patches?
           /
  o---o---1---o---o---o        stable
 /     \           \
o---o---o---2---o---o---o---o  devel
             \
              o---o---o        customers/acme_patches?

Now I wonder:

What branch was customers/acme_patches forked from - devel or stable? I only know that it was forked off one of them in the past, but I don't know which. E.g. it might have been commit 1 or 2 in the above diagram.

I've been playing around with git log --oneline --graph and gitk but since customers/acme_patches was forked a few hundred commits ago, it's hard to follow the lines being drawn.

Is there maybe a quick command (a little script is fine, too) which can somehow follow the commits in customers/acme_patches backwards to find the first commit with two children (the fork point) and then determines whether that commit was done in stable or in devel?

In the best case, I could just execute something like (excuse the prompt, I'm on Windows):

C:\src> git fork-origin customers/acme_patches
stable

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

乖乖兔^ω^ 2024-10-21 11:57:40

使用 git 1.9/2.0(2014 年第 1 季度),您可以使用 git merge-base --fork-point 来根据 Git 请求最佳共同祖先。

您可以看到该新选项:


自从 提交来自 John Keeping (johnkeeping)git rebase 可以使用相同的新 --fork-point选项,如果您需要将 customers/acme_patches 等分支重新设置为 devel,该选项会派上用场。
(我并不是说这在您的特定场景中有意义)


注意:Git 2.16(2018 年第一季度)确实澄清并增强了“merge-base --fork-point”的文档,因为它很清楚它计算了什么,但没有计算原因/目的。

请参阅 提交 6d1700b(2017 年 11 月 9 日),作者:Junio C Hamano (gitster)
(由 Junio C Hamano -- gitster -- 合并于 提交 022dd4a,2017 年 11 月 27 日)

merge-base --fork-point 文档:阐明示例和故障模式

用于解释--fork-point模式的图解历史
命名三个关键点提交 B3、B2 和 B1(从最旧到最旧)
最新的,很难阅读。
将它们重新标记为 B0、B1、B2。
还说明使用 --fork-point 工具进行变基后的历史记录。

文本中已经提到了 reflog 的使用,但描述中没有提到
明确我们试图通过使用引用日志获得什么好处
澄清一下,它是为了找到已知位于最顶端的提交
远程跟踪分支

这反过来又要求用户了解基本假设的后果,即,reflog 条目过期将导致无法确定哪些提交位于远程跟踪分支的顶端,并且当有疑问时我们会失败(而不是给出随机的提交)甚至没有错误的结果
警告)。
另一个限制是,如果您不是从远程跟踪分支的尖端而是从中间分叉,它将没有用。
描述一下它们。

因此,文档现在如下:

处理使用 git checkout -b 创建的 topic 分支后
topic origin/master,远程跟踪分支的历史记录
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 分支是在它之上启动的
origin/master 位于 B0 时,您构建了三个提交,D0、D1,
和D,在它上面。
想象一下,您现在想要在更新的 origin/master 之上重新调整您在 topic 上所做的工作。

在这种情况下,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

将在 B 之上重播 D0、D1 和 D,以创建此的新历史
形状:

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

需要注意的是,您的存储库中较旧的引用日志条目可能会被
已被 git gc 过期。
如果 B0 不再出现在远程跟踪分支 origin/master 的 reflog 中,则 --fork-point 模式显然无法找到它并失败,从而避免给出随机且无用的结果(例如 B0 的父级,就像不带 --fork-point 选项的相同命令一样)。

此外,您使用 --fork-point 模式的远程跟踪分支
with 必须是您的主题从其尖端分叉出来的那个。
如果您从比提示更旧的提交分叉,此模式将找不到分叉点(想象一下在上面的示例历史记录中 B0 不存在,
origin/master 从 B1 开始,移动到 B2,然后移动到 B,然后你分叉了
origin/master 为 B1 时,您在 origin/master^ 的主题;的形状
历史记录将与上面相同,没有 B0 和父级
B1 的内容是 git merge-base origin/master topic 正确找到的内容,
--fork-point 模式不会,因为它不是其中之一
过去位于 origin/master 顶端的提交)。

With git 1.9/2.0 (Q1 2014), you can use git merge-base --fork-point to ask for the best common ancestor according to Git.

You can see that new option:


And since commit ad8261d from John Keeping (johnkeeping), git rebase can use that same new --fork-point option, which can come in handy should you need to rebase a branch like customers/acme_patches onto devel.
(I am not saying this would make sense in your specific scenario)


Note: Git 2.16 (Q1 2018) does clarify and enhance documentation for "merge-base --fork-point", as it was clear what it computed but not why/what for.

See commit 6d1700b (09 Nov 2017) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit 022dd4a, 27 Nov 2017)

merge-base --fork-point doc: clarify the example and failure modes

The illustrated history used to explain the --fork-point mode
named three keypoint commits B3, B2 and B1 from the oldest to the
newest, which was hard to read.
Relabel them to B0, B1, B2.
Also illustrate the history after the rebase using the --fork-point facility was made.

The text already mentions use of reflog, but the description is not
clear what benefit we are trying to gain by using reflog.
Clarify that it is to find the commits that were known to be at the tip of
the remote-tracking branch
.
This in turn necessitates users to know the ramifications of the underlying assumptions, namely, expiry of reflog entries will make it impossible to determine which commits were at the tip of the remote-tracking branches and we fail when in doubt (instead of giving a random and incorrect result without even
warning).
Another limitation is that it won't be useful if you did not fork from the tip of a remote-tracking branch but from in the middle.
Describe them.

So the documentation now reads:

After working on the topic branch created with git checkout -b
topic origin/master
, the history of remote-tracking branch
origin/master may have been rewound and rebuilt, leading to a
history of this shape:

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

where origin/master used to point at commits B0, B1, B2 and now it
points at B, and your topic branch was started on top of it back
when origin/master was at B0, and you built three commits, D0, D1,
and D, on top of it.
Imagine that you now want to rebase the work you did on the topic on top of the updated origin/master.

In such a case, git merge-base origin/master topic would return the
parent of B0 in the above picture, but B0^..D is not the range of
commits you would want to replay on top of B (it includes B0, which
is not what you wrote; it is a commit the other side discarded when
it moved its tip from B0 to B1).

git merge-base --fork-point origin/master topic is designed to help in such a case.
It takes not only B but also B0, B1, and B2 (i.e. old tips of the remote-tracking branches your repository's reflog knows about) into account to see on which commit your topic branch was built and finds B0, allowing you to replay only the commits on your topic, excluding the commits the other side later
discarded.

Hence

$ fork_point=$(git merge-base --fork-point origin/master topic)

will find B0, and

$ git rebase --onto origin/master $fork_point topic

will replay D0, D1 and D on top of B to create a new history of this
shape:

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

A caveat is that older reflog entries in your repository may be
expired by git gc.
If B0 no longer appears in the reflog of the remote-tracking branch origin/master, the --fork-point mode obviously cannot find it and fails, avoiding to give a random and useless result (such as the parent of B0, like the same command without the --fork-point option gives).

Also, the remote-tracking branch you use the --fork-point mode
with must be the one your topic forked from its tip.
If you forked from an older commit than the tip, this mode would not find the fork point (imagine in the above sample history B0 did not exist,
origin/master started at B1, moved to B2 and then B, and you forked
your topic at origin/master^ when origin/master was B1; the shape of
the history would be the same as above, without B0, and the parent
of B1 is what git merge-base origin/master topic correctly finds,
but the --fork-point mode will not, because it is not one of the
commits that used to be at the tip of origin/master).

残疾 2024-10-21 11:57:40

好吧,这个答案可能没有完美的解决方案。我的意思是,在 git 中没有相当于 fork-origin 的东西(据我所知)。
由于 stable 分支已合并到 devel 中,因此您的 acme_patches(从 1 开始)同时位于 devel上>稳定分支。

你可能会做的是:

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

如果你有 stable 但没有 devel,或者有 devel 但不稳定,那么你就知道它来自哪里。

例如,在情况 2 中,您将拥有,

$ 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

因为它现在在两个分支上(因为从稳定版合并到开发版)

Well, there is probably no perfect solution to this answer. I mean there is no fork-origin equivalent in git (to my knowledge).
Because the stable branch is merged into devel, your acme_patches (from 1) is on both devel and stable branch.

What you could possibly do is:

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

If you have stable and not devel, or devel and not stable, then you know where it comes from.

For example, in the case 2, you would have

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

while in case 1 you would have

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

As it's now on both branches (because of the merge from stable to dev)

我爱人 2024-10-21 11:57:40

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

例如,您可以尝试 gitk --left-rightcustomers/acme_patches...stable (注意三个点!)。这将显示这些分支中而不是合并基础中的所有提交。使用 --left-right 将根据每个提交所在的分支用左箭头或右箭头标记每个提交 - 如果它们位于customers/acme_patches,则使用左箭头;如果它们处于稳定状态,则使用右箭头。

可能还添加 --date-order 我发现有时有助于理解输出。

(您可以将此语法与 git log --graph 结合使用,而不是与 gitk 结合使用,但恕我直言,在这种情况下,可视化图形显示有了很大的改进)。

well, git merge-base customers/acme_patches stable should show the common ancestor of those two branches.

You could try, for instance, gitk --left-right customers/acme_patches...stable (note three dots!). This will show all the commits that are in those branches and not in the merge base. Using --left-right will mark each commit with a left or right arrow according to which branch they are in- a left arrow if they are in customers/acme_patches and a right arrow if they are in stable.

Possibly also add --date-order which I've found sometimes helps make sense of the output.

(You can use this syntax with git log --graph rather than gitk but imho this is a case where the visual graph display is a big improvement).

罪歌 2024-10-21 11:57:40

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

git_branch_contains() {
    local b=$1
    local c=$2
    IFS_=$IFS
    IFS=

这是测试它们的脚本(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 之间的数字,用于指定要测试的情况(拓扑)。

\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):

像这样使用它,

在哪里NUMBER 是 1 到 11 之间的数字,用于指定要测试的情况(拓扑)。

Not sure if it covers all cases, but here's the functions that I came up with:

git_branch_contains() {
    local b=$1
    local c=$2
    IFS_=$IFS
    IFS=

And here's the script to test them (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

Use it like so,

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

Where NUMBER is a number from 1 to 11 to specify which case (topology) to test.

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

And here's the script to test them (git-upstream-branch-test.sh):

Use it like so,

Where NUMBER is a number from 1 to 11 to specify which case (topology) to test.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文