首页 > 解决方案 > 为什么 git reset 不能拉取请求

问题描述

有时我登录到一个云实例,拉一个 repo,然后想尝试一个拉请求。我现在使用的命令是

git fetch origin pull/<ID>/head && git checkout FETCH_HEAD

这很长。我也尝试过更短的方法

git reset --hard origin/pull/<ID>
git reset --hard origin/pull/<ID>/head
git reset --hard origin/pull/<ID>/HEAD

给出以下错误

$ git reset --hard origin/pull/27
 fatal: ambiguous argument 'origin/pull/27': unknown revision
 or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

为什么git reset --hard origin/<some-branch>工作但拉取请求分支不工作?

我注意到在输出中

$ git ls-remote origin

常规分支和拉取请求分支之间存在差异。例如

c31a55 refs/heads/fix-async-stdout-order

615f5a refs/pull/10/head

与有何heads不同?pull(我在这里缩短了哈希,所以它看起来更干净)

标签: gitpull-request

解决方案


每个 Git 存储库都有自己的名称副本。每个名称都映射到一个哈希 ID,例如,在您的示例中:

c31a55 refs/heads/fix-async-stdout-order
615f5a refs/pull/10/head

您建议refs/heads/fix-async-stdout-order映射到 hash ID c31a55,并refs/pull/10/head映射到 hash ID 615f5a1

名称是 Git 称为refsreferences的东西。(散列 ID 是您现在应该非常熟悉的散列。)

在大多数情况下,当您为 Git 命名时,Git 会立即将其转换为底层哈希 ID。哈希 ID 才是真正重要的:提供这些名称主要是为了让我们这些普通人能够处理作为哈希 ID 的真实姓名。哈希 ID 永远不会改变:它们总是唯一地标识特定的内容——例如,一个特定的提交。提交及其哈希 ID 是永久的,而一个或多个名称可以随意创建或销毁。2

当一个名称标识一个提交时,我们可以直接使用该名称来查找该提交:refs/heads/fix-async-stdout-order例如,标识 commit c31a55。但是提交也允许我们找到它们的直接提交:从c31a55我们可以向后工作到它的父提交,并且从那个父提交我们可以向后工作另一个步骤到另一个提交,依此类推,一直回到时间的开始在存储库中。因此,这样的名称不仅用于查找它存储其哈希 ID 的一个提交,而且还用于查找其链中所有较早的提交

当名称是分支名称时(就像这个名称一样),Git 还允许我们专门将它与git checkout. 该git checkout命令将另一个特殊名称 , 附加HEAD到分支名称。Git 现在认为我们“在”分支上,因此如果我们进行新的提交,Git 将自动更改存储在该分支名称下的哈希 ID

也就是说,之后:

git checkout fix-async-stdout-order

如果我们做一些工作然后进行的提交,名称fix-async-stdout-order——实际上是refs/heads/fix-async-stdout-order,我们只是为了显示目的而将其缩短了——将停止指向提交c31a55并开始,而是指向我们的新提交。我们的新提交将c31a55作为其父级。

能够“进入”一个分支的这个属性只允许用于分支名称。Git 有很多名称:分支名称、标签名称、远程跟踪名称等。它们都是引用,但只有拼写开头的引用refs/heads/才是分支名称。

引用refs/tags/v1.2(如果存在)是名为的标记v1.2

引用refs/remotes/origin/master(如果存在)是远程跟踪名称 origin/master

这些前缀中的每一个——<code>refs/heads/ refs/tags/、 和refs/remotes/——代表一个命名空间refs/heads/命名空间中的引用是分支名称

除了允许git checkout以上述方式使用它们方面的特殊性之外,分支名称的另一个特殊功能发生在客户端 Git(例如您自己的 Git)连接到服务器 Git 时。服务器 Git 为客户端显示其所有名称,包括分支名称,以及这些名称所代表的哈希 ID。客户端Git 会复制服务器的分支名称,但同时更改它们,以便服务器调用什么refs/heads/master,客户端调用什么refs/remotes/origin/master

这个过程是你的Git 首先拥有远程跟踪名称的方式。服务器 Git 有它的分支,你的 Git 会出现——当你运行时git fetch——看到并记住它们的分支作为你的远程跟踪origin/*名称。这些存在于您的 Git 中,在refs/remotes/命名空间中。

此过程发生在分支名称上!3 由于refs/pull/10/head不以 开头refs/heads/,因此不是分支名称。该过程不适用于refs/pull/10/head. 这就是为什么以及如何heads不同于pull.


1这些都是缩写的哈希 ID;实际的哈希 ID 目前总是 40 个字符长。

2这里需要注意的是,如果没有允许您查找提交或其他 Git 对象的名称,则该提交或其他对象现在不受Git 垃圾收集过程的保护。因此,名称不仅可以让我们找到链中的最后一个提交,还可以保护该提交及其所有前辈不被当作垃圾丢弃。

3这个过程是可编程的,通过 Git 所说的refspecs。上面的描述仅适用于您在运行时获得的默认git clonerefspecs 。如果您编写自己的 refspec,则可以更改此处发生的情况。请参阅文档并注意这git fetchremote.origin.fetch一个累积设置;每个实例都remote.origin.fetch提供一个 refspec。

克隆时的默认设置是 Git 创建一个remote.origin.fetch设置,将其所有分支复制到您的远程跟踪名称,或者如果您在克隆期间选择,则将其分支之一复制到一个远程--single-branch跟踪名称。


推荐阅读