【问题标题】:Are git pull and push repository wide operations or branch specific?git pull 和 push 存储库范围的操作还是特定于分支的?
【发布时间】:2014-04-17 06:55:48
【问题描述】:

试图真正掌握 git :) git pull 是一个存储库范围的操作吗?意思是,它是更新存储库中的本地分支(跟踪远程分支),还是只为当前签出的分支获取和合并?

push 也一样吗? --all 对 push 和 pull 有什么作用?

任何帮助都会动摇!

另外,fetch 有什么作用?它是否获取特定分支的信息(.git 文件夹内的文件)?或者 .git 文件夹在整个仓库中是否一致?如果我确实 fetch 而不是 clone,那之后我真的什么都做不了,那么 fetch 之后我该怎么办?

【问题讨论】:

  • 取决于版本、确切的命令、本地配置……请仔细阅读您的手册。

标签: git git-push git-pull git-fetch


【解决方案1】:

git pull 只是git fetchgit merge 的组合。

git fetch会更新所有远程分支,git merge会通过合并对应的远程分支来更新当前分支。

普通git push 的确切行为取决于git config push.default 的输出。 最近的 git 版本将此设置为 simple,这只会推送当前分支。

有关命令行选项的确切含义,请使用git help pushgit help pull

git clone 只是 git initgit remote addgit fetchgit checkout 的组合。

您的.git 文件夹您的本地存储库,其中包含所有文件的完整历史记录。 .git 文件夹之外的文件是您的“工作树”。更改文件需要工作树,但大多数其他 git 命令(如 git log)不需要工作树。

【讨论】:

  • 对于任何使用 git 1.8.3 或更早版本的人来说,git pull 运行 git fetch 的方式存在显着差异:您看不到远程分支的更新。如果你只是运行git fetch,它会更新所有远程分支。这最终会显示出来,并且可能会令人困惑。如果你有 1.8.4 或更新版本,没什么大不了的! :-)
【解决方案2】:

TL;DR 总结:“视情况而定”。

答案是“两者都不是”,真的。或“视情况而定”。或者类似的东西!

首先,需要考虑两个基本操作:fetchpush。 (pull 操作只是在fetch 之上构建的一个shell 脚本,所以一旦你知道它是如何工作的,我们就可以正确解释pull。)

fetchpush 都可以访问整个存储库。但一般来说,它们不能通过网络(或其他通信渠道)发送整个存储库来工作。它们基于引用

获取和推送操作通常采用“refspecs”,它们是引用对(分别为remote:local 和local:remote)加上一个可选的“force”标志前缀+。但是,它们可以只给出一个简单的引用,并且可以使用-f--force 指定强制标志。

这两个命令已经存在了很长时间,并且积累了很多“老东西”。使用远程存储库的“现代”方式是通过称为“远程”的东西,使用git remote add 创建它们(并且git clone 默认创建一个名为origin 的东西)。这些变成.git/config 文件中的条目:

