繁体   English   中英

git fetch 和 git pull 关系

[英]Git fetch and git pull relationship

查找git pullgit fetch之间的区别,许多消息来源说git pull是 fetch 的超集,即git pull是 fetch + merge。

但是,我似乎记得很多次git pull告诉我一切都是最新的,但是 fetch 产生了新信息。

有人可以解释理论与现实之间的这种差异吗?

拉确实是提取加合并。

除非它不是。

不是什么时候? 当它是 fetch 加 rebase 时,或者 - 很少 - fetch 加 checkout 时。 但在所有三种情况下,它仍然是:

  1. git fetch ,然后是
  2. 第二个 Git 命令对获取的提交做一些事情。

这变得复杂的地方并不在于第二个命令——尽管第二个命令确实使事情复杂化——而是来自git pull传递的参数 由于git pull正在运行另外两个 Git 命令,并且 Git 命令的操作取决于它们的选项和参数,因此git pull传递git fetch和第二个命令选项和参数很重要,无论它可能是什么。

旁白:回顾历史

在 Git 的早期,没有像origin这样的“远程”,这意味着也没有“远程跟踪名称”。 你会运行:

git fetch git://name-of-linus-torvalds-machine/repos/foo.git

从 Linus 获取东西,然后运行git merge FETCH_HEAD或类似的东西。 这很容易出错(很容易在 URL 中出现拼写错误)并且很烦人,因此 Git 获得了一堆临时方法来处理这个问题。

请注意,在没有遥控器的情况下,所有git fetch可以做的就是.git/FETCH_HEAD中留下一堆信息,以便您可以确定 Linus 的 repos 中的哪些分支已经更新等等。 当然, git pull将这两个命令合二为一,这样您就不必运行两个单独的命令,而且大多数人都使用git pull 但显然缺少了一些东西。 于是发明了遥控器

  • 我们现在有了一个简短的简单名称,例如我们可以使用的origin来代替 URL。 (这消除了对文档中仍然列出的所有用于命名遥控器的奇怪黑客的需要,但它们都仍然存在。 Named file in $GIT_DIR 。)
  • 我们现在有一种方法让 Git保存与 Linus 最新版本相关的哈希 ID,这样我们就不需要在本地创建大量分支。 远程跟踪名称( origin/master等)接管过去需要使用本地分支名称的工作。

但是所有这些东西仍然受到支持,其中一些仍然在一些(古代)文档中被描述为“做事的方式”,所以你仍然可以使用旧的粗略方法。 也许有些人会。

无论如何,现在存在远程跟踪名称。 但是,在 Git 1.7 和 Git 2.0 之间,对它们进行了一些更新。 具体来说,Git 1.8.4 修复了一些最终被宣布为错误的问题。 出于某种奇怪的原因,有些人仍在使用 Git 1.7.x,所以请注意您可能会碰到他们。

在 Git 2.11 中,旧的git pull shell 脚本正式退役。 虽然git pull仍然有效地运行git fetch后跟第二个 Git 命令,但您不能再指向 shell 脚本并说:“看,在这一行,它运行git fetch 。然后它进行了这些测试,然后最终运行这个其他命令......” 结果是它在 Windows 上运行得更快,而且更难解释。 😀 从那以后它也获得了一两个功能,足以让至少像我这样的一些铁杆“反拉”人现在愿意实际使用这个东西。 但那是另一回事了。

你如何运行git pull

git pull命令有很多选项。 请参阅其文档以获取完整列表,然后将这些选项与git fetchgit rebasegit merge的选项进行比较。 请注意,拉取文档说某些选项被传递给一个或另一个或两者,并且在某些选项中有相当多的重叠(例如,所有选项都采用-q表示quiet-v表示verbose )。

但是,无论有没有这些选项,您都可以运行:

git pull

或者:

git pull origin

或者:

git pull origin main

例如。 如果并且当您运行其中任何一个时,所有这些位置参数都会传递给git fetch

请注意,您甚至可以运行:

git pull origin main feature

但你几乎肯定不应该 我们将在后面介绍为什么会这样。

如果您提供选项,它们将按所述传递给 fetch 和 second-command 步骤中的一个或两个。

fetch命令总是传递一个额外的选项,即--update-head-ok 拉取需要传递这个选项,但也需要小心,因为不小心使用它会使你当前的分支、索引和工作树不同步。 除非您确切知道自己在做什么,否则不要自己使用此选项。

