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

880

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

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


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

1152

虽然Dav是正确的,信息并没有直接存储,但这并不意味着你永远无法找到。以下是一些你可以做的事情。

查找包含该提交的分支

git branch -a --contains <commit>

这会告诉你所有历史记录中包含给定提交的分支。如果提交已经合并,则显然这不太有用。

搜索Reflogs

如果您正在该提交所在的存储库中工作,可以搜索该提交的reflog行。 git-gc清理90天前的reflog,因此,如果提交过旧,则无法找到它。 也就是说,您可以执行以下操作:

git reflog show --all | grep a871742

查找提交记录 a871742。请注意,您必须使用提交记录的前7个缩写字符。输出应该类似于以下内容:

a871742 refs/heads/completion@{0}: commit (amend): mpc-completion: total rewrite

说明提交是在分支 "completion" 上进行的。默认输出显示缩写的提交哈希值,因此确保不要搜索完整的哈希值,否则将找不到任何内容。

git reflog show 实际上只是 git log -g --abbrev-commit --pretty=oneline 的别名,因此如果您想调整输出格式以使不同的内容可供 grep 搜索,那么这是您的起点!

如果您没有在提交所在的存储库中工作,则在这种情况下,您最好检查 reflogs 并查找提交首次引入到您的存储库的时间;带着希望,您已经获取了它提交的分支。这有点更复杂,因为您无法同时遍历提交树和 reflogs。您需要解析 reflog 输出,检查每个哈希值是否包含所需的提交。

查找后续合并提交

这取决于工作流程,但对于良好的工作流程,提交是在开发分支上进行的,然后合并到主干。您可以执行以下操作:

git log --merges <commit>..

查看以给定提交为祖先的合并提交。(如果该提交只被合并了一次,则第一个合并提交应该是您要查找的;否则,您可能需要检查几个。) 合并提交消息应包含已合并的分支名称。

如果希望能够依赖此操作,可能需要使用--no-ff选项进行git merge以强制创建合并提交,即使在快进情况下也是如此。(不过不要过于急切,如果过度使用可能会使事情变得复杂。)VonC回答了相关问题,有助于详细解释这个主题。


9
我完全同意-分支应该是轻量级和灵活的。如果您采用了一个工作流程,在这个流程中这些信息变得重要,您可以使用 --no-ff 选项来确保始终存在合并提交,这样您就可以随时追踪给定提交的路径,它是如何朝着主分支合并的。 - Cascabel
37
如果您想查找仅存在于远程的提交,请在第一个命令中添加-a标志,例如:git branch -a --contains <commit> - Jonathan Day
8
不,这会在任何分支上查找提交记录。如果你想只查找远程分支上的提交记录,使用“-r”。 - Cascabel
5
@VonC和Jefromi:我认为知道一个提交是在哪个分支上创建的显而易见的用例是为了确定该更改属于哪个功能。我们为每个功能使用不同的分支,然后将其合并回发布分支。使用Git很难一眼看出一个提交最初属于哪个功能,因为我们无法区分一个分支和另一个分支。评论和标签有所帮助,但这不应该那么困难。 - Simon Elms
7
就像我说的一样,如果这真的是一个问题,使用merge --no-ff来可靠地记录分支名称。但是,除此之外,请将分支名称视为临时短标签,将提交说明视为永久标签。“在开发过程中我们用什么短名称来指代它?”这个问题确实不应该像“这个提交做了什么?”这个问题那样重要。 - Cascabel
显示剩余13条评论

251

这个简单的命令非常好用:

git name-rev <SHA>

例如(其中 test-branch 是分支名称):

git name-rev 651ad3a
251ad3a remotes/origin/test-branch

即使对于复杂的情况,它也能正常工作,如:

origin/branchA/
              /branchB
                      /commit<SHA1>
                                   /commit<SHA2>

这里git name-rev commit<SHA2>返回branchB


14
我发现git name-rev --name-only <SHA>对于仅获取分支名称更加有用。我的问题是...它在任何情况下是否可能返回多个分支? - dnk8n
7
如果你只想要查找分支名称而排除标签,可以使用以下命令:git name-rev --refs="refs/heads/*" --name-only <SHA> - Andrey Semakin
2
很遗憾,在合并之后的短暂时间内才能起作用,然后引用就会被垃圾回收。这与@Cascabel答案中“搜索reflog”的方法具有相同的限制。正如VonC所指出的那样,唯一真正保存此信息的方法是,如果您的工作流程包括在每次合并时将其保存在git注释或带注释标签中。 - Yitz
5
这个不能正常工作。在一些项目中,我已经把所有的提交都放在主分支(master)里,但是在我的提交上运行git name-rev命令时,给出了我从未使用过的分支,特别是来自另一个用户的远程分支。这完全没有意义! - vinc17
1
如果您能解释一下这段代码的实际作用,那将会很有帮助。 - Richard Hunter
显示剩余5条评论

