Git:为啥“git分支”不列出所有分支?

Posted

技术标签:

【中文标题】Git:为啥“git分支”不列出所有分支?【英文标题】:Git: why doesn't "git branch" list all the branches?Git:为什么“git分支”不列出所有分支? 【发布时间】:2022-01-09 06:01:22 【问题描述】:

我以前使用过 Mercurial,但我无法理解 Git 分支。为什么

git branch

只列出部分分支,我需要运行

git branch -a

获取所有分支的列表?在我看来,Git 有多种不同的分支类型,而 Mercurial 只有一种。

【问题讨论】:

【参考方案1】:

正如我在https://***.com/a/11223644/334451 上写的那样,实际上恰恰相反。 Mercurial 有多个逻辑上类似于 Git 分支的东西:未命名的分支命名的分支书签。 Git只有分支,但它使用名称空间作为分支名称(基本上是分支的名称空间路径+分支名称)。 Git 用户经常谈论“master”分支(或现在的“main”),如果它们实际上是指refs/heads/masterrefs/remotes/origin/master 或其他东西,你必须从上下文中破译。如果您只使用全名的最后一部分,并且在某些上下文中明确定义了短名称的解释,Git 也足够聪明,可以猜出正确的路径。例如,当你说

git push origin foo:bar

它将实际执行(假设foo 是本地分支而不是标签)

git push origin refs/heads/foo:refs/heads/bar

这意味着'获取本地“refs/heads/foo”并使用远程服务器上的名称“refs/heads/bar”将其推送到远程服务器“origin”。只要目标是已经存在的分支,您就可以对源使用提交 SHA-1(否则 git 无法知道您是否要创建新标签或分支)。如果我需要强制(覆盖)远程服务器上的东西,我更喜欢使用这种语法,以便我可以准确地指定目标分支的新状态。

由于所有内容实际上都有完整的命名空间名称(refname),您还可以执行一些操作,例如创建一个名为“master”(实际上是 refs/heads/master)的分支和名为“master”(实际上是 refs/tags/master)的标签,但是那只是自找麻烦。 Git 总是在后台使用完整的引用名称,但允许在用户界面中使用较短的名称。