[remote "origin"]
    fetch = +refs/heads/*:refs/remotes/origin/*
    url = ssh://...

url = 行给出了 fetch 和 push 的 URL — 尽管如果需要可以有一个额外的 pushurl = 行,以使推送转到其他地方。 (有一些“旧方法”可以直接运行 fetch 和 push 并提供 URL 等等,但我们忽略所有这些......远程更好!)这也提供了 refspecs——嗯,一个 refspec,在这种情况下——给git fetch

git ls-remote

除此之外,让我们完全从另一个命令开始,git ls-remote。这就像fetch,但实际上并没有获取任何东西:

$ git ls-remote origin
676699a0e0cdfd97521f3524c763222f1c30a094    HEAD
222c4dd303570d096f0346c3cd1dff6ea2c84f83    refs/heads/branch
676699a0e0cdfd97521f3524c763222f1c30a094    refs/heads/master
d41117433d7b4431a188c0eddec878646bf399c3    refs/tags/tag-foo

这告诉我们名为origin 的远程有三个引用名称。两个是分支,一个是标签。 (特殊的HEAD ref 与refs/heads/master 具有相同的SHA-1,因此git 会猜测远程是“在分支master”上,正如git status 可能所说的那样。远程协议中存在各种错误: git 应该能够说“HEAD 是一个符号引用,指向refs/heads/master”,这样你就不必猜测了。这将解决两个分支与HEAD 具有相同 SHA-1 的情况.)

git 获取

当您运行git fetch origin 时,提取操作以相同的ls-remote 开始,或多或少,因此会看到所有分支和标签。如果你使用--tags,它也会带来所有的标签,否则它会做一些相当复杂的事情1 会带来所有的分支和一些标签。它还可以查看所有 other 引用,但默认情况下,它不会将这些引用带过来:例如,远程可能有 refs/notes/commitsgit notes 使用它,但那个没有过来。

但是,当您更改提供给 git fetch 的 refspecs 时,您会更改所带来的内容。默认是.git/configfetch = +refs/heads/*:refs/remotes/origin/* 中的那个。这个 refspec 说要引入所有 refs/heads/* 引用 - 所有分支 - 并将它们本地存储在 refs/remotes/origin/ 下,使用与远程分支名称相同的名称。使用--tags 会增加一个额外的参考规范:refs/tags/*:refs/tags/*。这就是 git 带来所有标签的方式:所有匹配 refs/tags/* 的东西,也就是所有标签,都以匹配的名称进入你本地的 refs/tags/

(您可以添加更多fetch = 行并带来更多内容。有关示例,请参见“远程标签”中的this answer。)

现在,仅引入引用 name 不会有多大好处,除非 git 还引入任何所需的底层 objects,2 为由他们的 SHA-1 识别。假设您已经拥有676699a...,但还没有222c4dd...。 (你在master 上是最新的,但在branch 上不是。也许你甚至还没有 分支branch。)获取操作需要确保提交该提交.该提交可能需要各种文件,以及以前的提交,等等。因此,您的git fetch 与正在查看另一个 git 存储库的远程对象进行通信,并且他们进行了一些对话,每个人都告诉对方他们现在拥有哪些 SHA-1,以及他们仍然需要哪些 SHA-1。如果你需要222c4dd...,它会询问另一端“我还需要使用 222c4dd...”,检查它是否有这些,如果没有,将它们添加到它的列表中,检查那些添加后更详细,依此类推。

在最终同意交换什么后,他们的 git 将对象发送给您——如果可能,通常以“精简包”的形式发送(细节取决于传输方式)——然后您的 git 会根据需要解包和/或重新打包它们,然后更新任何新分支、标签或其他引用的本地引用。 (默认情况下,你的 git 只是将他们的分支存储在你的“远程分支”中——你的“我上次与他们交谈时他们拥有的内容”的副本——但会更新 你的标签。也就是说,有没有“远程标签”,只有“远程分支”。)

一个重要的 git fetch 特例

作为一种特殊情况,如果您给git fetch 提供任何超出远程名称的参数,例如:

git fetch origin master

例如——这些 refspecs 会覆盖配置文件中的那些,(在 1.8.4 之前的 git 版本中)阻止更新“远程分支”。这通常会限制获取的内容,有时会限制很多。 (在 1.8.4 及更高版本中,它们仍然限制 fetch,但无论如何远程分支都会更新,这更有意义。)这里,缺少冒号的 refspec - 就像上面的那样 - 是 not 被视为两边都具有相同的名称。相反,“他们的”分支像往常一样被收集起来,但 SHA-1 和分支名称被写入.git/FETCH_HEAD

(有一个很好的理由:如果git fetch origin master 更新了您的master,您将丢失您所做的所有新提交!所以您希望它只更新origin/master 和/或FETCH_HEAD。 )

git 推送

push 操作与fetch 非常相似。虽然它不是完全对称的:你不会推送到“远程分支”,一般来说,你只是直接推送到“分支”。例如,当推送您的分支master 时,您的本地引用是refs/heads/master,并且它们的本地引用 refs/heads/master。肯定不是refs/remotes/yoursystem/master。所以用于 push 的 refspecs 通常要简单一些。

如果你只是运行git push(或git push origin),这仍然需要提供一些参考规范。

在 git 配置文件 push.default 中有一个(某种新的)控制旋钮,允许您配置 git 推送的引用。在当前版本的 git 中,它默认为 matching。在 git 2.0 中,它将更改为 simple。总共有五种可能的设置:

  • nothing:产生错误
  • current:将你所在的分支推送到同名分支
  • upstream:将您所在的分支推送到其上游名称
  • simple: 和上游一样,但要求上游名称与本地名称匹配
  • matching:推送所有同名分支

其中一些需要进一步解释。 “上游名称”是另一端的分支名称。假设您有一个名为origin/feature 的远程分支,并且为它创建了一个本地跟踪分支,但将其命名为feature2,因为您已经在另一个feature 分支上工作(尚未在origin 上创建)。所以你本地的feature2remote/origin 作为它的上游(而你的feature 根本没有上游)。推送到upstream 将遵循映射,并将您的feature2 推送到他们的feature。使用 simple 推送将拒绝该尝试。

因此,如果您 git push 没有 refspec,git 将查找默认配置3 并基于此构造一个 refspec。对于matching 案例,它会将您和他们都拥有的每个分支推送(因此,如果您拥有masterbranch,请将您的master 推送到他们的master,并将您的branch 推送到他们的branch),但对只有你们一个人拥有的分支不做任何事情。

如果你给出一些明确的 refspec(s),所有这一切都变得毫无意义:推送操作会推送你给它的 refspec。此外,不带冒号的 refspec 意味着“两端使用相同的名称”,因此master 是编写完整长版本refs/heads/master:refs/heads/master 的简写方式。

与 fetch 一样,您的 git 和他们的 git 通信以确定需要发送哪些存储库对象(如果有)来完成推送。

git 拉

git pull 操作运行git fetch 的四字形式。

它的第一步是弄清楚要使用什么遥控器。如果你说出一个:

git pull origin master

它需要你给它的名字;否则它会查看您所在的分支(比如说master),然后查看.git/config 以找到branch.master.remote(可能是origin)。

然后,它会确定使用哪个分支。如果您命名一个,它会使用它;否则,它使用branch.master.merge,这是另一端分支的名称(通常只是master)。然后它使用这些参数运行git fetch

这意味着提取只会带来“有趣”的分支,在本例中为 master,并将 SHA-1 放入 FETCH_HEAD。 (如果你有 git 1.8.4 或更高版本,它也会更新origin/master。)

最后,pull 运行 mergerebase,再次取决于配置条目以及您是否使用 --rebase 运行它。您将合并或变基的提交是其 SHA-1 现在存储在 FETCH_HEAD 中的提交。

请注意,这只会合并或重新设置您当前的分支。


1如手册中所述,fetch 默认使用“标签跟踪”技巧:它查看标签中的 SHA-1,并查看它们是否在或将在您的存储库中。对于那些现在或将要成为的人,它带来了那个标签。您可以使用 --no-tags 关闭此功能。

2对象是存储库实际存储的东西:“blob”(文件)、树(包含文件或更多目录的目录)、提交和“带注释的标签”。每个都有一个唯一的 SHA-1 名称。

3但是,您可以使用每个分支配置 branch.<em>name</em>.pushremoteremote.<em>name</em>.push 来覆盖它。您可以通过转动大量配置旋钮来制作大量难以理解的效果。

【讨论】:

  • 非常感谢,这太彻底了!
猜你喜欢
  • 2013-12-29
  • 2019-09-30
  • 2011-02-24
  • 2012-01-16
  • 2021-09-02
  • 1970-01-01
  • 2020-06-27
  • 2016-03-15
  • 1970-01-01
相关资源
最近更新 更多