0

这个问题比典型的 SO 问题更“理论”,所以我觉得有一定的理由是有道理的。我遇到的困难之一git是我发现自己不断地做一些在我看来“应该起作用”但实际上却没有的事情。即使在git过去 10 个月左右的时间里几乎每天都使用它,并且比我用于工作的任何其他软件工具更深入地研究它,这仍然是正确的。这意味着,在内心深处,git对我来说根本没有意义。因此,这个问题的目的只是为了理解 git,并希望减少我认为“应该工作”与实际工作之间的不匹配。(顺便说一句,“阅读文档”不是解决方案;

这个问题是我之前提出的问题的后续问题:如何将 git pull <REMOTE> <BRANCH> 分解为 fetch + merge?. 特别是,我建议运行该问题中给出的示例脚本,并检查其输出。

git pull命令应该是 a和git fetcha的组合git merge

因此,我对以下事实感到困惑,如果<BRANCHNAME>是当前分支,则运行

git pull --no-edit <REMOTENAME> <BRANCHNAME>

更新跟踪分支<REMOTENAME>/<BRANCHNAME>1,而

git fetch <REMOTENAME> <BRANCHNAME>

保持<REMOTENAME>/<BRANCHNAME>不变,因此尝试将其合并<BRANCHNAME>通常是无操作的。

有什么办法可以理解这种明显的不一致吗?或者,为什么不git fetch <REMOTENAME> <BRANCHNAME>直接失败,或者至少给出警告?它是一个值得保留的常见用例吗?

简而言之,这种明显的不一致是界面设计的buggit,还是一个特性,如果是后者,它又是怎样一个特性呢?

编辑:澄清一下:这个问题与上述行为与其文档的一致性问题无关/不关心。这个问题试图确定的是git pullgit fetch在解释他们的论点时所遵循的约定的差异是(设计)错误还是(设计)特性。

1<BRANCHNAME>另外,它会在和 this updated之间执行任何必要的合并<REMOTENAME>/<BRANCHNAME>,或者至少它会开始此合并。

4

2 回答 2

1

这是我的看法。

参考规范

首先,让我们揭穿所谓的“refspec”是什么,因为将来会需要它。

refspec 代表“参考规范”。引用是指向提交或另一个引用的东西。一个分支是一个引用,一个标签是一个引用;HEAD是一个特殊的(所谓的“符号”)引用。让我们暂时忽略各种引用之间的区别。

通常将引用简称为“refs”。

refspec 具有以下形式:

[+]source[:destination]
  • source指定要从中获取一系列提交的引用。
  • destination,如果指定,则指定对更新的引用,其中包含来自 的一系列提交source
  • 加号(如果包含)会强制更新发生,即使 指向的提交行destination完全包含在 指向的提交行中source

refspecs 用于获取和推送,在这些情况下源和目标 refs 的含义是相反的,显然:当我们获取时,源 refs 在远程 repo 中,而当我们推送时,源 refs 在我们的本地回购。

对于普通的 Git(我的意思是它的参考实现),refs 只是名称相对于其存储库根目录的文件(对于普通存储库,这是“.git”目录,对于裸存储库,这是存储库根目录)。

一些引用,例如HEAD(or ORIG_HEADor MERGE_HEADor FETCH_HEAD) 位于存储库的根目录中,而其他一些引用,例如分支和标签以及远程分支位于名为“refs”的目录中,它们在各自的子目录下进一步排序:

  • 分支位于“refs/heads”子目录中。
  • 标签位于“refs/tags”中。
  • 远程分支位于其相应远程的“refs/remotes/<remote>”目录中。
  • 注释在“参考/注释”中。
  • ...

因此,代表名为“master”的分支的 ref 的全名确实是“refs/heads/master”,从命名的远程存储库“origin”获取的名为“foo”的远程分支是“refs/remotes/origin/foo” ”。

Git 使用智能查找机制允许您在大多数情况下缩写 ref 名称,但是当您想要严格时存在任何歧义时,可能会使用完整(或“更完整”)名称。血淋淋的细节在手册git rev-parse

(需要这些关于完整 ref 名称的解释来理解下面解释的简单调用是如何git fetch工作的。)

抓取

现在回到获取。

git fetch基本上有几种操作模式,具体取决于您提供给它的参数:

  • git fetch <git_url>在通过 访问的远程存储库中获取refHEAD指向的任何内容<git_url>,获取其历史记录并将其提示提交的 SHA-1 名称写入.git/FETCH_HEAD文件。

    在裸存储库HEAD中,通常指向名为“master”的分支(尽管可能会更改)。在非裸(正常)存储库HEAD中,显然指向当前签出到工作树的内容。

  • git fetch <git_url> <refspec> ...使用指定的 refspecs 从远程存储库中获取(而不是HEAD),并且 - 对于那些包含“目标”部分的 refspecs,它还尝试使用获取的历史记录更新那些本地 refs。每个提取的 refspec 的 SHA-1 名称都会写入.git/FETCH_HEAD文件。

    展示:

    git fetch git://server/repo.git master devel test
    From git://server/repo.git
     * branch            master     -> FETCH_HEAD
     * branch            devel      -> FETCH_HEAD
     * branch            test       -> FETCH_HEAD
    

    请注意,在获取提交时,没有更新本地引用。

    现在让我们以更复杂的方式来做:

    git fetch git://server/repo.git master devel:foo test:bar
    From git://server/repo.git
     * branch            master     -> FETCH_HEAD
     * [new branch]      devel      -> foo
     * [new branch]      test       -> bar
    

    如您所见,git fetch创建了两个本地分支,“foo”和“bar”,而“master”只是被获取但没有用于在我们的本地 repo 中创建任何东西。所有三个远程引用的 SHA-1 名称仍然在.git/FETCH_HEAD文件中。

  • git fetch <remote> <refspec> ...-作为使用配置或自动创建<remote>的命名远程存储库- 行为与具有显式的表单完全相同,但从该命名远程的配置中获取要使用的 url。git remote addgit clone<git_url>git fetch

  • git fetch <remote>从该远程获取所有分支并尝试更新(或创建,如果它们不存在)该远程的所谓远程分支

    这对于理解这个操作过程不是魔术至关重要。当您添加一个命名远程(或git clone为您创建一个)时,Git 会在您的存储库配置中添加几个描述该远程的变量,其中一个将remote.<name>.fetch是一个 refspec 参数,如果没有直接传递 refspec 则使用该参数git fetch( !)。

    现在,对于名为“origin”的远程,Git 将创建参数remote.origin.fetch设置为+refs/heads/*:refs/remotes/origin/*. 你可以去你当地的仓库自己看看:

    $ git config --get remote.origin.fetch
    +refs/heads/*:refs/remotes/origin/*
    
  • “最神奇”很简单git fetch——它就像遥控器上面的呼叫被自动接听一样。

拉取(获取加合并)

将其拉出定义为“提取然后将提取的内容合并到当前签出的分支”。传递给git pull(或缺少)的 refspec 直接传递给git fetch,因此上述所有规则都适用。

为什么git pull <remote> <branch>`git fetch 不更新匹配的远程分支?

要理解这个“奥秘”,请记住 Git 在远程分支及其远程跟踪分支的背后使用了某些方便的魔法(当 yogit fetch origin后面跟着时git branch foo origin/foo,“origin/foo”是一个远程分支和“foo”是一个远程跟踪分支,因为它跟踪相应的远程分支)。您应该记住的另一点是远程分支用于在上次获取时捕获其远程存储库中相应分支的状态。

现在假设您将远程跟踪分支推送到远程存储库中的同名分支(最常见的情况)。假设你这样做了:

$ git fetch origin
(this created the "origin/foo" branch)
$ git chekcout -b foo origin/foo
(this created a new local branch "foo" tracking "origin/foo")
$ git commit ...
$ git push origin foo

在推送时,Git 注意到如果您同时获取匹配的远程分支您将在其中获得与刚刚推送的相同的提交。所以它继续并使“origin/foo”指向同一个提交“foo”指向。

现在回到git pull...在您的情况下,git pull完成后,Git 注意到您刚刚使用从您在本地拥有远程分支的分支获得的提交更新了远程跟踪分支,因此它继续并更新此远程分支因为这是它最后一次看到的状态。

让我们回顾一下:

  1. “origin/foo”是一个远程分支,它捕获远程存储库“origin”中名为“foo”的分支的状态。
  2. “foo”是一个跟踪“origin/foo”的本地分支。
  3. 你做

    git checkout foo
    git pull origin foo
    

    Git 会更新你的 "foo" 分支,但这意味着 "foo" 现在至少有一个提交 "origin/foo" 会收到,如果你只是git fetch originor的话git push origin foo,所以 Git 继续并更新 "origin/foo" 。

也就是说,在我看来,您的情况下远程分支的更新不是因为git fetch ...已运行,而是因为本地远程跟踪分支因合并而更新。现在有意义吗?

进一步阅读

我建议您只阅读存储库中的“.git/config”文件,以了解如何配置所有这些“远程内容”。

于 2013-03-19T19:38:51.753 回答
0

git-fetch 手册页说

获取的 ref 的 ref 名称及其对象名称存储在 .git/FETCH_HEAD 中。此信息留给 git merge 稍后完成的合并操作。

运行git fetch origin master会将获取的结果存储在FETCH_HEADref 中。您可以将其合并到您当前的分支中git merge FETCH_HEAD

手册页还说

参数的格式<refspec>是可选的加号 +,后跟源 ref <src>,后跟冒号:,然后是目标 ref <dst>

获取匹配的远程引用,<src>如果<dst>不是空字符串,则使用 快速转发匹配它的本地引用<src>

你可以告诉 git fetch 用git fetch origin master:refs/remotes/origin/master.

于 2013-03-19T16:16:18.093 回答