我遇到无法切换到仅在git ls-remote
中列出的分支的情况,以下是详细信息:
我将 github 存储库 A 分叉为 repoB,创建自己的分支并将其推送到 ComputerA 中的 repoB,在 ComputerB 中,我将分叉的存储库克隆到本地磁盘,添加远程上游,并尝试切换到我创建的分支但失败了,我可以成功切换到 github 网页中的同一分支。
以下结果来自计算机 B 中的存储库 B。
LS-远程分支:
$ git ls-remote --heads
2da2080ea7201fc7928e947dc3214dd89d86c4ba refs/heads/enable-vim-better-whitespace
433cedd84bba8bcdf3584734906b2c0fd3b6dc3a refs/heads/fix-lsp-cache-dir
ff65e1cd687d0c144e98b09e4d7a164f8b6bfd3e refs/heads/gh-pages
17e53cf01badebc2abef7df375903da71bf884d8 refs/heads/master
7b8f8a2dccb0715ff1c1c411abf40b2ff6cec30b refs/heads/vim-plug
26b8a0ba594af1068997c70c4ef0f503571557b3 refs/heads/vundle
列出分支:
$ git branch
abc
* master
$ git branch -r
origin/HEAD -> origin/master
origin/master
upstream/gh-pages
upstream/master
upstream/vim-plug
upstream/vundle
$ git branch -a
abc
* master
remotes/origin/HEAD -> origin/master
remotes/origin/master
remotes/upstream/gh-pages
remotes/upstream/master
remotes/upstream/vim-plug
remotes/upstream/vundle
abc
分行是我还没有推的本地分行。
我尝试了几种切换到分支的方法,例如fix-lsp-cache-dir
$ git checkout fix-lsp-cache-dir
error: pathspec 'fix-lsp-cache-dir' did not match any file(s) known to gi
$ git checkout -t origin/fix-lsp-cache-dir
fatal: 'origin/fix-lsp-cache-dir' is not a commit and a branch 'fix-lsp-cache-dir' cannot be created from it
我尝试了谷歌,但所有建议的方法都失败了。
那么我该怎么做才能切换到git ls-remote
中的仅分支列表
您在评论中提到您有多个遥控器,origin
和upstream
。 这会干扰(嗯,可能会干扰)人们通常不知道他们所依赖的 Git 功能:git checkout
所谓的DWIM 模式。 这还不是问题,但我们不妨解决它(在下面的长节中)。
您在第二条评论中提到git config -l
包含以下输出:
remote.origin.fetch=+refs/heads/master:refs/remotes/origin/master
这不是具有origin
的典型标准克隆的正常设置。 正常设置为:
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
如果您最初运行git clone --single-branch
或git clone --depth=...
(这意味着--single-branch
),您拥有的设置是常态。
为了使事情顺利进行,您需要更改或添加到remote.origin.fetch
设置。 例如,如果您首先将其更改为+refs/heads/*:refs/remotes/origin/*
(请参阅 VonC 的更新答案),则可以运行:
git fetch origin
其次:
git checkout -t origin/fix-lsp-cache-dir
甚至只是:
git checkout fix-lsp-cache-dir
如果您只有一个 远程origin
.如果您有多个遥控器,它有时会失败,在这种情况下,您需要使用稍长的git checkout -t origin/fix-lsp-cache-dir
或单独的git branch
命令来创建自己的分支名称fix-lsp-cache-dir
。
无论如何,您都需要一个首先从origin
获取的git fetch
。 您可以在git fetch
中明确命名origin
,或者使用从所有远程获取的选项之一(git fetch --all
或git remote update
,尽管使用git remote
误入新的领域,带来了许多新选项)。
龙:幕后发生了什么
要理解所有这些,您需要了解以下所有信息:
分支名称,您已经熟悉,但在内部存储,
refs/heads/
卡在前面(如您在git ls-remote
中看到的那样);远程跟踪名称— Git 调用此远程跟踪分支名称,但它们实际上不是分支名称,所以我更喜欢从中间删除这个词:它们在内部存储,
refs/remotes/
卡在前面,后跟远程名称本身;远程,它们是像
origin
和upstream
这样的短字符串,如果没有别的(通常还有其他东西),会存储一个 URL;refs或references,它们是分支名称、标签名称 (
refs/tags/*
)、远程跟踪名称和其他不太常见的名称(如refs/notes/*
和refs/stash
)的长形式;refspecs,它们大多只是由冒号分隔的 ref 对
:
并可选择以加号+
为前缀; 最后,git checkout
的"DWIM模式"功能。 DWIM代表做我的意思(而不是我输入的内容)。 这个特殊的首字母缩略词可以追溯到施乐PARC和Warren Teitelman:参见Eric Raymond的行话文件条目和维基百科关于Teitelman的文章。
、参考规范和远程
真的,你已经知道参考了。 它们只是每种引用的全名。 他们让像git fetch
这样的命令知道他们是否正在处理分支名称(refs/heads/master
)或远程跟踪名称(refs/remotes/origin/master
)或其他什么,如果他们甚至关心的话。1
最简单的 refspec形式只是一对带有冒号的 ref。 左边的名称是源,右边的名称是目标。 对于git fetch
,源部分意味着:使用您在输出中看到的相同内容git ls-remote
在我正在获取的存储库中找到名称和值。目标部分意味着在我自己的存储库中创建或更新目标名称。
前导加号(如果出现)将为由于该 refspec而发生的任何更新设置--force
标志。 因此:
+refs/heads/master:refs/remotes/origin/master
是一个 refspec 说:抓住他们的master
分支,并使用它来创建或更新我的origin/master
远程跟踪名称。 如果需要,强制进行此更新。您将获得他们在master
上的任何新提交,然后创建或更新您的origin/master
。 您将对自己的origin/master
进行此更新,即使这意味着某些提交在此过程中会"从"您的origin/master
"掉落"(--force
)。
我提到遥控器不仅仅包含 URL。每个远程都列出了一定数量的默认获取引用规范。 通常它只是一个,但通常那个是:
+refs/heads/*:refs/remotes/<remote>/*
填写remote
部分。 这个特殊的 refspec 说:获取他们所有的分支名称——所有与refs/heads/*
匹配的字符串——并强制创建或更新我所有相应的远程跟踪名称。远程origin
的相应名称是refs/remotes/origin/*
,所以这就是这里显示的内容。
单分支克隆的工作原理是在 refspec 中使用单个分支名称。 现在,您的git fetch
不会创建或更新其余潜在的远程跟踪名称。 修复此问题后,您的git fetch
将创建或更新其余的远程跟踪名称。
请注意,使用refs/heads/*
会启用另一个功能:--prune
。 将--prune
添加到git fetch
命令中,或将fetch.prune
设置为配置中的true
,git fetch
不仅会创建或更新正确的远程跟踪名称集,还会删除任何不再具有源的剩余远程跟踪名称。
例如,如果 Git onorigin
有一个名为X
的分支一小段时间,并且您运行git fetch
,则您的 Git 会创建自己的origin/X
。 但是,无论谁在原点上控制 Git,都会删除分支X
。 如果您没有启用修剪,则继续携带origin/X
:您的 Git 在它存在时创建并更新了它,但现在它没有,您的 Git 对此没有任何作用。 启用修剪,你的 Git 对自己说:啊哈,我有一个剩余的垃圾origin/X
! 我会自动剪掉它。修剪可能是默认的,带有"不修剪"选项,但事实并非如此。
1Fetch 实际上确实很关心,因为它试图用标签做一堆神奇的奇怪事情。
Checkout 的"DWIM 模式",以及何时以及为什么使用两个或多个遥控器失败
首次克隆 Git 存储库(不带--single-branch
)时,您自己的 Git 会获取origin
存储库中每个分支的远程跟踪名称:
git clone https://github.com/git/git/
例如,在 GitHub 上为 Git 存储库中的五个分支提供五个远程跟踪名称。
作为本git clone
的最后一步,您的 Git有效 2运行git checkout master
。在此阶段,您没有名为master
的分支。 事实上,你根本没有分支名称! 那么git checkout
怎么检查呢? 如何:
git checkout <name>
曾经工作过,当根本没有分支名称时?
答案是git checkout
实际上master
创建分支名称。 另请参阅下面的侧边栏(格式化为额外部分,因为我无法做真正的侧边栏)。 当git checkout
被赋予看似可能是分支名称但事实并非如此时,它会查看您所有的远程跟踪名称:origin/master
、origin/maint
、origin/next
等,例如,如果您使用的是 Git 存储库。 如果只有一个名称匹配,那么您的 Git 就像您实际运行一样:
git checkout -t origin/<name>
它告诉git checkout
:创建分支,将远程跟踪名称设置为其上游。现在这个名字已经存在了,现在git checkout
可以查看它了。
如果有两个或多个匹配的名称,则此过程将失败。例如,假设您没有fix-lsp-cache-dir
作为分支名称,但在您自己的 Git 存储库中确实有origin/fix-lsp-cache-dir
和upstream/fix-lsp-cache-dir
。 您运行:
git checkout fix-lsp-cache-dir
找不到fix-lsp-cache-dir
但确实找到了origin/fix-lsp-cache-dir
和upstream/fix-lsp-cache-dir
. 它发现的不是一个而是两个远程跟踪名称。 它应该使用origin
的,还是upstream
的? 它不知道。
在这一点上,git checkout
干脆放弃并说它不知道你所说的fix-lsp-cache-dir
是什么意思。 所以现在你需要,例如,git checkout -t origin/fix-lsp-cache-dir
,这是一个明确的指令:查找远程跟踪名称origin/fix-lsp-cache-dir
,使用它来创建fix-lsp-cache-dir
,然后签出fix-lsp-cache-dir
。这提供了有关使用哪个上游远程跟踪名称以及要创建哪个分支名称的答案。
2我在这里说"有效"是因为git clone
里面的代码这样做,并没有真正git checkout
运行,也不会打扰很多 DWIM 模式的东西:它确切地知道它已经放入存储库并可以作弊。 如果将git clone
拆分为一系列单独的命令:
git init
git remote add origin <url>
git fetch
git checkout master
您将从字面上运行git checkout master
并调用我所描述的 DWIM 模式。
(心理锻炼:比较和对比 Git 的分支 DWIM 和智能手机的自动更正。
超长侧边栏:Git 分支如何真正工作
每个 Git 分支名称(实际上,每个 Git引用)实际上只存储一个哈希 ID。 对于分支名称(以及隐含的远程跟踪名称),哈希 ID 被约束为提交哈希 ID;其他一些 ref 具有更大的灵活性,例如,标签名称可以指向 Git 的四种内部对象类型中的任何一种。
问题是,当我们说"分支master
",或者"这个提交在分支master
",或者任何类似的内容时,我们通常不是指一个特定的提交,即使实际的分支名称master
只能识别一个特定的提交。 它是如何工作的解释了很多关于 Git 的信息。
胶囊形式:
为了创建一个分支,我们将一些现有的有效提交的哈希 ID 写入以前不存在的名称中。
为了更新分支,我们将一些现有的有效提交的哈希 ID 写入已经存在的名称中。 它不再识别它刚才记住的提交。 现在它标识了我们选择的那个。
但是,无论如何,我们从提交哈希 ID 开始。 所以从某种意义上说,重要的是提交,而不是分支名称(当然我们也想要这些!
在 Git 中,每个提交都由自己唯一的、丑陋的大哈希 ID 标识。 例如,Git 的 Git 存储库中的一个提交是9c9b961d7eb15fb583a2a812088713a68a85f1c0
。 (这是一个为 Git 版本 2.23 做准备的提交,但不是任何特定版本。 这些哈希 ID 对于Git来说很好用——它是一个计算机程序,在将这些东西用作键值数据库中的键时不会出错——但它们对人类来说毫无用处。 我们在名称上做得更好,例如master
. 如果我们创建分支名称master
并使该名称表示"提交9c9b961d7eb15fb583a2a812088713a68a85f1c0
",我们可以运行:
git log master
或:
git diff my-branch master
什么的。master
每次都会选择提交9c9b961d7eb15fb583a2a812088713a68a85f1c0
的名称。 但是 Git 怎么知道提交8619522ad1670ea82c0895f2bfe6c75e06df32e7
——另一个随机的哈希 ID——是master
(9c9b961d7eb15fb583a2a812088713a68a85f1c0
)之前的提交呢?
答案是8619522ad1670ea82c0895f2bfe6c75e06df32e7
存储在9c9b961d7eb15fb583a2a812088713a68a85f1c0
中:
$ git cat-file -p 9c9b961d7eb15fb583a2a812088713a68a85f1c0 | sed 's/@/ /'
tree 33bba5e893986797fd68c4515bfafd709c6f69e5
parent 8619522ad1670ea82c0895f2bfe6c75e06df32e7
author Junio C Hamano <gitster@pobox.com> 1563561263 -0700
committer Junio C Hamano <gitster@pobox.com> 1563561263 -0700
The sixth batch
Signed-off-by: Junio C Hamano <gitster@pobox.com>
此处的parent
行给出了上一次提交的原始哈希 ID。
每个 Git 提交——嗯,几乎每一个——都至少有一个父级。3Git 可以在历史记录中向后移动一步,从提交到其父级。 父级本身有另一个父级,因此 Git 可以再移动一步。 通过逐步从提交移动到父级获得的路径是Git 存储库中的历史记录。
对于简单的线性链,我们可以通过假装 Git 为每个提交使用一个字母名称而不是大丑陋的哈希 ID 来绘制这个:
... <-F <-G <-H <--master
链中的最后一个提交是提交H
。 这是存储在名称下的哈希 IDmaster
. 我们说master
指向H
.H
反过来存储G
的哈希ID ,所以我们说H
指向G
.G
存储F
的哈希 ID,因此G
指向F
。F
指向F
的父母。 这种情况一直持续到我们遇到没有父级的提交,例如此存储库的第一次提交......这些是"在"分支master
上的提交。
要添加新提交,我们让 Git 保存所有源文件的快照,添加我们的名称和电子邮件地址以及git log
显示的其他内容,使用 commitH
的实际哈希 ID 作为父级,然后写出一个新的提交。 这个新提交会得到一个新的、唯一的哈希 ID,但我们只称它为I
。 然后 Git 只是用这个新的哈希 ID 覆盖名称master
:
... <-F <-G <-H <-I <--master
master
分支现在提交时间更长。 链中的最后一次提交称为提示提交。 我们知道(或找到)提示通过从分支名称中读取哈希 ID 在 Git 存储库中提交。
分支名称master
只是标识链中的最后一个提交。移动分支名称或远程跟踪名称的各种 Git 命令,例如git reset
或git branch -f
,或者 - 对于远程跟踪名称 -git fetch
- 实际上只是使名称指向一个特定的提交。
如果我们可以从新提示开始,并使用内部的向后箭头找到旧提示,那么我们所做的就是向分支添加一些提交。 当我们使用git commit
创建一个提交时,它就是这样做的:它创建一个新的提交,它成为 tip,并且将旧 tip 作为其父级。
当我们使用git fetch
并且我们得到,比如说,我们的远程跟踪名称origin/master
的三到五个新提交,其中最后一个- 提示 - 最终回到我们运行git fetch
之前我们的origin/master
指向的位置。 因此,新提交只是新添加到origin/master
远程跟踪名称中。
Git 称这种名称更新,只会添加一些东西,快进。 您可以使用git fetch
进行快进,更新您的远程跟踪名称,并使用git push
向其他一些 Git 提供新提交并让它们更新其分支名称。 在这两种情况下,您的 Git 和/或他们的 Git 都没有丢失任何提交,因为从新提示开始并向后工作,您或他们到达旧提示。
你也可以 - 有一些额外的皱纹 - 用git merge
快进。 如果git merge
执行快进而不是合并,则使用您已有的提交,而无需实际进行任何新提交。 例如,在git fetch origin
之后,您可能有:
...--F--G--H <-- master (HEAD)
I--J <-- origin/master
在这里,您实际上是在自己的master
,通过将特殊名称HEAD
附加到名称master
来表示。 您的 Git 现在可以通过将名称移动到master
使其指向提交J
,并同时执行git checkout
提交J
来执行快进而不是真正的合并:
...--F--G--H--I--J <-- master (HEAD), origin/master
这就是快进合并:它实际上根本不是合并,而只是一个git checkout
,它也向前拖动当前分支名称,就像git fetch
快进您的origin/master
一样。
当操作不是快进时,需要--force
标志。 例如,假设您刚刚执行了上述操作,那么现在master
和origin/master
都标识提交J
。 同时,谁在origin
控制存储库,谁说:哦,废话! 提交J
是不好的! 我用git reset --hard
扔掉它,而是添加一个新的提交K
!现在,您再次运行git fetch
并获得:
K <-- origin/master
/
...--H--I--J <-- master (HEAD)
您仍然有提交J
:它在您的master
上。他们试图抛弃提交J
(无论它的实际哈希 ID 是什么——你的 Git 和他们的 Git 同意它的哈希 ID)。 你的origin/master
现在指向K
,K
的父母是I
,而不是J
。 您的origin/master
刚刚被强制更新。
您将git fetch
输出中看到以下内容:
$ git fetch
...
+ a83509d9fc...0ddebcb508 pu -> origin/pu (forced update)
Git 的 Git 存储库中的pu
分支是每个人都同意定期强制更新的分支。 所以我的origin/pu
用来识别a83509d9fc
,但现在它可以识别0ddebcb508
。 请注意,+
,单词(forced update)
,以及两个哈希ID之间有三个点而不是两个点的事实:这是git fetch
宣布我的origin/pu
刚刚被强制更新的三种方式。 我现在可以这样做:
$ git rev-list --left-right --count a83509d9fc...0ddebcb508
79 214
这告诉我 79 个提交被删除(从我的旧origin/pu
),并添加了 214 个提交(到我的新更新origin/pu
)。 在这种情况下,我实际上并不在乎,但是如果我出于某种原因这样做,我可以看到他们在origin
做了什么。
(稍微有用一点:
$ git rev-list --count master..origin/master
210
告诉我,现在我可以将 210 个新提交带入我的master
。 要实际查看这些提交,我可能需要git log
。
3没有父级的提交定义为根提交。 当您在一个新的、完全空的 Git 存储库中进行第一次提交时,这就是您所做的那种提交。 第一次提交不能有父级,所以它没有。
具有两个或更多父项的提交定义为合并提交。 这是git merge
通常会做出的那种承诺。第一个父母一切照旧;任何其他父级都会告诉 Git 合并了哪些提交。
您需要先git fetch
。
检查你的 git config remote.origin 确实显示了一个获取引用规范,如下所示:
fetch = +refs/heads/*:refs/remotes/origin/*
这会将fix-lsp-cache-dir
导入您的存储库,您将能够签出该分支。
结帐或...很快git switch
.
OP CodyChan在评论中证实:
remote.origin.fetch=+refs/heads/master:refs/remotes/origin/master
那只会得到主人,没有别的。
cd /path/to/my/repo
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
这应该可以解决它。