默认情况下,当您运行“git branch”时,它只列出refs/heads/*,没有完整的引用名。对于完整的分支名称,您必须运行类似

git branch --format="%(refname)"

或对于所有分支,本地或远程,无论您配置了多少远程服务器

git branch -a --format="%(refname)"

这将列出所有已知分支的全名。所有这些名称实际上都是工作目录中.git/refs/ 目录下的文件路径,因此整个系统实际上非常简单(该路径末尾的文件仅包含提交的 SHA-1,即该分支的尖端) .当你在 Git 中“创建一个新分支”时,你实际上是创建了一个 41 字节的新文件,其中包含当前签出的提交 SHA-1(“git rev-parse HEAD”的输出)并带有尾随换行符,文件名就是文件名您创建的分支。文件.git/HEAD 包含对您工作目录中当前签出的提交或头部或标记的文本引用。

Git 还支持使用包含斜杠的分支名称,在这种情况下,refs/heads/ 层次结构下会有额外的目录级别,但其他一切都一样。 git 的官方 git repo 使用带有额外目录级别前缀的分支名称。

Git 标签的实现方式类似,但它们存储在.git/refs/tags/ 中,并且在签出标签后创建新提交时不会自动修改。请注意,标签不会保存在单独的命名空间中,但是当您获取更改时,您也会自动获取所有标签,并且这些标签始终位于前缀 refs/tags/ 中。

您可以使用命令列出所有具有完整引用名的已知标签

git tag --format='%(refname)'

请注意,“git tag -a”确实存在,但它并不意味着“列出所有”而是“创建带注释的标签”(一个附加了更多信息而不仅仅是名称的标签),因为标签没有命名空间,所以不需要“列出所有标签”。

refs/remote/ 开头的分支将在您运行“git fetch”时自动更新(或执行“git pull”,这将在您背后运行“git fetch”)。

如果你从不使用“git pull”来做任何事情,Git 会更容易理解。始终运行“git fetch”(或“git fetch --all”,如果您有多个远程服务器),它只会更新refs/remote/ 层次结构并下载所需的pack/object 文件以实际了解所有这些SHA -1 的意思。执行“git fetch”后,您可以使用“gitk --all”、“gitg”或其他可以同时显示本地和远程分支的存储库查看器。如果你没有任何 GUI 工具,你可以运行类似的东西

git log --oneline --decorate --graph --all

或(所有内容都在一行中)

git log --graph --all --pretty=format:"%C(auto)%h%d%Creset %s %Cgreen(%cr)%Creset"

然后,您可以明智地决定是要 mergerebase 还是做其他事情。

作为聚会的把戏,你也可以做类似的事情

git push . HEAD:foo

这意味着推送到本地存储库,将本地分支HEAD 更新为分支“foo”(快进)的新值,其中HEAD 当前是照常检出的版本。您也可以在此处使用 SHA-1。当您使用功能分支并希望将当前功能分支包含到本地“master”分支时,这非常有用。您可以简单地将当前分支推送到本地 master 分支,而不是从您的功能分支中检出 master 并“合并”更改。这比强制 master 分支到 HEAD 要好,因为如果更改不会快进,Git 会显示错误。为此,我将“git update-master”别名为“git push . HEAD:master”。 (我实际上并没有输入git update-master,而是输入git ui,然后是TAB,它会自动完成其余部分。请确保为您的shell中的所有git命令启用自动完成功能,除非默认启用。)

【讨论】:

"Git 只有分支……" 嗯,不完全正确。 refs/remotes/origin/master 是一种特殊的分支——远程跟踪分支。它们不等同于本地分支机构。您不能随意结帐或移动它们;移动它们的唯一方法是与远程存储库同步(获取/拉取/推送)。 您可以执行 git push . master:refs/remotes/origin/master2 之类的操作(本地→本地更新),但您是对的,如果您运行 git checkout refs/remotes/origin/master,您最终会在 @987654380 的 SHA-1 处得到“分离的 HEAD” @ 而不是像普通分支一样签出该分支。我猜 Git 这样做是因为它假定 refs/remotes/origin/* 可以在您执行 git fetch 时随意覆盖,因此允许在本地使用它们可能是个坏主意。 我刚刚测试过,似乎这种关于远程跟踪分支的魔法仅在git checkout 中实现。我可以运行git checkout origin/master && echo "ref: refs/remotes/origin/master" > .git/HEAD,然后通过Git 在我的本地副本中更新分支refs/remotes/origin/master 来创建新的提交。当然,如果我稍后再做git fetch,它会被覆盖,所以这更多是为了保护你的工作,而不是这些分支的特殊性。 git checkoutgit switch 将从 remote-tracking 名称创建 branch 名称的功能(例如,创建 dev来自origin/dev) 以前称为DWIM 模式,现在由--guess / --no-guess 控制。 Git 基本上做了一件事,在它发出“你在说什么分支”错误之前,如果启用了猜测,则在结帐/切换中进行猜测。如果猜测找到了一个好的猜测,它会创建分支,然后检查它/切换到它。 请注意,与git checkout 不同(它将对任何非分支名称提交说明符进行“分离的HEAD”检查),git switch 需要--detach 标志用于此目的。这使得git switch 对新手更加友好。由于一些……有问题的设计选择,Git 对新手相当有敌意,现在必须永远保留以保持兼容性。 :-)

以上是关于Git:为啥“git分支”不列出所有分支?的主要内容,如果未能解决你的问题,请参考以下文章

Git分支操作

Git分支操作

为啥 `git svn clone` 不能转换所有的 SVN 分支?

如何找到提交给 git 分支的文件列表?

为啥“合并后删除分支”只删除远程GIT分支而不是本地?

Git:分支管理