[英]Git fetch and git pull relationship
查找git pull
和git fetch
之间的区别,许多消息来源说git pull
是 fetch 的超集,即git pull
是 fetch + merge。
但是,我似乎记得很多次git pull
告诉我一切都是最新的,但是 fetch 产生了新信息。
有人可以解释理论与现实之间的这种差异吗?
拉确实是提取加合并。
除非它不是。
不是什么时候? 当它是 fetch 加 rebase 时,或者 - 很少 - fetch 加 checkout 时。 但在所有三种情况下,它仍然是:
git fetch
,然后是 这变得复杂的地方并不在于第二个命令——尽管第二个命令确实使事情复杂化——而是来自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
。)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 fetch
和git rebase
和git 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
运行的第二个命令是:
git merge
,默认情况下,或git rebase
,如果你告诉 Git 这样做,或者git checkout
,在一种特殊情况下。 同样, git pull
将选项和参数传递给第二个命令,这里事情变得一团糟。 当git pull
运行git merge
时,它通过:
-m
选项(除非您提供自己的-m
); 加 最后一个是一个谜:“选定”的真正含义是什么? 好吧,让我们回到git pull
语法:
git pull
git pull origin
git pull origin main
我们知道,如果提供这些词( origin
和main
),将传递给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
命令合并。 这会更改可以通过的选项集:
-m
,所以你不能给一个;--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 -b
或git 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.