git - 如何签出仅在`git ls-remote`中列出的分支?
问题描述
我遇到了无法切换到仅在 中列出的分支的情况git ls-remote
,以下是详细信息:
我将 github repoA 分叉为 repoB,创建并将自己的分支推送到 ComputerA 中的 repoB,在 ComputerB 中,我将分叉的 repo 克隆到本地磁盘,添加远程上游,并尝试切换到我创建但失败的分支,我虽然可以成功切换到 github 网页中的同一分支。
以下结果来自 ComputerB 中的 repoB。
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
无论如何,您将需要一个git fetch
从第一个获取的origin
。您可以在您的 , 或使用从所有远程获取的选项之一origin
显式命名(或者,尽管使用引入许多新选项的新领域的流浪)。git fetch
git fetch --all
git remote update
git remote
龙:幕后发生了什么
要理解所有这些,您需要了解以下所有内容:
分支名称,您已经熟悉但内部存储
refs/heads/
在前面(如您所见git ls-remote
);远程跟踪名称——Git 调用这个远程跟踪分支名称,但它们实际上不是分支名称,所以我更喜欢从中间删除这个词:它们在内部存储,
refs/remotes/
前面卡住,后面是远程名称本身;remotes,它们是类似的短字符串
origin
,upstream
如果没有别的东西——通常还有别的东西——存储一个 URL;refs或references,它们是分支名称、标签名称 (
refs/tags/*
)、远程跟踪名称和其他不太常见的名称(如refs/notes/*
and )的长形式refs/stash
;refspecs,它们大多只是由冒号分隔的成对的 refs
:
并且可选地以加号作为前缀+
;最后,git checkout
的“DWIM 模式”功能。DWIM 代表Do What I Mean(与我输入的相反)。这个特殊的首字母缩略词可以追溯到 Xerox PARC 和 Warren Teitelman:请参阅Eric Raymond 的术语文件条目和关于 Teitelman 的 Wikipedia 文章。
参考、参考规范和遥控器
真的,你已经知道 refs 了。它们只是各种参考的全名。他们让命令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。每个远程都列出了一些默认的 fetch refspecs。通常只有一个,但通常那个是:
+refs/heads/*:refs/remotes/<remote>/*
remote
填写部分。这个特定的参考规范说:获取他们所有的分支名称- 所有匹配的字符串refs/heads/*
-<em> 并强制创建或更新我所有相应的远程跟踪名称。remote 的对应名称origin
是refs/remotes/origin/*
,所以这就是这里显示的内容。
单分支克隆通过在 refspec 中使用单分支名称的简单权宜之计来工作。现在您git fetch
不会创建或更新其余的潜在远程跟踪名称。解决这个问题,您git fetch
将创建或更新其余的远程跟踪名称。
请注意, usingrefs/heads/*
启用了另外一项功能:--prune
. 添加--prune
到您的git fetch
命令(或在您的配置中设置fetch.prune
为),不仅会创建或更新正确的远程跟踪名称集,还会删除任何不再有来源的剩余远程跟踪名称。true
git fetch
例如,如果 Git onorigin
有一个名为X
短暂的分支,并且您运行git fetch
,您的 Git 会创建您自己的origin/X
. 但是,无论谁在 origin 上控制 Git,都会删除branch X
。如果你没有启用修剪,你继续携带origin/X
:你的 Git在它存在时创建并更新它,但现在它没有,你的 Git 对此什么也不做。启用修剪,你的 Git 对自己说:啊哈,我有一个剩余的垃圾origin/X
!我会自动剪掉的。 修剪可能应该是默认设置,带有“不修剪”选项,但事实并非如此。
1 Fetch 实际上确实很在意,因为它试图用标签做一堆神奇的怪事。
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
,如果您使用 Git 的 Git 存储库origin/maint
,它会查看您的所有远程跟踪名称。origin/next
如果只有一个名称匹配,那么您的 Git 就像您实际运行一样:
git checkout -t origin/<name>
它告诉git checkout
:创建分支,将远程跟踪名称设置为其上游。 现在名称存在,现在 git checkout
可以检查它。
如果有两个或更多匹配的名称,此过程将失败。例如 ,假设您没有分支名称,但在您自己的 Git 存储库中有,并且. 你跑:fix-lsp-cache-dir
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
and 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;其他一些引用具有更大的灵活性,例如,标签名称可以指向 Git 的四种内部对象类型中的任何一种。
问题是,当我们说“分支master
”或“此提交在分支上master
”或类似的任何内容时,我们通常并不是指一个特定的提交,即使实际的分支名称 master
只能识别一个特定的提交. 它的工作原理解释了很多关于 Git 的内容。
胶囊形式:
为了创建一个分支,我们将一些现有的有效提交的哈希 ID 写入一个以前不存在的名称中。
为了更新一个分支,我们将一些现有的有效提交的哈希 ID 写入一个已经存在的名称中。它不再识别它刚才记住的提交。现在它识别了我们选择的那个。
不过,无论如何,我们都是从提交哈希 ID 开始的。所以从某种意义上说,重要的是提交,而不是分支名称(当然我们也想要那些!)。
在 Git 中,每个提交都由它自己唯一的、又大又丑的哈希 ID 标识。例如,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 提交——嗯,几乎每个提交——都至少有一个parent。3 Git 可以在历史上倒退一步,从提交到其父级。父级本身还有另一个父级,因此 Git 可以再移动一步。从 commit 到 parent 一步一步移动得到的路径,就是Git 仓库中的历史。
对于简单的线性链,我们可以通过暂时假设 Git 使用一个字母名称而不是大而丑陋的哈希 ID 来绘制每个提交:
... <-F <-G <-H <--master
链中的最后一个提交是 commit H
。那是存储在 name 下的哈希 ID master
。我们说那master
指向 H
。 H
反过来存储 的哈希 ID G
,所以我们说H
指向G
。 G
存储 的哈希 ID F
,因此G
指向F
。 F
指向F
的父级。这一直持续下去,直到我们遇到一个没有父级的提交,例如这个存储库的第一次提交......并且那些是“在”分支上的提交master
。
要添加新的提交,我们让 Git 保存所有源文件的快照,添加我们的姓名和电子邮件地址以及其他git log
显示的内容,使用提交的实际哈希 IDH
作为parent,然后写出新的提交。这个新的提交获得了一个新的、唯一的哈希 ID,但我们只称它为I
. master
然后 Git 简单地用这个新的哈希 ID覆盖名称:
... <-F <-G <-H <-I <--master
并且master
分支现在是一个提交时间更长。链中的最后一个提交称为提示提交。我们通过从分支名称中读取哈希 ID 来了解或找到 Git 存储库中的提示提交。
分支名称master
只是标识链中的最后一个提交。 移动分支名称或远程跟踪名称的各种 Git 命令,例如git reset
orgit branch -f
或 - 对于远程跟踪名称 - <code>git fetch - 实际上只是使名称指向一个特定的提交。
如果我们可以从新的提示开始,并使用内部的、向后的箭头来找到旧的提示,那么我们所做的就是向分支添加一些提交。当我们git commit
用来创建一个提交时,它就是这样做的:它创建一个新的提交,它成为提示,并将旧提示作为其父级。
当我们使用git fetch
并获得三到五个新的远程跟踪 name 提交时origin/master
,其中的最后一个- 提示 - 最终会回到我们在运行之前origin/master
指定的位置。因此,新提交只是新添加到远程跟踪名称中。git fetch
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
使其指向 commitJ
并同时执行 a git checkout
of commit来执行快进非真正合并J
:
...--F--G--H--I--J <-- master (HEAD), origin/master
这就是快进合并的含义:它实际上根本不是合并,而只是git checkout
将当前分支名称向前拖动,就像刚才git fetch
快进你origin/master
的方式一样。
--force
当操作不是快进时需要该标志。例如,假设您刚刚执行了上述操作,那么 nowmaster
和origin/master
两者都识别 commit J
。同时,控制存储库的人origin
说:哦,废话!提交J
是坏的!我将它扔掉git reset --hard
并添加一个新的提交K
! 现在你git fetch
再次运行并得到:
K <-- origin/master
/
...--H--I--J <-- master (HEAD)
你仍然有commit J
:它在你的 master
. 他们试图抛弃提交J
(无论它的实际哈希 ID 是什么——你的 Git 和他们的 Git 同意它的哈希 ID)。您的origin/master
now 指向K
, andK
的父母是I
, not J
。您origin/master
刚刚被强制更新。
您将在git fetch
输出中看到:
$ git fetch
...
+ a83509d9fc...0ddebcb508 pu -> origin/pu (forced update)
pu
Git 的 Git 存储库中的分支是每个人都同意定期强制更新的分支。所以我origin/pu
以前是识别a83509d9fc
的,现在它识别了0ddebcb508
。请注意+
,单词,以及两个哈希 ID 之间有三个(forced update)
点而不是两个点的事实:这些是宣布 my刚刚被强制更新的三种方式。我现在可以这样做: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
告诉我master
现在可以带入 210 个新的提交。要真正查看这些提交,我可能想要git log
。)
3没有父母的提交被定义为根提交。这就是你在一个新的、完全空的 Git 存储库中进行第一次提交时所做的那种提交。第一次提交不能有父级,所以它没有。
具有两个或多个父级的提交被定义为合并提交。git merge
这就是通常所做的那种提交。第一父母照常营业;任何其他父项都会告诉 Git 合并了哪些提交。
推荐阅读
- docker - Docker Trust - 使用自己的密钥而不是自动生成的?
- many-to-many - 方法 Illuminate\Database\Eloquent\Collection::detach 不存在。(我该如何解决?)
- azure - 是否有可能使用 Azure Devops REST API 自动创建整个 Azure 管道?
- python - Pandas Dataframe 在函数内分组
- automation - 空手道 API 自动化工具:断言 SOAP 响应
- jenkins - 由 groovy 添加到詹金斯作业的参数在构建页面上不可见
- apache-spark - Spark + 写入 Hive 表 + 解决方法
- scala - 如何使用 Scala 拆分 HDFS 文件
- django - 如何在 django 中插入一个表并更新另一个表
- java - 使用 Java 驱动程序对 MongoDB 聚合查找阶段的结果进行排序