查找一个Git提交是来自哪个分支

880

有没有办法通过给定的SHA-1哈希值找出提交来自哪个分支?

如果您能告诉我如何使用Ruby Grit完成此操作,将获得额外的奖励。


18
下面的不同方法是实用的、有用的、可行的方式来推断一个可能的答案,但需要注意的是,在git中问题本身就是一种误解,提交并不是来自分支。分支来来去去,它们移动,只有提交才代表真正的存储库历史记录。再次强调,这并不是说以下解决方案不好。只是要知道没有一个给出完全可靠的答案,这是git的设计所无法获得的。(一个简单的例子是已删除的分支:我创建了一个分支,我提交了两次,我合并到另一个分支,我删除了第一个分支。那么这个提交“来”自哪里呢?) - Romain Valeri
2
我有一个案例,其中我明确地从标签中拉取深度为1的浅克隆。这很便宜、容易和高效...直到现在,它完美地完成了我想要的一切。现在我想知道标签所在的分支,但我卡住了,至少在这个细节上是这样。有时候你不能回家,哈哈 - Paul Hodges
17个回答

2
如果OP试图确定在创建特定提交时分支遍历的历史记录(“查找给定SHA-1哈希值的提交来自哪个分支”),那么如果没有reflog,则Git对象数据库中没有任何记录显示命名分支绑定到什么提交历史记录。
(我将其发布为答案,以回复评论。)
希望这个脚本能说明我的观点:
rm -rf /tmp/r1 /tmp/r2; mkdir /tmp/r1; cd /tmp/r1
git init; git config user.name n; git config user.email e@x.io
git commit -m"empty" --allow-empty; git branch -m b1; git branch b2
git checkout b1; touch f1; git add f1; git commit -m"Add f1"
git checkout b2; touch f2; git add f2; git commit -m"Add f2"
git merge -m"merge branches" b1; git checkout b1; git merge b2
git clone /tmp/r1 /tmp/r2; cd /tmp/r2; git fetch origin b2:b2
set -x;
cd /tmp/r1; git log --oneline --graph --decorate; git reflog b1; git reflog b2;
cd /tmp/r2; git log --oneline --graph --decorate; git reflog b1; git reflog b2;

输出结果显示没有任何方法知道提交'Add f1'是来自/tmp/r2的分支b1还是b2。
(输出结果的最后几行在此处)
+ cd /tmp/r1
+ git log --oneline --graph --decorate
*   f0c707d (HEAD, b2, b1) merge branches
|\
| * 086c9ce Add f1
* | 80c10e5 Add f2
|/
* 18feb84 empty
+ git reflog b1
f0c707d b1@{0}: merge b2: Fast-forward
086c9ce b1@{1}: commit: Add f1
18feb84 b1@{2}: Branch: renamed refs/heads/master to refs/heads/b1
18feb84 b1@{3}: commit (initial): empty
+ git reflog b2
f0c707d b2@{0}: merge b1: Merge made by the 'recursive' strategy.
80c10e5 b2@{1}: commit: Add f2
18feb84 b2@{2}: branch: Created from b1
+ cd /tmp/r2
+ git log --oneline --graph --decorate
*   f0c707d (HEAD, origin/b2, origin/b1, origin/HEAD, b2, b1) merge branches
|\
| * 086c9ce Add f1
* | 80c10e5 Add f2
|/
* 18feb84 empty
+ git reflog b1
f0c707d b1@{0}: clone: from /tmp/r1
+ git reflog b2
f0c707d b2@{0}: fetch origin b2:b2: storing head

对于两种情况,git log 80c10e5..HEAD --ancestry-path --merges --oneline --color | tail -n 1git log 086c9ce..HEAD --ancestry-path --merges --oneline --color | tail -n 1 命令的输出是什么? - Eugen Konkov
当运行命令时,SHAs当然会随之改变。为了重新拼写您的命令,我在新的运行中使用了HEAD^1HEAD^2。命令和输出如下:$ git log HEAD^1..HEAD --ancestry-path --merges --oneline --color | tail -n 1,它产生了376142d merge branches,以及$ git log HEAD^2..HEAD --ancestry-path --merges --oneline --color | tail -n 1,它产生了376142d merge branches。这显示了合并提交摘要,这可以在创建合并时被覆盖,可能混淆合并的分支历史。 - qneill