50
例如,要查找 c0118fa 提交是从 redesign_interactions 分支来的:

要查找提交所在的分支,请使用以下命令:

* ccfd449 (HEAD -> develop) Require to return undef if no digits found
*   93dd5ff Merge pull request #4 from KES777/clean_api
|\
| * 39d82d1 Fix tc0118faests for debugging debugger internals
| * ed67179 Move &push_frame out of core
| * 2fd84b5 Do not lose info about call point
| * 3ab09a2 Improve debugger output: Show info about emitted events
| *   a435005 Merge branch 'redesign_interactions' into clean_api
| |\
| | * a06cc29 Code comments
| | * d5d6266 Remove copy/paste code
| | * c0118fa Allow command to choose how continue interaction
| | * 19cb534 Emit &interact event

你应该运行:

git log c0118fa..HEAD --ancestry-path --merges

向下滚动查找最后一个合并提交。即:

commit a435005445a6752dfe788b8d994e155b3cd9778f
Merge: 0953cac a06cc29
Author: Eugen Konkov
Date:   Sat Oct 1 00:54:18 2016 +0300

    Merge branch 'redesign_interactions' into clean_api

更新

或者只需一个命令:

git log c0118fa..HEAD --ancestry-path --merges --oneline --color | tail -n 1

9
这是唯一能够给我想要的结果的解决方案。我试过其他方法。 - crafter
1
请注意,此方法仅适用于合并提交摘要包含合并使用的分支名称的情况。默认情况下,它们通常包含这些信息。但是,git merge -m“任意字符串”将隐藏源分支和目标分支的信息。 - qneill
@qneill:不,合并操作始终会包含有关已合并分支的信息:Merge: f6b70fa d58bdcb。您可以按照自己的意愿为合并提交命名。这不会有任何影响。 - Eugen Konkov
1
@EugenKonkov 一旦分支合并完成,关于它们在图形中来自哪个路径的历史记录会留存在 reflog 中,但不会留存在 git 对象数据库中。我发了一个回答试图解释我的意思。 - qneill
在UDP中,“56a44a5”哈希值是从哪里来的? - lordhog
显示剩余3条评论

50

2013年12月更新:

sschuberth comments

git-what-branch(Perl脚本,请参见下文)似乎不再维护。 git-when-merged是一种替代方案,用Python编写,对我来说非常有效。

它基于 "Find merge commit which include a specific commit"。

git when-merged [OPTIONS] COMMIT [BRANCH...]

寻找某个提交合并到一个或多个分支的时间。 找到将提交COMMIT合并到指定分支的合并提交。 具体而言,查找包含COMMIT作为祖先的BRANCH第一父历史上最早的提交。

2010年9月的原始答案:

Sebastien Douche刚才发推(在此SO答案之前16分钟):

git-what-branch:发现提交位于哪个分支,或者如何到达命名分支

这是来自Seth RobertsonPerl脚本,看起来非常有趣:

概述

git-what-branch [--allref] [--all] [--topo-order | --date-order ]
[--quiet] [--reference-branch=branchname] [--reference=reference]
<commit-hash/tag>...

概述

告诉我们默认情况下导致请求提交到命名分支的最早因果路径的提交和合并。 如果在命名分支上直接进行了提交,那显然就是最早的路径。

通过最早的因果路径,我们指的是按提交时间(除非指定了--topo-order)最早合并到命名分支的路径。

性能

如果许多分支(例如数百个)包含提交,则系统可能需要很长时间(对于Linux树中的特定提交,探索一个分支需要8秒,但有200多个候选分支)来跟踪每个提交的路径。
选择特定的--reference-branch --reference tag以进行检查将快几百倍(如果您有数百个候选分支)。

示例

 # git-what-branch --all 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4
 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4 first merged onto master using the following minimal temporal path:
   v2.6.12-rc3-450-g1f9c381 merged up at v2.6.12-rc3-590-gbfd4bda (Thu May  5 08:59:37 2005)
   v2.6.12-rc3-590-gbfd4bda merged up at v2.6.12-rc3-461-g84e48b6 (Tue May  3 18:27:24 2005)
   v2.6.12-rc3-461-g84e48b6 is on master
   v2.6.12-rc3-461-g84e48b6 is on v2.6.12-n
   [...]

这个程序不考虑挑选感兴趣的提交(commit)对结果的影响,只考虑合并操作。


