【问题标题】:Rebase based on remote commit sometimes give 'fatal: invalid upstream' error基于远程提交的变基有时会给出“致命:无效上游”错误
【发布时间】:2022-08-23 15:40:31
【问题描述】:

场景是这样的:我创建了一个本地分支 feature1

[local] main - feature1

我将feature1 上的更改推送到origin main

[origin] main - change1

我通过 UI 在 change1 上编辑了一些东西(可能更改了标题,或者重新基于不同的更改)

[origin] main - change1-1

现在我希望基于change1-1 更新我的本地分支feature1。 在这种情况下,我尝试了rebasecheckout

git switch feature1
git fetch origin
git rebase <SHA-of-change1-1>
or 
git checkout <SHA-of-change1-1>

有时这有效,但有时无效,老实说,我不知道每种情况有什么区别。

当 rebase 不起作用时,我看到了

fatal: invalid upstream <SHA-of-change1-1>

当结帐不起作用时,我看到

fatal: reference is not a tree: <SHA-of-change1-1>

    标签: git gerrit


    【解决方案1】:

    TL;博士

    你可能需要设置你的 Git 来获取refs/changes/*

    git config --add remote.origin.fetch "+refs/changes/*:refs/changes/*"
    

    稍后,您可能会考虑直接使用refs/changes/,或者继续使用原始提交哈希ID。

    长(但如果你使用 Gerrit,请阅读它)

    这里可能有多个问题需要解决。让我们从第一个开始,它本身并不重要今天但总有一天会很重要:Git 不再将提交 ID 称为或者SHA-1Git 现在在内部支持多种不同的哈希算法的哈希 ID。因此对于吉特这些是对象 ID或者OID.然而,出于好的和重要的原因,几乎没有人使用除 SHA-1 散列之外的任何东西,因此 OID 几乎总是 SHA-1 散列 ID。 ? 但是一个Git 提交哈希不再称为“SHA”。

    第二——这可能更重要——格里特分配它自己的将 ID 更改为一系列提交用于实现一些实际的变化。 These Gerrit change-IDs start with the letter I. Gerrit 更改 ID 与 SHA-1 非常相似,因为 Gerrit 实际上会运行一些 Git 操作来生成 Git 哈希 ID,并且只要生成的 Git 哈希 ID 在内部是 SHA-1 哈希 ID(通常是这样)你得到一个 SHA-1。然后 Gerrit 将字母 I 粘贴在前面,它永远不会出现在真正的 SHA-1 哈希 ID 中,因为它们在 hexadecimal 中表示。

    Gerrit 生成这个 change-ID 的原因是格里特可以跟踪用于完成某些任务的提交。提交的集合达到预期的结果会随着时间的推移而发展,但它们会保持不变更改 ID这样它们就可以聚集在一起进行审查和其他管理步骤,同时引导错误修复或增强,或者通过将其放入软件的过程中可能发生的任何事情。吉特对这个 Gerrit 实体一无所知:Git 只知道提交。

    因此,在这一点上要记住以下几点:

    • 吉特使用一个对象 ID找到任何一个给定的提交。此对象 ID 指定恰好一个提交, 没有两个不同的提交曾经重用 Git 哈希 ID。Git 哈希 ID 永远不会以 I 开头。

    • 格里特使用一个更改 ID找到一个“Gerrit 更改”。这个 ID 对 Git 来说是陌生的;如果你曾经交过这个 ID,Git 会很困惑吉特。切勿将此 ID 直接提供给 Git。然而,格里特将使用此 ID 来定位某些 Gerrit 级别任务的“更改集”(一个或多个提交的某个集群):始终使用相同的格里特该任务的 ID,以便 Gerrit 可以跟踪它。不要给 Gerrit吉特哈希 ID。Gerrit 更改 ID 始终以 I 开头。

    因此I ID 归 Gerrit,而非I ID可能使用 Git。这个单词可能在这里,因为您的问题实际上可能不是上述任何一个。

    Git 级别的 fetch 操作

    你提到过

    我通过 UI 在 change1 上编辑了一些东西(可能更改了标题,或者重新基于不同的更改)

    吉特没有这种用户界面。一些 Git 托管站点确实添加了自己的 UI,但 Git 不知道它们。在 Git 命令行级别 — 运行 git rebasegit cherry-pickgit loggit checkout 和其他此类 Git 命令1——Git 不会知道你在这里所做的任何事情。

    现在我希望根据 change1-1 更新我的本地分支 feature1。在这种情况下,我尝试了变基或结帐。

    git switch feature1
    git fetch origin
    git rebase <SHA-of-change1-1>
    or 
    git checkout <SHA-of-change1-1>
    

    有时这有效,但有时无效,老实说,我不知道这两种情况有什么区别。

    此处的git fetch origin 步骤是必要的,它会导致或至少会导致您的吉特获取任何新提交的软件Gerrit 系统在您在这里使用的任何托管系统上使用的 Git 服务器。

    然而,可能的问题是,格里特变化——可能包括一个或多个吉特 提交——它本身不是一个 Git 实体。您使用某些 UI 所做的任何新提交都将是此时 Gerrit Git 服务器,但它们可能在姓名Git 不知道的。这是我们进入 Git 的一些深奥和异国情调的地方。

    Git 实际上使用哈希 ID(我们不应该再将其称为“SHA”,即使它们可能仍然是 SHA-1 ID)来唯一标识提交。 git fetch 操作将经常, 但不是总是,从其他 Git 存储库获取任何新提交。棘手的部分是来自其他 Git 的传输操作取决于名字存储在另一个 Git 存储库中。

    正常的名字我们(人类)使用的,存储在任何普通的日常 Git 存储库中,以 refs/heads/refs/tags/refs/remotes/ 开头。这些前缀字符串将名称分配给 namespace(有时称为名称空间、连字符或名称空间,两个词):refs/heads/ 中的名称是分行名称refs/tags/ 中的那些是标签名称,而refs/remotes/ 中的那些是远程跟踪名称.

    当您运行 git fetch origin(或只是 git fetch)时,您的 Git 软件会调用他们的 Git 软件,连接到他们的 Git 存储库,并列出他们的名称,包括他们的分支和标签名称。然后你的 Git 软件会仔细检查它们的分支和标签名称,寻找对你来说新的提交。找到此类提交后,您的 Git 软件会将这些提交带到您的Git 存储库。

    如果您获得了这些提交,则可以通过它们的 Git 提交哈希 ID(它们的 Git 级 OID)来引用它们。如果你有提交并使用吉特OID,这个总是有效.但:

    • 你需要有提交, 和
    • 您需要使用Git OID,而不是 Gerrit ID。

    我猜你的特定问题很可能是这两点中的第一点,那是因为当有人用一些新的提交更新 Gerrit 更改请求时,格里特告诉 Git存储最新的 Git ID使用不符合上述模式的名称。

    在我们继续描述 Gerrit 命名系统之前,让我们完成关于git fetch 的最后一点。由于 Gerrit 做事的方式,这无关紧要然而,但它将在下一节中介绍。

    看到他们的分支名称和哈希 ID,你自己的 Git 软件重命名他们的分支成为你的名字远程跟踪名称.因此他们的 Git 分支名称 main 成为您的远程跟踪名称 origin/main;他们的 Git 分支名称 develop 成为您的远程跟踪名称 origin/develop;他们的 Git 分支名称 feature/tall 成为您的远程跟踪名称 origin/feature/tall;等等。重命名采用他们的分支名称并将origin/ 放在前面,origin 部分来自我们运行git fetch origin 的事实(或者如果我们运行git fetch,则意味着git fetch origin)。 Git 移动他们的分支命名空间名称到我们的远程跟踪命名空间,并将origin/ 放在前面,这样如果我们有多个偏僻的,这一切都有效。2

    Git 分支名称总是意味着最后的提交我们应该称为“在”或“在”那个分支。 (这就是 Git 定义分支名称的方式:其中存储了任何哈希 ID,即“在”该分支上的最后一次提交的哈希 ID。)所以在 git fetch 之后,我们的 Git 更新了我们的远程跟踪名称匹配他们的分支名称,因此我们的远程跟踪名称对我们来说和他们的一样好分支名字为他们工作。我们是否希望看到最新的提交他们的develop 分支,我们可以让 Git 向我们展示我们的 origin/develop 远程跟踪名称的最新提交。

    请注意,您必须经常运行git fetchGit 不会一直在线:它只会接收新的提交当你运行git fetch.


    1请注意,Gerrit 将其自己的命令行命令添加到该集合中。例如,git review 实际上是一个格里特命令,而不是 Git 命令。所以你不能使用命令的git 部分来假设某事是较低级别的吉特命令。

    2大多数人在他们的设置中大多只有一个遥控器。您可以使用git remote add 添加第二个遥控器,之后您将拥有第二组远程跟踪名称。如果你运行git remote add r2 <em>url</em>,然后运行git fetch r2,你会让你的Git填写一堆refs/remotes/r2/*名称,git branch -r将显示为r2/mainr2/developr2/feature/tall等等.这里r2 是另一个偏僻的r2/* 的名字更多远程跟踪名称.

    通常的originorigin/* 是通常的第一的远程和远程跟踪名称。 git clone 命令设置 origin作为第一个遥控器,然后为您运行初始git fetch origin。大多数人使用git clone 创建他们的大部分Git 存储库,因此大多数人在他们的大多数Git 存储库中都有一个名为origin 的遥控器。


    特殊的 Gerrit 命名空间

    要在 Gerrit 内部引导 Git 提交,Gerrit makes use of several namespaces that the Gerrit folks made up。一个命名空间以refs/for/ 开头,然后包含一个分支名称,例如mastermain,或develop,或feature1,等等。

    要使用它,您需要进行一组更改,然后运行:

    git push origin feature1:refs/for/feature1
    

    这个特殊的命名空间非常神奇和虚假:这里的传入提交是格里特读并且永远不要放入refs/for/。 (您的 Git 软件会将这些视为已被接受,并且思考他们的 Git 创建或更新了refs/for/feature1,但它没有。)

    Gerrit 创建和使用的第二个命名空间以refs/changes/ 开头。一旦更改分配了 Gerrit 更改 ID,每个 Git 提交系列都会被赋予适当的魔法 refs/changes/ 名称。 Gerrit 文档(上面链接)以这种方式描述了这个空间:

    在这个命名空间下,每个为每个更改上传的补丁集都会在他们的 git 中获得一个静态引用。该格式很方便,但仍打算扩展到数十万个补丁集。要访问给定的补丁集,您将需要更改编号和补丁集编号。

    refs/changes/<em>last two digits of change number</em>/<em>change number</em>/<em>patch set number</em>

    您还可以在每次更改的页面上找到链接的这些静态引用。

    如果你让你的 Git 软件获取这些名称,这将迫使你的 Git 软件下载所有提交.请注意,您将获得允许获得的每一个可审查的提交!这个命名空间显然强制执行了 Gerrit 端访问控制,因此您可能无权查看部分或全部名称;如果是这样,那可能是一个无法克服的问题,您可能不得不避免使用 UI(或让您的 Gerrit 管理员授予您读取权限)。没有使用 Gerrit,我将所有这些都基于我在上面链接的页面中阅读的内容。

    无论如何,假设 refs/changes/* 技巧有效,您现在将拥有所需的提交。您可以通过 Git 的哈希 ID 引用它们(记住不要再将其称为“SHA”),无论您是否使用:

    git rebase <SHA-of-change1-1>
    

    或者

    git checkout <SHA-of-change1-1>
    

    基本要求这是您的 Git 拥有对象,因此哈希 ID 有效,并且您使用正确的原始 Git 哈希 ID,而不是 Gerrit 更改 ID。我们通过运行来满足这个基本要求:

    git config --add remote.origin.fetch "+refs/changes/*:refs/changes/*"
    

    一次在我们的克隆中,以便git fetch origin 读取所有refs/changes/* 名称并将其复制到我们自己的存储库中,从而迫使我们的 Git 选择适当的 Git 对象。3

    现在您有了refs/changes/*,您可能想要使用 Gerrit 更改 ID。正如我在上面所引用的,refs/changes/zz/Ixxxxxxx...xxzz/1(或者可能是 refs/changes/zz/xxxx...zz/1/01 或其他任何可能)姓名将保存正确的 Git 哈希 ID。通过查看特殊的命名空间名称,您可以参考之前发布的提交集以供审查。

    (无论是 Git 原始哈希 ID,还是 Gerrit 生成的 Gerrit 更改 ID,对您来说更方便完全是另一个问题。可能有一些附加软件可以让您更方便地处理这个问题,如果没有的话,你可以自己写。)


    3如果您知道自己在做什么,则可以将其添加到您的全局 Git 配置中,或者添加到所有 Gerrit 克隆的包含配置中,或其他任何内容。以这种方式请求不存在的 refs 通常是无害的,但在使用 --global 进行类似设置之前了解自己在做什么总是一个好主意。


    关于 Git rebasecheckoutswitch 的注释

    你提到:

    当 rebase 不起作用时,我看到了

    fatal: invalid upstream <SHA-of-change1-1>
    

    当结帐不起作用时,我看到

    fatal: reference is not a tree: <SHA-of-change1-1>
    

    正如 Gerrit 文档所说,其原因在于一些“坚韧不拔的细节”,即关于 rebase 和 checkout 的工作方式。

    Git 将几乎所有内容都存储为犯罪.提交有一个唯一的哈希 ID——我们不应该再称之为“SHA”的东西——它在 Git 的大型全对象数据库中定位该提交。但是什么无论如何提交?答案有两个:

    • 每个提交都包含一个每个文件的完整快照.提交中的文件存储在一个特殊的、只读的、压缩的(有时是高度压缩的)和去重形式,所以考虑到大多数提交主要重用早期提交的文件,而那些不主要是小的改变对于一个文件,每个文件的这些归档版本占用的空间非常少。重复项都被完全删除,类似的文件最终(但不是立即——这部分很棘手)使用delta compression,这样他们几乎不占用任何空间,直到存储存档文件的位置存储库可能需要更小的空间而不是您在结帐时获得的可用、可编辑的文件。

    • 同时,每个commit都存储了一些metadata,或有关提交本身的信息。我们不会在这里详细介绍,因为我们不会深入到 rebase 来需要它。

    让您使用文件提交,Git 必须提炼那些文件。存储的文件是无用的格式:只有 Git 可以读取它们,实际上没有任何东西,甚至是 Git 本身,都不能覆盖它们。可用文件需要可读可写.因此git switchgit checkout 采用提交哈希ID 并使用它来定位充当永久存档的所有文件的快照。 Git 称之为,这就是为什么你会看到:

    fatal: reference is not a tree ...
    

    如果您给 Git 一个它不能用作提交对象(然后定位树对象)的 ID,并且 Git 也不能​​直接用作树对象。

    git switch 命令需要一个分店名称,如:

    git switch feature1
    

    除非你使用--detach 操作,但git checkout 操作会自动认为--detach 如果你给它一个提交或树哈希 ID。这两个命令,给定--detach(或假设它,如果合适的话),将进入 Git 的分离头模式并检查与某个提交关联的树,给定提交的 ID。然后,您可以查看所有文件,或构建它们,或做任何您喜欢的事情。

    请注意,文件摘自提交是不在 Git 中.那些文件在 Git 中是压缩的、去重的、Git 化的档案。这些可以——而且事实上曾经——用于生产您刚刚获得的可用文件,但您所做的任何更改那些生成的文件也不在 Git 中。你必须 git addgit commit 让 Git 存储一个新的提交。

    git rebase 命令比git checkoutgit switch 命令更复杂。当我们使用git rebase 时,我们是在告诉 Git 我们有一些提交——一系列提交中的一个或多个提交——我们喜欢的地方一些事物关于那些提交,和不喜欢关于他们的其他一些事情。现在,事实是全部Git 提交是完全只读.任何 Git 提交都无法更改,即使 Git 本身也无法更改。但是我们不喜欢关于现有提交的一些东西:我们改变。

    Git 允许我们这样做的方式是 Git 让我们构建一个新的提交系列从原始提交。当我们以最奇特的形式使用它时,如git rebase -i,它:

    • 检查我们最后一次提交想改变;
    • 使用git cherry-pick 应用,但不实际提交,我们要提交的第一个喜欢改变;然后
    • 在这个交互式变基中间停止。

    这使我们有机会获取工作树中的文件——这些文件现在是普通的日常文件,并且能够被改变——并且改变如果我们愿意的话。然后我们运行git addgit commit,或者git rebase --continue 将运行git commit 为我们创建一个新的和不同的尽我们所能就像修好了一样。这可以像更改元数据中的日志消息一样简单,也可以像我们喜欢的那样复杂,对许多源文件进行许多更改。但无论如何,我们已经接受了我们最初的提交——我们喜欢一些关于,但不是一切——并用它制作了一个新的和不同的提交,它得到一个新的和不同的哈希 ID。一旦更正的提交到位,rebase 可以继续进行以后的提交,复制那些也适用于新的和改进的提交。当 rebase 完成最后一个必要的副本时,它会存储最后的新的和改进的提交到分支名称中。由于定义的分支名称说明了哪个提交是最后的一,即完成操作。

    通过交互式 rebase,我们可以在这里获得很多控制权。对于其他类型的变基操作,我们放弃了部分或全部这种控制,让我们做的更少,但更容易完成。这里有一个普遍的原则,当改写成蜘蛛侠电影或漫画书时,变成With great power comes great responsibility。如果我们放弃很多权力,我们可能会少很多谨慎和负责任,但仍然会得到正确的结果。这就是为什么我们在 Git 中拥有越来越强大的工具,以便我们可以使用正确的工具来完成工作。4

    在任何情况下,git rebasegit checkout 的主要区别在于 rebase将一个或多个提交复制到新的和改进的提交.它不仅查看一次提交。所以它实际上不能使用原始的ID。它需要一个犯罪哈希 ID。这就是为什么这里的错误消息说:

    fatal: invalid upstream ...
    

    我们提供的哈希 ID必须是一个提交,并 rebase 调用该特定提交上游的犯罪。 Rebase 实际上需要两个哈希 ID:一个上游的.但是,很多时候,可以指定两个ID使用一个单身的身份证或姓名。在这种情况下,我们只需提供一个 ID 或名称,Git 会自行计算出另一个。当我们确实需要这两个 ID 时,我们运行 git rebase --onto <em>onto</em> <em>upstream</em>,使用onto提供“onto”哈希 ID 和upstream仅提供上游的参数。当我们不使用--onto 时,upstream论据实际上是onto和 Git 计算出真实的 upstream独立——但 Git 仍然称其为upstream在其消息和the git rebase documentation 中。


    4请注意,同样的原则也适用于许多其他地方。一个设备齐全的木工车间不会只有一种锯子、一种锉刀或锉刀、一把凿子、一把锤子等等。但是您不会使用手锯来撕开房屋的胶合板护套,也不会使用钻床为室内装潢大头钉打孔。您需要合适的工具来完成这项工作。

    【讨论】:

    • 我要感谢@torek 以被删除为代价(Stackoverflow 中的模块似乎删除了欣赏的 cmets)提供了一个有趣的阅读,清理了我大部分杂乱无章的知识!读完这篇文章后,我有一些事情要自己解决,但这仍然是一个很好的开始。
    猜你喜欢
    • 1970-01-01
    • 2013-09-04
    • 2019-10-03
    • 2013-01-05
    • 2022-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    相关资源
    最近更新 更多