首页 > 解决方案 > `是什么意思![拒绝] dev_tom -> dev_tom(非快进)* [新分支] dev_tom -> remote_repo/dev_tom`?

问题描述

当我 git 将远程仓库从分支拉到remote_repo/dev_tom我的本地仓库dev_top分支时:

dele-MBP:BK2 ldl$ git pull remote_repo dev_tom:dev_tom
dele@13.20.32.76's password: 
remote: counting: 45, complete.
remote: compressing: 100% (44/44), complete.
remote: Total 45 (delta 36), reused 0 (delta 0)
Unpacking objects: 100% (45/45), done.
From 13.20.32.76:/data/dele/repo/BK2
 ! [rejected]        dev_tom -> dev_tom  (non-fast-forward)
 * [new branch]      dev_tom -> remote_repo/dev_tom

你看到有两行信息,但我不明白,拉动作是失败的:

! [rejected]        dev_tom -> dev_tom  (non-fast-forward)
* [new branch]      dev_tom -> remote_repo/dev_tom

这两行是什么意思?

  1. 是什么意思dev_tom -> dev_tom
  2. 是什么意思dev_tom -> remote_repo/dev_tom
  3. !,是什么意思*
  4. rejected,是什么意思new branch?这new branch并不意味着创建一个新分支,好吗?
  5. 是什么意思non-fast-forward

我使用git fetch仍然无法正常工作,出现non-bare repository错误。

dele-MBP:BK2 ldl$ git fetch remote_repo dev_tom:dev_tom
ldl@13.20.32.76's password: 
fatal: Refusing to fetch into current branch refs/heads/dev_tom of non-bare repository

标签: git

解决方案


正如其他人所说,您可能不应该运行:

git pull remote_repo dev_tom:dev_tom

一点也不。但这并不能回答关于各种输出线的含义的问题,所以让我们来看看。请注意,这有点长,但它必须是完整的。

pull 表示 fetch+(merge|rebase),而 fetch 使用 refspecs

首先,记住这git pull实际上意味着运行两个 Git 命令。这两个 Git 命令中的第一个始终是git fetch. 大多数选项和参数git pull直接通过git fetch,所以:

git fetch remote_repo dev_tom:dev_tom

做了一些非常相似的事情,这解释了所有的输出行。所以我们将在这里专注于做什么git fetch,而不是担心git pull。相反,我会注意到第二个命令的重点是整合引入的东西git fetch。也就是说,这整个过程是一个两步过程:获取一些提交,然后对提交做一些事情。获取步骤是获取提交部分。1

git fetch命令调用其他 Git 以从其他 Git 获取提交。因此,当你使用 时git fetch,你必须告诉它几件事:

  • 它应该在哪里找到另一个 Git?

    在最基本的层面上,您可以git fetch为此提供一个 URL:

    git fetch https://github.com/path/to/repo.git
    

    例如。然而,URL 有点长并且每次都难以正确键入,因此 Git 提供了一种将 URL 存储在短名称下的方法,例如origin. 这使您可以运行:

    git fetch origin
    

    甚至只是,这意味着基于您签出的分支git fetch的远程(通常)。origin

    URL 存储在远程名称下:

    git config --get-all remote.origin.url
    

    (假设遥控器名为origin)。

  • 哪些分支名称(如果有)在其他 Git 中特别有趣?

    你不必说出任何名字。如果你不这样做,Git 的默认设置是考虑他们感兴趣的每个分支名称。这实际上是由您可以观察到的设置控制的:

    git config --get-all remote.origin.fetch
    

    请注意,此设置也位于遥控器的名称下(origin在这种情况下)。所以origin不仅提供了 URL,还提供了一组默认的有趣分支。(如果您使用 URL 而不是远程 like origin,您会错过这个“默认分支”功能。因此不仅origin更短且更容易正确键入,而且还可以获得更多功能。)

  • 最后,从另一个 Git 存储库获取提交存在问题。提交按其哈希 ID 编号(或命名)。git log您已经在输出中看到了哈希 ID :

    commit 745f6812895b31c02b29bdfe4ae8e5498f776c26
    Author: Junio C Hamano ...
    

    那个大而丑陋的字母和数字字符串745f681whatever,是提交的 ID。这些 ID 看起来完全是随机的(尽管它们实际上不是),并且无法知道哪个是哪个。这确实是分支名称 likemaster远程跟踪名称 likeorigin/master的全部内容。

让我们仔细看看结果git config --get-all remote.origin.fetch

$ git config --get-all remote.origin.fetch
+refs/heads/*:refs/remotes/origin/*

这个字符串——<code>+refs/heads/*:refs/remotes/origin/*——有几个部分:

  • 有一个可选的前导加号+,这个有。
  • 然后有两个由冒号分隔的字符串:。冒号几乎是可选的:冒号左侧部分,右侧目标部分。

Git 将这个东西——可选的加号,然后是源冒号目标——称为refspec。该fetch命令使用这个 refspec 来控制当其他 Git 提供它们时它查看哪些分支,以及它在你的Git 中更新的名称作为结果。

请注意,在这种情况下,我们的源部分是refs/heads/*

  • refs/heads/部分揭示了有关分支名称的一些重要信息:它们实际上只是更一般的 Git 机制的一个特定案例。更通用的机制称为refsreferences,它也包括标签名称。该分支 master实际上是refs/heads/master. 标签 v2.1实际上refs/tags/v2.1是. 2 各种 Git 命令省略了前面部分,只显示masteror v2.1,当很明显它是一个分支或标签名称时。

  • *部分表示所有名称。由于前导refs/heads/部分仅选择分支名称,因此*最终表示所有名称为分支名称。这就是为什么我们的 Git 会默认查看所有Git分支的原因。

我们的目标部分是refs/remotes/origin/*

  • refs/remotes/部分是指我们的远程跟踪名称。下一个文字字符串 ,origin/只是一个文字origin/

  • *意味着与源部分匹配的任何内容

所以这就是他们的分支——他们的masterref——如何refs/heads/master成为我们的远程跟踪名称origin/master,也就是我们的refs/remotes/origin/master. 因为这个 fetch refspec,我们把他们的master, 改成了 our 。origin/master

因此,一个简单git fetch origin的方法是让我们的 Git 调用他们的 Git,从他们那里获取所有分支名称的列表,并将所有这些名称复制到我们的远程跟踪origin/*名称中。我们的 Git 对其 Git 分支的记忆将被更新。这也是前导加号出现的地方:它是一个强制标志


1为您运行的第二个命令git pull默认为git merge,但您也可以选择git rebase你应该使用哪一个?这取决于你——但我会在这里指出,在某些情况下,你可能想根据git fetch获取的内容来决定。如果您使用,则在查看获取的内容之前git pull,您必须决定使用第二个命令。由于这个和许多其他原因,我根本不想使用。只需自己运行这两个命令中的每一个,当您想决定使用哪个命令来完成工作时,在它们之间运行。git fetchgit pullgit log

2这意味着您可以创建一个名为master-<code>refs/tags/master 的标签,它不同于名为 master的分支refs/heads/master, . 不要这样做!它不会伤害Git,但会让感到困惑。:-)


名称仅包含一个哈希 ID,但这意味着许多提交

值得在这里停下来看看提交在 Git 中是如何工作的。

正如我们上面提到的,每个提交都有自己唯一的哈希 ID。该哈希 ID 用于在“您在此存储库中拥有的所有提交和其他内部 Git 对象”的大型 Git 数据库中查找提交。找出存储库中是否存在哈希 ID,如果存在,其内容是什么,是一种快速的数据库查找操作。但是,提交中的实际内容非常重要。

除了保存所有文件的完整快照(间接地,通过对象),每个 Git 提交都会记录一些元数据:关于提交本身的一些信息,这不是文件快照的一部分。这包括提交的作者——姓名、电子邮件地址和日期和时间戳——关于提交者的相同信息通常与作者相同),当然还有作者和提交者记录的任何提交日志消息当他们做出承诺时。但它也包括一些哈希 ID,通常是一个。提交的父级是在此特定提交之前的提交。

换句话说,当你有一个类似的分支名称时master,它只包含一个提交哈希 ID。但是那个哈希 ID 会找到一个实际的提交。该提交有另一个哈希 ID:它之前的提交的哈希。因此,如果我们有一个分支名称包含master一些哈希 H:

        ... <-H   <-- master

我们可以H用来查找G

    ... <-G <-H   <-- master

找到G后,我们可以使用它来查找F

... <-F <-G <-H   <-- master

等等。所以像这样的名称master标识了一个提交,但是一个提交最终会导致它之前的每个提交,在一个链中一直追溯到第一个提交。

要将提交添加到存储库,Git:

  • 保存快照
  • 将您添加为作者和提交者,“现在”作为日期/时间
  • 放入您的日志消息
  • 当前提交作为提交的父级放入

hten 使用所有这些来制作提交对象。这个提交对象获得一个新的唯一哈希 ID(我们I简称它)并将其放入存储库数据库中:

...--F--G--H   <-- master
            \
             I

(I的父级是H) 然后,作为它的最后一步,将新提交的哈希 ID 写入name master

...--F--G--H
            \
             I   <-- master

或者:

...--F--G--H--I   <-- master

树枝就是这样生长的。

请注意,当这种情况发生时,所有可以通过在链的末尾开始到达的提交 - 在H- 和向后工作,仍然在链中。我们现在刚刚从新的一端开始I,然后向后工作。

现在,假设你的 Git 突然获得了一堆新的提交:

...--F--G--H--I   <-- master
               \
                J--K--L

你的 Git 可以“向前滑动名称”,在一种快进操作中(比如快进录音机,对于那些记得录音机是什么的人),对着内部的后向箭头:

...--F--G--H--I
               \
                J--K--L   <-- master

这种分支名称移动是一种快进操作。

但是,如果不是作为其父级,而是作为其父级J,会发生什么?然后我们开始:IJH

...--F--G--H--I   <-- master
            \
             J--K--L

如果我们移动master指向L,commit 会发生什么I

...--F--G--H--I
            \
             J--K--L   <-- master

答案是:提交I就消失了。我们再也找不到了!内部箭头总是向后,从子提交到其父。没有从 to 的向前箭头,只有从HtoI的向后I箭头H。因此,如果我们master从记住 的哈希更改为记住I的哈希L,我们将完全失去提交I。我们最终得到:

...--F--G--H--J--K--L   <-- master

提交I刚刚消失。

强制远程跟踪名称更新

至少在 Git 的编程中,丢失提交是一件坏事。因此,要让 Git 执行此操作,您必须强制更改名称。就是这样,这就是refspec--force的前导加号。+

在大多数方面,远程跟踪名称与分支名称完全相同。3 这包括 Git 不愿以非快进操作的方式修改它们。也就是说,假设你有:

             I   <-- master
            /
...--F--G--H   <-- origin/master

然后你跑了git fetch origin,那fetch带来了提交J, K, 并L以这种方式排列:

             I   <-- master
            /
...--F--G--H   <-- origin/master
            \
             J--K--L

fetch操作现在可以快进 origin/master,以便它从 移动HL

...--F--G--H--I   <-- master
            \
             J--K--L   <-- origin/master

但是如果新的JG作为它的父母,出于某种原因:

             I   <-- master
            /
...--F--G--H   <-- origin/master
         \
          J--K--L

那么 Git 不会origin/masterHto更新,L除非你强制更新。加号登录:

+refs/heads/*:refs/remotes/origin/*

告诉 Git 如果需要它应该强制这些更新。如果你忽略了加号,Git 将拒绝这样的尝试。


3关键区别在于你可以“启用”一个分支名称——<code>git status 会说on branch master——但你不能“启用”一个远程跟踪名称。git checkout origin/master相反,做会给你一个分离的 HEAD。因为能够“打开”它是分支的关键特性,这使得远程跟踪名称与分支名称不同。但在所有其他方面,它们基本相同。


我们终于可以解释你的输出了

你跑了:

git fetch remote_repo dev_tom:dev_tom

这里的remote_repo部分是一个远程名称,例如origin. 这里的dev_tom:dev_tom部分是refspec。此 refspec 部分覆盖并部分增加Git 在运行时将找到的 refspec:

git config --get-all remote.remote_repo.fetch

大概是这样写的:

+refs/heads/*:refs/remotes/remote_repo/*

如果您使用过--single-branch或类似的,它可能还有其他内容:可能是多行输出。您也可以完成git config --edit或弄乱设置,但这可能是它的设置方式。

您的 Git 现在从git config --get remote.remote_repo.url. Git 提供了一堆参考资料,包括refs/heads/dev_tom. 引用有一个哈希 ID。4 您的 Git 使用该哈希 ID 来确定您是否需要从他们的 Git 获取新提交:您是否拥有该哈希 ID 指定的提交?如果没有,您需要获取它,以及完成整个提交链所需的任何其他对象,这些提交会导致您拥有的提交。

在这种情况下,他们dev_tom命名了一些不是您自己的提示的后代的提交dev_tom。我不可能确切地知道这些提交链是什么样的,但一种可能性是:

...--F--G--H--I   <-- dev_tom
            \
             J--K--L   <-- [their dev_tom]

(其他可能性包括它们dev_tom指向H、 或G、 或F等)

如果他们有你没有的提交,你的 Git 会将这些提交带入 - <code>J,KL上面的示例中。然后你的 Git 做了两件事,我们可以反过来介绍:

  1. 你的 Git 遵守了fetchrefspec:

    +refs/heads/*:refs/remotes/remote_repo/*
    

    这个 refspec 说他们的分支应该被重命名为你的远程跟踪名称。所以他们dev_tom变成了你的remote_repo/dev_tom

     * [new branch]      dev_tom -> remote_repo/dev_tom
    

    您的remote_repo/dev_tom远程跟踪名称是您的存储库中的新名称,因此您的 Git 假定它们dev_tom之前一定不存在于其存储库中。这使得他们Git中有了dev_tom一个新的分支。是否有一个无关紧要:你的 Git 只考虑你没有参考的事实。(现在你做到了。)dev_tomrefs/remotes/remote_dev/dev_tom

  2. 您的命令行 refspec 是dev_tom:dev_tom. 这表示 * 使用它们dev_tom来覆盖我的dev_tom- 或者在这种情况下,使用它们refs/heads/dev_tom来覆盖你的refs/heads/dev_tom. 5

     ! [rejected]        dev_tom -> dev_tom  (non-fast-forward)
    

    使用他们的哈希 IDdev_tom覆盖您的 dev_tom分支名称会导致您的 Git 丢失一些提交,例如 commit I。(确切的提交取决于图表,您没有显示,我们只能猜测。)

    您没有使用--force,也没有使用加号 ( +dev_tom:dev_tom),因此您的 Git 拒绝使用此哈希 ID 覆盖您自己的分支名称。换句话说,您的 Git 拒绝了非快进更新。

请注意,还有两个可能的git fetch输出,如下所示(假设 a git fetch origin):

From <url>
   aaaaaaa..bbbbbbb  name1   -> origin/name1
 + ccccccc...ddddddd name2   -> origin/name2  (forced update)

空格和两个点表示您以前有origin/name1过。它之前的哈希 ID 是,缩写为aaaaaaa. 现在是现在bbbbbbb,这个更新是一个快进。

加号、三点和(forced update)注释表示您以前有origin/name2过。它之前的哈希 ID 是ccccccc(缩写),现在是ddddddd; 此更新是非快进的,但由于强制标志,无论如何都完成了。

四个单字母代码是:空格(现有远程跟踪名称和快进更新)、*(新远程跟踪名称)、+(现有远程跟踪名称和强制非快进更新)和!(现有远程- 跟踪 anme 并错误更新它;确切的错误通常是non-fast-forward,但其他错误是可能的)。


4要查看这些内容,请运行:

git ls-remote remote_repo

这就像git fetch它调用了另一个 Git。但是,与 不同git fetch的是,它不需要 refspecs 并且不会带来提交。它只是调用该 Git,然后将其他 Git 提供的信息转储到其输出(您的终端窗口)。

5这些是不合格的参考名称:您的 Git 会根据您及其参考名称空间中的各种内容来猜测是使用分支名称还是标签名称等。在这种情况下,您的 Git 猜测这些都将被视为分支名称。


推荐阅读