如何签出仅在“git ls-remote”中列出的分支?

Cod*_*han 4 git github

我遇到了一种情况,无法切换到仅在 中列出的分支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
Run Code Online (Sandbox Code Playgroud)

列出分支:

$ 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
Run Code Online (Sandbox Code Playgroud)

该分支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
Run Code Online (Sandbox Code Playgroud)

我尝试了谷歌,但所有建议的方法都失败了。

那么我该怎么做才能切换到仅分支列表git ls-remote

tor*_*rek 6

在评论中提到您有多个遥控器origin并且upstream。这会干扰\xe2\x80\x94,可能会干扰\xe2\x80\x94a 人们通常不知道他们依赖的 Git 功能:git checkout所谓的DWIM 模式这还不是问题,但我们不妨解决它(在下面的长部分中)。

\n

您在包含此输出的第二条评论中提到git config -l

\n
remote.origin.fetch=+refs/heads/master:refs/remotes/origin/master\n
Run Code Online (Sandbox Code Playgroud)\n

对于具有origin.​ 正常设置是:

\n
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*\n
Run Code Online (Sandbox Code Playgroud)\n

git clone --single-branch如果您最初运行, 或git clone --depth=...(这意味着),则您拥有的设置是标准设置--single-branch

\n

为了使工作更方便,您需要更改或添加您的remote.origin.fetch设置。例如,如果您首先将其更改为+refs/heads/*:refs/remotes/origin/*(请参阅VonC\'s 更新的答案),则可以运行:

\n
git fetch origin\n
Run Code Online (Sandbox Code Playgroud)\n

其次是:

\n
git checkout -t origin/fix-lsp-cache-dir\n
Run Code Online (Sandbox Code Playgroud)\n

甚至只是:

\n
git checkout fix-lsp-cache-dir\n
Run Code Online (Sandbox Code Playgroud)\n

如果您只有一个遥控器,这种最短的方法将始终有效origin。如果您有多个远程名称,有时会失败,在这种情况下,您需要使用稍长的命令或单独的命令来创建您自己的分支名称。git checkout -t origin/fix-lsp-cache-dirgit branchfix-lsp-cache-dir

\n

无论如何,您都需要一个git fetch从第一个获取的origin。您可以origin在 中明确命名,或者使用从所有git fetch遥控器获取的选项之一(或者,尽管使用流浪进入新领域,带来许多新选项)。git fetch --allgit remote updategit remote

\n

Long:幕后发生了什么

\n

为了理解这一切,您需要了解以下所有内容:

\n
    \n
  • 分支名称,您已经熟悉它,但在内部存储时refs/heads/卡在前面(正如您所看到的git ls-remote);

    \n
  • \n
  • 远程跟踪名称\xe2\x80\x94Git 调用此远程跟踪分支名称,但它们实际上不是分支名称,所以我更喜欢从中间删除该单词:它们在内部存储为refs/remotes/在前面,后面是远程名称本身;

    \n
  • \n
  • remotes,它们是类似originupstreamthat 的短字符串,如果没有别的的话\xe2\x80\x94通常还有其他东西\xe2\x80\x94存储一个URL;

    \n
  • \n
  • refsreferencesrefs/tags/* ,它们是分支名称、标记名称( )、远程跟踪名称以及其他不太常见的名称(例如refs/notes/*和 )的长形式refs/stash

    \n
  • \n
  • refspecs,主要是由冒号分隔的引用对:,并且可以选择以加号作为前缀+;最后,

    \n
  • \n
  • git checkout的“DWIM 模式”功能。DWIM 代表“Do What I Mean ”(按照我的意思做,而不是我输入的内容)。这个特殊的缩写可以追溯到 Xerox PARC 和 Warren Teitelman:请参阅Eric Raymond 的行话文件条目关于 Teitelman 的维基百科文章

    \n
  • \n
\n

参考、参考规格和遥控器

\n

真的,你已经了解裁判了。它们只是各种参考文献的全名。他们让命令git fetch知道他们是否正在处理分支名称(refs/heads/master)或远程跟踪名称(refs/remotes/origin/master)或其他什么,如果他们关心的话。1

\n

refspec最简单的形式就是一对带冒号的 ref。左边的名称是,右边的名称是目的地。对于git fetch部分意味着:使用您在输出中看到的相同内容git ls-remote,在我从中获取的存储库中查找名称和值。 目的地部分意思是创建或更新目标名称。

\n

前导加号(如果出现)会为由于以下原因而--force发生的任何更新设置标志:。因此:

\n
+refs/heads/master:refs/remotes/origin/master\n
Run Code Online (Sandbox Code Playgroud)\n

参考规范说:抓住他们的master分支,并使用它来创建或更新我的origin/master远程跟踪名称。如果需要,强制执行此更新。 您将获得他们在其 上 master的任何新提交,然后创建或更新您的origin/master. 您将自己进行此更新,即使这意味着在此过程中origin/master某些提交“脱落” (origin/master--force ( )。

\n

我提到过遥控器不仅仅包含一个URL。每个远程都会列出一定数量的默认获取引用规范。通常只有一个,但通常是:

\n
+refs/heads/*:refs/remotes/<remote>/*\n
Run Code Online (Sandbox Code Playgroud)\n

这个特定的参考规范remote说:获取所有分支名称refs/heads/*\xe2\x80\x94 与\xe2\x80\x94匹配的所有字符串,并强制创建或更新我所有相应的远程跟踪名称。 远程对应的名称originrefs/remotes/origin/*,因此这就是此处显示的内容。

\n

单分支克隆通过在 refspec 中使用单分支名称的简单权宜之计来工作。现在您git fetch不会创建或更新其余的潜在远程跟踪名称。解决这个问题,您git fetch 创建或更新其余的远程跟踪名称。

\n

请注意,使用refs/heads/*可启用另一项功能:--prune。添加--prune到您的git fetch命令\xe2\x80\x94或在您的配置中设置fetch.prune为\xe2\x80\x94 ,不仅会创建或更新正确的远程跟踪名称集,还会删除任何不再存在的剩余远程跟踪名称有来源。truegit fetch

\n

例如,如果 Git 上origin有一个命名X为短时间的分支,并且您运行git fetch,您的 Git 将创建您自己的origin/X. 但是,控制 origin 上的 Git 的人就会删除分支X。如果您没有启用修剪,您将继续携带origin/X:您的 Git在它存在时创建并更新了它,但现在它没有,您的 Git 对此不做任何事情。启用修剪,你的 Git 对自己说:啊哈,我有剩余的垃圾origin/X!我会自动把它剪掉。 修剪可能应该是默认设置,带有“不修剪”选项,但事实并非如此。

\n
\n

1 Fetch 实际上确实关心,因为它试图用标签做一些神奇的奇怪的事情。

\n
\n

检查“DWIM 模式”,以及两个或多个遥控器失败的时间和原因

\n

当您第一次克隆 Git 存储库(不带--single-branch)时,您自己的 Git 会获取存储库中每个分支的远程跟踪名称origin

\n
git clone https://github.com/git/git/\n
Run Code Online (Sandbox Code Playgroud)\n

例如,为您提供 GitHub 上 Git 存储库中五个分支的五个远程跟踪名称。

\n

作为最后一步git clone,您的 Git 将有效运行2git checkout master在此阶段,您还没有名为的分支master。事实上,你根本没有分支名称!那么怎样才能git checkout查出来呢?怎么能:

\n
git checkout <name>\n
Run Code Online (Sandbox Code Playgroud)\n

当根本没有分支名称时,曾经工作过吗?

\n

答案是git checkout实际创建您的分支名称master。另请参阅下面的侧边栏(由于我无法制作真正的侧边栏,因此将其格式化为额外部分)。当git checkout给出看起来可能是分支名称但实际上不是的名称时,它会查看所有远程跟踪名称:origin/masterorigin/maintorigin/next等,例如,如果您正在使用 Git 的 Git 存储库。如果恰好有一个匹配,那么您的 Git 就会像您实际运行一样运行:

\n
git checkout -t origin/<name>\n
Run Code Online (Sandbox Code Playgroud)\n

它告诉git checkout创建分支,将远程跟踪名称设置为其上游。 现在这个名字已经存在了,现在 git checkout可以检查一下。

\n

如果有两个或多个匹配的名称,则此过程将失败。 例如,假设您没有分支fix-lsp-cache-dir名称,但在您自己的 Git 存储库确实origin/fix-lsp-cache-dir upstream/fix-lsp-cache-dir. 你跑:

\n
git checkout fix-lsp-cache-dir\n
Run Code Online (Sandbox Code Playgroud)\n

它没有找到fix-lsp-cache-dir,但找到了origin/fix-lsp-cache-dirupstream/fix-lsp-cache-dir。它发现的不是一个而是两个远程跟踪名称。应该用其一origin,还是用其一upstream?它不知道。

\n

此时,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这提供了有关要使用哪个上游远程跟踪名称以及要创建哪个分支名称 的答案。

\n
\n

2我在这里说“有效”,因为里面的代码git clone不会真正运行git checkout,也不会打扰很多 DWIM 模式的东西:它确切地知道它已经放入存储库中的内容并且可以作弊。如果你分开你的git clone一系列单独的命令:

\n
git init\ngit remote add origin <url>\ngit fetch\ngit checkout master\n
Run Code Online (Sandbox Code Playgroud)\n

你真的会跑git checkout master并调用我正在描述的 DWIM 模式。

\n

(心理练习:比较和对比 Git 的分支 DWIM 和智能手机的自动更正功能。)

\n

超长侧边栏:Git 分支的实际工作原理

\n

每个 Git 分支名称 \xe2\x80\x94 事实上,每个 Git引用\xe2\x80\x94 实际上只存储一个哈希 ID。对于分支名称\xe2\x80\x94 以及远程跟踪名称\xe2\x80\x94,哈希 ID 被限制为提交哈希 ID;其他一些引用具有更大的灵活性,例如,标记名称可以指向 Git\ 四种内部对象类型中的任何一种。

\n

问题是,当我们说“分支master”或“此提交在分支上master”或类似的任何内容时,我们通常并不是指一个特定的提交,即使实际的分支名称 master只能标识一个特定的提交犯罪。它的工作原理解释了很多关于 Git 的内容。

\n

胶囊形式:

\n
    \n
  • 为了创建分支,我们将一些现有的有效提交的哈希 ID 写入以前不存在的名称中。

    \n
  • \n
  • 为了更新分支,我们将一些现有的有效提交的哈希 ID 写入已经存在的名称中。它不再标识它刚才记住的提交。现在它识别出我们选择的那个。

    \n
  • \n
\n

但无论如何,我们都从提交哈希 ID 开始。所以从某种意义上说,重要的是提交,而不是分支名称(尽管我们当然也想要这些!)。

\n

在 Git 中,每个提交都由其自己唯一的、又大又难看的哈希 ID 来标识。例如,Git 的 Git 存储库中的一项提交是9c9b961d7eb15fb583a2a812088713a68a85f1c0. (这是为 Git 版本 2.23 做准备的提交,但不是任何特定版本。)这些哈希 ID 适合Git使用\xe2\x80\x94it\'s 计算机程序,并且不会使使用这些东西作为键值数据库\xe2\x80\x94中的键是错误的,但它们对人类来说几乎没有用处。我们在名字上做得更好,比如master。如果我们创建分支名称master并使该名称表示“提交9c9b961d7eb15fb583a2a812088713a68a85f1c0”,我们可以运行:

\n
git log master\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
git diff my-branch master\n
Run Code Online (Sandbox Code Playgroud)\n

管他呢。每次名称master都会挑选出提交。9c9b961d7eb15fb583a2a812088713a68a85f1c0但是 Git 如何知道提交\xe2\x80\x94 另一个看起来随机的哈希 ID\xe2\x80\x94 是在( )之前的8619522ad1670ea82c0895f2bfe6c75e06df32e7提交呢? master9c9b961d7eb15fb583a2a812088713a68a85f1c0

\n

答案是8619522ad1670ea82c0895f2bfe6c75e06df32e7存储在里面 9c9b961d7eb15fb583a2a812088713a68a85f1c0

\n
$ git cat-file -p 9c9b961d7eb15fb583a2a812088713a68a85f1c0 | sed \'s/@/ /\'\ntree 33bba5e893986797fd68c4515bfafd709c6f69e5\nparent 8619522ad1670ea82c0895f2bfe6c75e06df32e7\nauthor Junio C Hamano <gitster@pobox.com> 1563561263 -0700\ncommitter Junio C Hamano <gitster@pobox.com> 1563561263 -0700\n\nThe sixth batch\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n
Run Code Online (Sandbox Code Playgroud)\n

这里的行给出了先前parent提交的原始哈希 ID 。

\n

每个 Git 提交\xe2\x80\x94 好吧,几乎每个\xe2\x80\x94 都有至少一个父级3 Git 可以在历史上向后退一步,从提交到其父提交。父级本身还有另一个父级,因此 Git 可以多移动一步。从提交一步步移动到父级所获得的路径就是Git存储库中的历史记录。

\n

对于简单的线性链,我们可以通过假装 Git 为每次提交使用一个字母名称来代替又大又丑的哈希 ID 来画出这一点:

\n
... <-F <-G <-H   <--master\n
Run Code Online (Sandbox Code Playgroud)\n

链中的最后一个提交是 commit H。这是存储在 name 下的哈希 ID master。我们说master 指向 H. H依次存储 的哈希 ID G,因此我们说H指向GG存储 的哈希 ID F,因此G指向FF指向F\ 的父级。这一直持续下去,直到我们遇到没有父级的提交,例如此存储库的第一次提交......而这些是“on”分支的提交master

\n

要添加新的提交,我们让 Git 保存所有源文件的快照,添加我们的姓名和电子邮件地址以及git log显示的其他内容,使用提交的实际哈希 IDH作为父项,然后写出新的提交。这个新的提交获得一个新的、唯一的哈希 ID,但我们将其称为Imaster然后 Git 只需用这个新的哈希 ID覆盖该名称:

\n
... <-F <-G <-H <-I   <--master\n
Run Code Online (Sandbox Code Playgroud)\n

现在分支master的提交时间更长了。链中的最后一次提交称为提示提交。我们知道\xe2\x80\x94或find\xe2\x80\x94提示通过读取分支名称中的哈希ID在Git存储库中提交。

\n

分支名称master只是标识链中的最后一次提交。 移动分支名称或远程跟踪名称的各种 Git 命令,例如git resetor git branch -f\xe2\x80\x94 用于远程跟踪名称\xe2\x80\x94 git fetch\xe2\x80\x94 实际上只是使名称指向一项特定的提交。

\n

如果我们可以从新的提示开始,并使用内部的向后箭头找到旧的提示,那么我们所做的就是向分支添加一些提交。当我们用来git commit创建一个提交时,这就是它所做的:它创建一个新的提交,该提交成为提示,并且将旧提示作为其父级。

\n

当我们使用git fetch并获得远程跟踪名称的三到五个新提交时origin/master最后一个\xe2\x80\x94提示\xe2\x80\x94最终会返回到我们运行之前origin/master所指向的位置。因此,新提交只是新添加到远程跟踪名称中。git fetchorigin/master

\n

Git 将这种仅添加内容的名称称为“快进”。您可以使用 进行快进git fetch,更新远程跟踪名称,并使用 进行快进git push,向其他一些 Git 提供新提交并让它们更新其分支名称。在这两种情况下,你的 Git 和/或他们的 Git 都没有丢失任何提交,因为从新提示开始并向后工作,您或他们到达旧提示。

\n

您还可以\xe2\x80\x94(带有一些额外的皱纹)\xe2\x80\x94 进行快进git merge。如果git merge进行快进而不是合并,则会使用您已有的提交,而不会实际进行任何新的提交。例如,在 后git fetch origin,您可能有:

\n
...--F--G--H   <-- master (HEAD)\n            \\\n             I--J   <-- origin/master\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,您实际上是独自一人,通过在名称上master附加特殊名称来表明。您的 Git 现在可以通过移动名称使其指向 commit并同时执行 a of commit来执行快进而非真正的合并:HEADmastermasterJgit checkoutJ

\n
...--F--G--H--I--J   <-- master (HEAD), origin/master\n
Run Code Online (Sandbox Code Playgroud)\n

这就是快进合并:它实际上根本不是合并,而只是git checkout将当前分支名称向前拖动,就像刚才git fetch快进的方式一样。origin/master

\n

--force当操作不是进时需要该标志。例如,假设您刚刚执行了上述操作,那么现在masterorigin/master都识别了 commit J。与此同时,无论谁控制了存储库,都会origin说:哦糟糕!承诺J很糟糕!我将其扔掉git reset --hard并添加一个新的提交K 现在你git fetch再次运行并得到:

\n
          K   <-- origin/master\n         /\n...--H--I--J   <-- master (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

仍然有提交J:它在你的 master. 他们试图丢弃提交J(无论其实际哈希 ID 是\xe2\x80\x94,你的 Git 和他们的 Git 都同意其哈希 ID)。您的origin/masternow 指向K,而K\ 的父级是I,而不是J。您origin/master刚刚被强制更新

\n

您将在git fetch输出中看到这一点:

\n
$ git fetch\n...\n + a83509d9fc...0ddebcb508 pu          -> origin/pu  (forced update)\n
Run Code Online (Sandbox Code Playgroud)\n

Git 的 Git 存储库中的分支pu是每个人都同意定期强制更新的分支。所以我origin/pu以前可以识别a83509d9fc,但现在它可以识别0ddebcb508。请注意+,单词,以及两个哈希 ID 之间有三个(forced update)而不是两个点的事实:这些是宣布 my刚刚被强制更新的三种方式。我现在可以这样做:git fetchorigin/pu

\n
$ git rev-list --left-right --count a83509d9fc...0ddebcb508\n79  214\n
Run Code Online (Sandbox Code Playgroud)\n

这告诉我删除了 79 个提交(从我的旧的origin/pu),并添加了 214 个提交(到我的新的更新的origin/pu)。我实际上并不关心这种情况,但如果我出于某种原因这样做了,我可以看到他们在origin

\n

(稍微有用一点:

\n
$ git rev-list --count master..origin/master\n210\n
Run Code Online (Sandbox Code Playgroud)\n

告诉我现在可以添加 210 个新提交master。为了实际查看这些提交,我可能想要git log。)

\n
\n

3没有父提交的提交被定义为根提交。这就是您在新的、完全空的 Git 存储库中进行第一次提交时所做的提交。第一个提交不能有父提交,因此它没有。

\n

具有两个或更多父级的提交被定义为合并提交。这就是git merge通常所做的承诺。第一个父母一切照旧;任何其他父级都会告诉 Git 合并了哪些提交。

\n