出于(至少,也许只是)历史原因,当传递一些 refspec 参数时,例如git fetch origin main main中的 main , git fetch只会更新指定的 refspecs 和关联的远程跟踪名称 由于git pull将您提供的所有 refspec 参数传递给git fetch ,但没有它自己的额外参数,当且仅当您在此处将 refspec 参数传递给git pull时, git fetch才会获得一个 refspec 参数。

(Fetch refspecs 与 push refspecs 稍有不同: git push origin main等价于git push origin main:main ,但git fetch origin main等价于git fetch origin main:<discard>的副作用是也会更新origin/main . 如果你愿意,你可以运行git fetch origin main:main ,但这要求你不在那个分支上,除了git pull安排的--update-head-ok特殊情况。)

添加第二条命令

git pull运行的第二个命令是:

  1. git merge ,默认情况下,或
  2. git rebase ,如果你告诉 Git 这样做,或者
  3. git checkout ,在一种特殊情况下。

同样, git pull将选项和参数传递第二个命令,这里事情变得一团糟。 git pull运行git merge时,它​​通过:

  • 文档描述为传递的合并选项;
  • 带有预先计算的合并消息的-m选项(除非您提供自己的-m );
  • 提交的提交哈希 ID,它是远程分支名称的分支提示,如所选。

最后一个是一个谜:“选定”的真正含义是什么? 好吧,让我们回到git pull语法:

git pull
git pull origin
git pull origin main

我们知道,如果提供这些词( originmain ),将传递给git fetch 他们指定遥控器,如果有第二个单词,则指定在该遥控器上看到的分支名称,用于git fetch操作。

如果我们提供远程分支上的名称, git pull要求当前分支——我们所在on分支,如git status中所说on branch main或其他——有一个上游集。 (另请参阅为什么我需要一直执行 `--set-upstream`? )上游在技术上是一对:既是远程又是远程分支名称。 这些通常以更可口的远程跟踪名称格式呈现给您,因此您的main的上游通常是您的origin/main ,即在origin上看到的main

如果需要,您的git pull命令将从上游提取分支名称。 它不会将其传递给git fetch ,但稍后会在第二个git merge命令中使用它。 此时git pull将使用.git/FETCH_HEAD —— git fetch仍然会写入,就像它在 Git 1.5 更广泛发布之前在原始 Git 中所做的那样——来找出与main over on origin关联的提交哈希 ID。 这是git pull传递给git merge的哈希 ID。

换句话说,如果你在你的main并且它的上游是origin/main并且你运行:

git pull

你的 Git 将运行:

git fetch --update-head-ok

其次,如果使用git merge

git merge -m "merge branch 'main' of <url>" <hash-ID>

其中 URL 和 hash-ID 来自origin.git/FETCH_HEAD

如果您自己运行:

git fetch
git merge

您将获得相同的效果,除了您没有-m选项并且合并消息将是默认值,即merge branch 'origin/main' 也就是说,URL 消失并且branch main of ...措辞不同。

但是如果你运行:

git pull origin main

您的git pull命令将运行:

git fetch --update-head-ok origin main
git merge -m <same message as before> <same hash ID as before>

也就是说,额外的origin main被传递给git fetch ,这限制了获取的内容

我们现在还可以看到为什么我们不应该运行:

git pull origin main feature

这将运行:

git fetch --update-head-ok origin main feature

(这本身很好),但随后它将运行:

git merge -m <message> <hash#1> <hash#2>

也就是说,您的git pull将从.git/FETCH_HEAD中提取出两个哈希 ID:一个对应于origin上的main ,一个对应于origin上的feature 然后它将两个哈希 ID传递给一个git merge命令 这个git merge命令将执行 Git 所说的octopus merge 1

(那些刚接触 Git 的人似乎经常期望:

git pull origin br1 br2

应该在本地检查br1 ,获取并合并origin/br1 ,然后在本地检查br2 ,然后获取并合并origin/br2 ,这可能比这个有点笨拙的顺序描述更有效。 可能是有道理的,我相信我自己也曾想过这一点,但事实并非如此。)

如果你告诉 Git 使用git rebase而不是git merge ——你现在可以通过多种方式做到这一点,例如将pull.rebase设置为true ,除了提供--rebase作为git pull的选项——Git 将替换git merge命令与git rebase命令合并。 这会更改可以通过的选项集:

  • rebase 不接受-m ,所以你不能给一个;
  • rebase 不接受--ff-only--no-ff ,所以你不能给这些。

