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/master
或refs/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"
然后,您可以明智地决定是要 merge
、rebase
还是做其他事情。
作为聚会的把戏,你也可以做类似的事情
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 checkout
或 git switch
将从 remote-tracking 名称创建 branch 名称的功能(例如,创建 dev
来自origin/dev
) 以前称为DWIM 模式,现在由--guess
/ --no-guess
控制。 Git 基本上做了一件事,在它发出“你在说什么分支”错误之前,如果启用了猜测,则在结帐/切换中进行猜测。如果猜测找到了一个好的猜测,它会创建分支,然后检查它/切换到它。
请注意,与git checkout
不同(它将对任何非分支名称提交说明符进行“分离的HEAD”检查),git switch
需要--detach
标志用于此目的。这使得git switch
对新手更加友好。由于一些……有问题的设计选择,Git 对新手相当有敌意,现在必须永远保留以保持兼容性。 :-)以上是关于Git:为啥“git分支”不列出所有分支?的主要内容,如果未能解决你的问题,请参考以下文章