1

我正在做一个提取/合并,想知道做之间是否有任何区别

git fetch

git fetch origin master

remote repository我在 GitHub 上没有任何其他分支和原点。

当我做:

git fetch origin master
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From github.com:XXXXXXXXXXXXXXX
 * branch            master     -> FETCH_HEAD

只是:

git fetch
From github.com:XXXXXXXXXXXXXXX
   531d466..aaf6df0  master     -> origin/master

注意 master 指向不同的东西;在一种情况下FETCH_HEAD,在另一种情况下,origin/master?它们不同吗?

4

1 回答 1

8

这是“TL;DR”版本(掩盖了许多特殊情况):git fetch 总是更新FETCH_HEAD,在各种情况下不止一行。它有时会更新“远程分支”,即全名以refs/remotes/. 其余的主要是关于“有时”,这取决于给定的参数数量git fetch和 git 版本。


我有机会对此进行测试。让我们区分三种情况,所有这些都假设在git fetch没有额外选项的情况下运行,-a甚至--all. 让我们也排除更奇怪的变体git fetch,比如直接使用 URL,或者insteadOf条目,或者列在.git/remotesor中的文件.git/branches。(我承认我只是在猜测,但我认为这些是[remote "name"]条目进入 git 配置文件前几天的遗留物。编辑,2019:事实证明这是正确的。)

  1. git fetch,并且没有其他论点。

    Git 确定您当前的分支(以通常的方式,通过阅读,但您当然可以通过orHEAD看到它是什么)。然后,它会为该分支查找一个配置条目,并将其命名为. 例如,假设您在分支上并且具有(在其他条目中):git branchgit statusremotedummy.git/config

    [branch "dummy"]
        remote = remote-X
    

    在这种情况下git fetch相当于git fetch remote-X。之后,这相当于案例 2,即:

  2. git fetch remote(除此之外没有更多的争论)。

    Git 这次不查看您当前的分支。要使用的遥控器是命令行中给出的遥控器。它确实为给定的遥控器寻找配置部分。假设您正在使用remote-X: 在这种情况下,它会查找:

    [remote "remote-X"]
        url = ...
    

    如果该部分不存在,或者没有url =条目,则会出现错误:fatal: 'remote-X' does not appear to be a git repository1 否则会给出 URL,并git fetch会尝试连接到那里。假设它可以连接...

    通常还有至少一个配置条目,可能更多,阅读:

        fetch = +refs/heads/*:refs/remotes/remote-X/*
    

    (遥控器的名称在这里是硬编码的)。假设有...

    接下来,git fetch询问远程它有哪些 refs(主要是分支和标签,虽然你可以获得所有 refs,但大多数人只关心分支和标签)。你可以自己做同样的事情git ls-remote remote-X,它会溢出这样的东西:

    676699a0e0cdfd97521f3524c763222f1c30a094        HEAD
    222c4dd303570d096f0346c3cd1dff6ea2c84f83        refs/heads/branch
    676699a0e0cdfd97521f3524c763222f1c30a094        refs/heads/master
    

    HEADref 的处理并不完全一致(我已经看到它的行为很奇怪),但通常在这里它只是被丢弃了。2 其余分支根据fetch =refspec 进行重命名和更新。(如果有多个fetch =refspecs,它们会根据它们全部重命名和更新。例如,这主要用于在 下引入refs/notes/或创建自己的“远程标签”命名空间refs/rtags/。)

    branch在这种情况下, fetch 将带来两个分支和所需的任何对象master,并根据需要更新(本地)“远程分支”名称refs/remotes/remote-X/branchrefs/remotes/remote-X/master。对于每个更新fetch的,打印如下一行:

       22b38d1..676699a  master     -> remote-X/master
    

    如果fetch =缺少线条,您会得到完全不同的东西。输出将显示:

     * branch            HEAD       -> FETCH_HEAD
    

    在这种情况下,就好像(缺失的)fetch =行在那里并且被包含fetch = HEAD

  3. git fetch remote refspec(该refspec部分实际上是一个或多个参考规范,如下所述)。

    这类似于案例 2,只是这一次,“refspecs”是在命令行上提供的,而不是来自fetch =远程的配置条目。但是,这里的 fetch 行为完全不同。


在这种特殊情况下,让我们暂停一下并正确描述 refspec。(Refspecs 也会出现,git push但是,与 git 一样,实现细节会泄漏出来,它们在那里的工作方式略有不同。) refspec 有一个可选的前导加号(+),我将在这里忽略它;3然后是两部分,用冒号 ( :) 分隔。两者通常都只是一个分支名称,但在分支名称的情况下,您可以(并且fetch =行可以)拼出“完整”引用名称。refs/heads/branch

对于获取操作,左侧的名称是遥控器本身的名称(如示例所示git ls-remote)。右侧的名称是要在本地 git 存储库中存储/更新的名称。作为一种特殊情况,您可以*在斜线后面加上一个星号 ( ) 作为最后一个组件,例如refs/heads/*,在这种情况下,左侧匹配的部分将在右侧替换。因此refs/heads/*:refs/remotes/remote-X/*,是什么原因refs/heads/master(如在远程看到的,带有git ls-remote)变成(如在本地存储库中看到的,并且以较短的形式,在行打印refs/remotes/remote-X/master的右侧)。->git fetch

但是,如果您不放入:git fetch则没有好地方放“那边的分支”的副本。假设它将带来遥控器refs/heads/master(遥控器上的master分支)。而不是更新 refs/heads/master的——如果你在分支中有自己的提交显然会很糟糕master——它只是将更新转储到FETCH_HEAD.

这就是事情变得特别怪异的地方。假设您运行git fetch remote-X master branch,即至少给出一个,也许是几个,refspecs,但都没有冒号。

  • 如果您的 git 版本早于 1.8.4,则更新只会进入FETCH_HEAD. 如果你给了两个无冒号的 refspec,FETCH_HEAD现在包含行:

    676699a0e0cdfd97521f3524c763222f1c30a094        branch 'master' of ...
    222c4dd303570d096f0346c3cd1dff6ea2c84f83        branch 'branch' of ...
    
  • 如果您的 git 版本是 1.8.4 或更高版本,则更新将在那里进行——这部分没有改变——而且 fetch 借此机会将这些分支永久记录在其适当的远程分支中,如远程行所给出的那样fetch =

    但是,无论出于何种原因,git fetch只为实际更新的远程分支打印一条更新->行。由于它总是在 中记录所有更新FETCH_HEAD,所以它总是在此处打印分支名称。

    (另一个问题,除了需要 git 1.8.4 或更高版本之外,更新远程分支是这些fetch =行必须存在。如果它们不存在,则 fetch 没有映射知道要重命名refs/heads/*refs/remotes/remote-X/*.)

换句话说,git 1.8.4 和更新版本确实“机会性地更新”了所有远程分支。旧版本的 git do it on git push,所以之前一直不一致。即使在 git 1.8.4 中,它仍然与 不一致git pull,我认为(尽管我没有git pull足够注意:-));这应该在 git 1.9 中修复。

现在让我们回到和之间的区别。git fetch remotegit fetch remote refspec ...


  • 如果您运行,即省略所有 refspecs,则 fetch 会像往常一样回到行。fetch 操作从行中获取所有 refs 。 所有这些都进入,但这次它们被标记为“不可合并”(带有标签,我将其更改为一个空格以更好地适应网页):git fetch remotefetch =fetchFETCH_HEAD

    676699a0e0cdfd97521f3524c763222f1c30a094 not-for-merge branch ...
    

    不是分支的 Refs,例如,refs/notes/被带来的 refs,改为阅读:

    f07cf14302eab6ca614612591e55f7340708a61b not-for-merge 'refs/notes/commits' ...
    

    同时,如有必要,远程分支 refs 会更新,并显示消息告诉您哪些已更新:

       22b38d1..676699a  master     -> remote-X/master
    

    同样,所有内容都被转储到FETCH_HEAD中,但只有“需要更新”的 refs 被更新和打印。新分支打印“新分支”,旧分支打印其缩写的新旧 SHA-1,master -> remote-X/master如上所述。

  • 另一方面,如果您运行,则 fetch带来指定的 refspecs。这些像往常一样进入, 6但这次每一个都被打印出来。然后,如果您的 git 是 1.8.4 或更高版本,任何可以映射(通过合理的行)和需要更新的参考更新也会更新和打印:git fetch remote refspec ...FETCH_HEADfetch =

     * branch            master     -> FETCH_HEAD
     * branch            branch     -> FETCH_HEAD
       22b38d1..676699a  master     -> remote-X/master
    

    如果您的 git 版本早于 1.8.4,则不会remote-X/master在这种情况下更新前面的标志。refs/heads/master:refs/remotes/remote-X/masterrefs/heads/*:refs/remotes/remote-X/*


1这不是一个很好的错误消息。这个remote-X论点不应该是一个“存储库”,它应该是一个“远程”!如果 git 在这里说更多信息可能会很好。

2 git 远程协议存在缺陷:HEAD 通常是间接引用,因为它是远程上的当前分支,因此它应该以“ref: refs/heads/master”的形式出现,但它却作为完全解析 SHA-1。至少有一个 git 命令 ( git clone) 尝试通过将此 SHA-1 与每个分支头的 SHA-1 进行比较来“猜测”远程上的当前分支。例如,在上面,很明显远程是“在分支主机上”,HEAD并且refs/heads/master具有相同的 SHA-1。但是,如果多个分支名称指向同一个提交,并且HEAD与该提交 ID 匹配,则无法判断哪个分支(如果有)HEAD处于打开状态。遥控器也可能处于“分离 HEAD”状态,在这种情况下它'

2019 年编辑:此错误已在 Git 版本 1.8.4.3 中修复。只要两个 Git 版本(在您要克隆的机器上和您自己的机器上)都是 1.8.4.3 或更高版本,Git 就不再需要猜测。

3加号的意思是“接受强制更新”,即,接受将被分支的“只是快进” 4规则拒绝的更新,或标签的“从不更改标签” 5规则。

4当提交有向无环图中的旧 SHA-1 是新 SHA-1 的祖先时,可以对标签进行“快进”,将其从旧 SHA-1 更改为新的。

5 “从不更改标签”规则是 git 1.8.2 中的新规则。如果您的 git 比这更旧,git 也使用标签的分支规则,允许快速转发而无需“强制更新”。

6但没有not-for-merge这个时间。基本上,当您提供无冒号的 refspecs 时,git fetch假定它们是“用于合并”并将它们放入FETCH_HEAD以便git merge FETCH_HEAD可以找到它们。(我没有测试过非分支引用会发生什么。)

于 2014-02-04T05:25:26.697 回答