5
git-what-branch 似乎不再维护了。git-when-merged 是一个用 Python 编写的备选方案,对我来说工作得很好。 - sschuberth
@sschuberth,感谢您的更新。我已将您的评论包含在答案中以增加可见性。 - VonC

25

khichar.anil 在他的回答中已经涵盖了大部分内容。

我只是添加了一个标志,它将从版本名称列表中删除标记。这给了我们:

git name-rev --name-only --exclude=tags/* $SHA

第二句话似乎缺少了一些东西。 - Peter Mortensen
1
这帮了我,否则输出中会得到一个标签而不是一个分支。 - Cole
2
这正是我来到这里的原因。谢谢分享。 - Jorrick Sleijster

11

git branch --contains <ref> 是最明显的“瓷器”命令来执行此操作。如果你只想使用“铁管”命令来做类似的事情:

COMMIT=$(git rev-parse <ref>) # expands hash if needed
for BRANCH in $(git for-each-ref --format "%(refname)" refs/heads); do
  if $(git rev-list $BRANCH | fgrep -q $COMMIT); then
    echo $BRANCH
  fi
done

(这是从这个SO答案中转载的)

哦,是的!我正在尝试编写一个脚本,可以从一系列不同状态的repos(在本地分支或分离头上,跟踪和设置上游或没有等等)中创建最简洁的差异,并且这就是我的答案!由于我的同事将通过LTE或有时是Iridium连接,因此我需要尽可能减少带宽,而这种机制告诉我从哪里开始(我只需将“refs/heads”更改为“refs/remotes/origin”)。 - Daniel Santos

9
我尝试了上述所有的解决方案,但都不能完全满足我的需求。
以下是目前为止唯一对我有效的方法(假设HEAD在合适的位置):
git log --branches --source | grep <sha>

#or if you also care about remotes
git log --branches --remotes --source | grep <sha>

分支名称应该位于行末。
来自文档
--source 打印出每个提交所到达的命令行上给出的引用名称。
因此,这可能会根据HEAD所在位置而改变,但对我来说,将HEAD放在我的主分支上最新的提交产生了我期望的结果。
使用gitk --all进行目视检查也可能有所帮助。它为每个提交都有一个“分支”字段,但它显示可以“到达”该提交的所有分支,而不一定是该提交所在的分支。参见这里
顺带一提: 同其他版本控制系统不同,Git中的分支只是指向您提交历史/图形中的位置的临时指针。 它们不“拥有”提交,也不“由”提交组成。 实质上,它们只是在您检出标记的情况下更新到您推送的任何提交的标记。 因此,分支不是提交系列,而是指向提交系列结尾/顶端的指针。 因此,提交不“属于”或“来自”分支,它们只是您的图形中的节点。
那么我们到底在做什么呢? 我们使用可达性的概念。 如果我们从每个分支指向的提交(“分支”的“末尾”)开始并往回查看历史记录,我们能够找到/到达哪些提交? 如果我们只有从特定分支开始搜索才能找到提交,那么我们称这些提交是“在”该分支上的。
请注意,这不是稳定的。 如果我们合并、重命名或变基分支,突然之间可到达(因此“在”)我们分支的提交将发生变化。 这就是为什么提交“在/由”特定分支拥有并不是Git中官方或始终具有意义的概念的原因(以及为什么这个问题没有清晰/明确的答案)。

1
这正是它的精髓,而且非常优美。 - Shōgun8

8

我处理与Jenkins多分支流水线相同的问题 - 只有提交信息并尝试找到此提交最初来自哪个分支名称。它必须适用于远程分支,本地副本不可用。

这是我要处理的内容:

git rev-parse HEAD | xargs git name-rev

你可以选择剥离输出:

git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g'

4

简单来说,Git不会存储提交所在分支的名称。尝试重构此信息的技巧似乎并不总是奏效。


1
作为附言,我个人认为Git历史中分支未命名的事实是文档严重缺失。这使得提交历史非常难以理解。 - Dimitri C.
2
同意,这基本上是git的设计限制。它故意不存储分支名称。但我觉得这是了解变更历史的一个相当关键的信息。 - undefined
1
同意,这基本上是 Git 的设计限制。它有意地不存储分支名称。但我觉得这是了解变更历史的一个相当关键的信息。 - O'Rooney

3
一个穷人的选择是使用工具tig1HEAD上搜索提交,然后从该提交开始沿着线条向上追踪,直到看到合并提交。默认的合并消息应该指定将哪个分支合并到哪里 :)

1 Tig是一个基于ncurses的Git文本模式界面。它主要作为Git仓库浏览器,但也可以在分块级别上协助暂存变更以进行提交,并作为各种Git命令输出的分页器。


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