2
作为一个实验,我制作了一个 post-commit hook,可以将当前检出的分支信息存储在提交元数据中。我还稍微修改了 gitk 以显示这些信息。
你可以在这里查看:https://github.com/pajp/branch-info-commits

1
但是,如果您想添加元数据,为什么不使用git notes,而不是搞乱提交的头文件呢?请参见https://dev59.com/KG435IYBdhLWcg3wyzQJ#5213110,https://dev59.com/61rUa4cB1Zd3GeqPou9m#7299044或http://stackoverflow.com/questions/7101158/is-git-notes-the-intended-way-to-add-category-style-information-to-a-commit/7102923#7102923。 - VonC
说实话,我在写那篇文章的时候并不知道 git notes 的存在。是的,使用 git notes 来实现同样的功能可能是一个更好的主意。 - pajp

1

TL;DR:

如果你关心Shell退出状态,请使用以下内容:

  • branch-current - 当前分支的名称
  • branch-names - 干净的分支名称(每行一个)
  • branch-name - 确保只返回branch-names中的一个分支

branch-namebranch-names都可以接受提交作为参数,并且如果没有给出,则默认为HEAD


在脚本编程中有用的别名

branch-current = "symbolic-ref --short HEAD"  # https://dev59.com/Hm025IYBdhLWcg3wCxZN#19585361
branch-names = !"[ -z \"$1\" ] && git branch-current 2>/dev/null || git branch --format='%(refname:short)' --contains \"${1:-HEAD}\" #"  # https://dev59.com/Hm025IYBdhLWcg3wCxZN#19585361
branch-name = !"br=$(git branch-names \"$1\") && case \"$br\" in *$'\\n'*) printf \"Multiple branches:\\n%s\" \"$br\">&2; exit 1;; esac; echo \"$br\" #"

只能从一个分支访问的提交

% git branch-name eae13ea
master
% echo $?
0

输出到标准输出(STDOUT)。 退出值为0。 保留HTML。

可以从多个分支访问的提交

% git branch-name 4bc6188
Multiple branches:
attempt-extract
master%
% echo $?
1
  • 输出到标准错误流(STDERR)
  • 退出值为 1

由于退出状态,这些可以安全地建立。例如,要获取用于提取的远程地址:
remote-fetch = !"branch=$(git branch-name \"$1\") && git config branch.\"$branch\".remote || echo origin #"

我不得不谷歌一下“可达性”的含义,我发现如果在遵循父链接的情况下可以到达提交,则该提交是可达的。对我来说,这是一个恍然大悟的时刻,因为我最初认为如果它是树形结构,那么只要你可以朝任何方向走,所有节点都是可达的,但事实并非如此,它只能沿着父节点方向前进 - 如果我错了,请有人纠正我。 - Max Carroll

1

git checkout <SHA> -> 会使你进入分离 HEAD 状态。

git rev-parse --abbrev-ref HEAD -> 将打印出 HEAD,因此此命令在此状态下不起作用。

现在我们处于分离的 HEAD 状态,我们可以使用以下命令来获取分支名称。
PS:如果您不处于分离的 HEAD 状态,则此命令将无法正常工作!

git branch -a --contains HEAD | sed -n 2p | awk '{ printf $1 }'

完成了!现在你有分支名称了。


0

我认为有些人可能会遇到同样的问题,即无法找到分支,尽管它实际上存在于一个分支中。

最好先全部拉取:

git pull --all

然后进行分支搜索:

git name-rev <SHA>

或者:

git branch --contains <SHA>

-1

查找本地分支:

grep -lR YOUR_COMMIT .git/refs/heads | sed 's/.git\/refs\/heads\///g'

查找远程分支:

grep -lR $commit .git/refs/remotes | sed 's/.git\/refs\/remotes\///g'

2
一些关于你的示例的解释会很好。 - Eugen Konkov

-6

除了在整个树中搜索匹配的哈希值,没有其他方法。


8
严格来说,Git确实不会永久存储该信息,但我并不想将此评论下降,因为通常仍然有可能找到这些信息。请参阅我的回答。 - Cascabel

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