git rebase命令有一个名为autostash的模式,如果您的状态不是“干净”(如在git status中不会说working tree clean, nothing to commit ), git rebase将在启动 rebase 之前运行git stash push ,并且最后git stash pop 一般来说,我不是git stash的粉丝,除非你非常擅长处理冲突,否则我建议不要使用此功能。

如果 autostash 被禁用(这是默认设置),如果状态不是“clean”,rebase 将拒绝启动。 使用git merge作为第二个命令,合并通常会拒绝在相同的情况下开始(尽管我记得古代 Git 版本的行为不同,在某些冲突情况下与git stash pop具有相同的混乱副作用)。

最后一种情况是很少见的。 您可以拥有一个处于特殊状态的 Git 存储库,Git 对此使用两个不同的术语:未出生分支孤立分支 这种状态的存在部分是因为一个新的、完全空的存储库根本没有提交。

Git 中的分支名称必须包含某个有效的现有提交的哈希 ID。 但是当你运行git init并创建一个新的、完全的存储库时,没有提交。 没有提交,就没有分支。 然而, git status会说你在某个分支上,并且还没有提交,你应该做第一个。

在这种状态下——这个孤儿/未出生的分支状态——你做出的下一个提交将是一个根提交,它在一个新的空存储库中是你通常想要的:这是有史以来的第一次提交,它开始存在历史。 现在你有一个提交,你可以在它的基础上进行构建。

但是,当您未出生分支状态下运行git pull时, git pull操作可能会从远程(例如来自origin )获得一堆提交。 第二个命令应该按照剩余的git pull参数的指示将git pull获得的那些新提交与当前分支上的提交结合起来。 当前分支上没有提交(不存在),但零加上某事是某事,对吧? 所以git pull声明这个 pull-into-empty-repository 的结果是你应该检查git pull -ed 分支顶端的提交。 那是:

git init
git remote add origin <url>
git pull origin main

应该让你的 Git 访问给定的 URL,找到他们的main ,从他们的 Git 获取提交,创建你的origin/main ,然后创建你自己的main ,它与你的 Git 刚刚创建的origin/main完全匹配他们的main

最后一步的事情是创建分支git checkout -bgit switch -c ,这就是git pull将在这里做的事情。 一个错误,在 Git 1.5 或 1.6 左右,如果你的工作树是非空的,这个git pull命令会完全清除它。这个错误至少咬了我一次,至少是部分原因我学会了避免git pull 。这个 bug 已经修复很久了,但我通常喜欢 fetch、 inspect和 merge-or-rebase,我需要在 fetch 和第二个之间运行git log来进行检查——或者相反,第三个命令。所以我仍然充其量仅少量使用git pull 。但它现在pull.ff only作为配置项,这涵盖了我最常见的情况,所以我正在慢慢适应它。)


1有关章鱼合并的更多信息,请参阅git merge文档 请注意,如果两个哈希 ID 相同,则此章鱼合并的效果与常规合并的效果基本相同,只是章鱼合并无法处理冲突。 至少,现在还没有: Junio Hamano 正在思考新的merge-ort是否能够解决这个问题。

我不清楚这是一个好主意。 事实上,我有点清楚章鱼合并较弱,并且无法处理合并冲突,是一件好事。


关于什么 ...

但是,我似乎记得很多次 git pull 告诉我一切都是最新的,但是 fetch 产生了新信息。

如果您运行git pull origin main并获取最新消息,则您当前的分支已合并origin/main ,此处无事可做。 但是,如果您随后运行git fetch origin (或只是git fetch ),您将获取他们所有的分支名称,并更新您所有的远程跟踪名称。

如果当前分支的上游origin/main ,则可以运行:

git pull

代替:

git pull origin main

并且git pull运行的git fetch将不仅限于获取它们的main

但是,我似乎记得很多次 git pull 告诉我一切都是最新的,但是 fetch 产生了新信息。

这仅仅是因为 Git 如何报告发生的事情。

git fetch更新所有远程跟踪分支并对此进行报告,因此远程站点上的任何新提交都会产生某种输出。

但是git pull ,虽然它也执行git fetch ,但只报告当前本地分支上发生的事情,即使 fetch 确实将大量提交带入远程跟踪分支,这也可能什么都没有。 (另一个不使用pull的好理由!)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM