Git 的 fetch 不获取文件——好吧,反正不是直接获取。
在某种程度上,Git 根本不关心文件。 Git 关心的是提交。不过,在我们进一步探讨这个想法之前,我们可能应该回顾一些基本的 Git 定义。
存储库中有什么
Git 存储库 包含三个主要部分:commits、index 和 work-tree。 (一些 Git 存储库会省略工作树,在较新版本的 Git 中,您可以拥有多个工作树,其中每个工作树都有自己的索引。但通常您从每个工作树开始。)
commit 是一个快照:一个完整的set 文件。它不仅仅是 a 文件,也不是某些文件中的 差异:它是一个独立的东西,您决定保存在该提交中的所有文件,在保存它们时的形式。提交也是永久且不变的。像所有 Git 对象一样,它有一个唯一标识符:例如3313b78c145ba9212272b5318c111cde12bfef4a。一旦存储,您将永远无法在提交中更改 任何内容。如果您尝试,您将获得提交的副本,其中包含更改,并且副本具有新的不同 ID。您可以(有时)完全删除一个提交,但不能更改它,只能将其复制——嗯,大部分,当然,除了更改的部分之外——复制到一个新的不同 ID 提交.
Git 真的,真的 关心提交。确保你永远不会失去一个非常努力。它不太关心索引和工作树:它们既不是永久的,也不是不变的。提交的优点是显而易见的,但它们的缺点也很明显:它们以计算机上其他任何东西都无法处理的形式存储在 Git 中——存储库中。
工作树与此相反:它的形式是计算机上的所有东西都可以处理,而且它是无常且多变的。这是你做所有工作的地方。它有文件,而不是神秘的 Git 对象。这是您读取、写入和编辑文件的地方。
Git 的 index 最初对大多数人来说是相当神秘的(对我来说是这样),它有很多你最终会遇到的复杂的曲折。一些软件试图完全隐藏索引,但这不是一个好主意。一方面,索引实际上非常简单:它是 Git 让您构建下一次提交的地方。索引开始匹配 current 提交,然后你 git add 现有文件的新版本或全新文件到索引,以复制新文件。然后当你运行 @987654324 @,Git 会根据您现在在索引中的任何内容进行新的提交。这会生成永久的、不变的快照。索引,也称为暂存区,只是您排列(或“暂存”)文件的位置,以使它们在快照中尽可能漂亮。
每个提交还记录其直接前任或父提交的 ID。一旦您开始处理历史,这将变得至关重要。历史记录由提交本身形成,通过“我的父母是……”信息。
像master 这样的分支名称仅通过其 ID 标识该分支上的 最新 提交。 Git 将此称为分支的 tip。这个最新的提交会记住它的父级,而那个父级会记住它自己的父级(最新提交的祖父级),依此类推。 Git 也有其他实体做同样的事情:记住一个特定提交的 ID。最重要的两个是标签和远程跟踪分支。
总结
存储库包含 commits,其中包含 snapshots,并形成了所有提交的历史记录。 分支名称 master 在master 上找到最新 提交。而且,虽然提交包含文件,但它们本身并不是文件:它们包含整套文件,全部作为一个集合。
存储库有一个索引,它是内部 Git 提交表单和工作树表单之间的中介,并且大多数存储库都有一个工作树,它可以让您获取提交的文件as文件。
git checkout 做了什么
git checkout 命令主要将提交复制到索引和工作树中,以便您可以在所有提交的历史记录中移动并在工作树中查看相应的快照。它还调整了 Git 调用的 HEAD。
名称HEAD,在 Git 中,总是通过其 ID 引用当前提交——但它以两种不同的方式之一这样做。您可以“在一个分支上”,在这种情况下,名称 HEAD 只包含分支的名称。然后是分支名称获取 Git 当前提交的 ID。或者,您可以有一个“分离的 HEAD”,在这种情况下,名称 HEAD 记录了当前提交的 ID。
如果你给git checkout 一个分支名称——比如git checkout master——它会将你放在“分支上”:它会检查提示提交,因为这是存储在分支名称中的 ID,然后它会将分支放在HEAD 中的名称。如果你给 git checkout 一个原始的提交 ID、标签名称或远程跟踪分支名称,它会找到相应的 ID,检查该提交,并将 ID 放入 HEAD。
git fetch——和git push——做什么
以上所有步骤都完全适用于您自己的存储库。不过,Git 并不将您限制在一个存储库中。在您选择的明确时间,您可以告诉您的 Git 调用另一个 Git,通常是通过 Internet,并与另一个 Git 进行某种对话。
git fetch 和 git push 都是这样做的。他们在某个 URL 的另一端调用其他 Git。 URL 通常存储在一个名为 remote 的名称下。最常见的——通常是任何给定存储库中唯一的远程——是origin(因为git clone为你设置了一个)。
但请记住,Git 最关心的是提交。所以当你的 Git 调用另一个 Git 时,他们的对话主要是关于提交。当然,他们确实需要一种方法来查找这些提交的 ID,为此他们通常以一些分支名称开头。这通常是 Git 开始一切的方式:取一个分支名称,或者可能只是名称 HEAD,然后找到一个提交 ID。使用该提交。然后,如果合适,请转到该提交的父级并使用 that 提交执行某些操作,依此类推。
fetch 进程尤其会获取另一个 Git 中所有分支的列表。然后,它会获取那些分支中的所有 commits,而这些分支在它自己的存储库中还没有。这些提交带有任何必要的快照文件,几乎是一种副作用。最后,您的 Git 获取其 Git 的分支名称并重命名它们,将这些分支名称转换为您自己的远程跟踪分支名称。
如果遥控器被命名为origin,他们的(来源)主人将成为你的origin/master。你得到了他们所有的提交,除了你已经拥有的。你已经拥有的,你已经拥有了。您的 Git 可以确定您拥有它们,因为您拥有 ID。每个提交的 ID 对于该提交都是唯一的,并且该提交是永久且不变的——因此,如果您拥有他们所拥有的 相同 ID,那么你们俩必然拥有相同的 commit .
你的 Git 和他们的 Git 使用 git push 非常相似,但在另一个方向上,并且有一个转折:你的 Git 给他们你的提交——你拥有的那些,他们没有,然后问他们设置他们的 master,或者你正在推送的任何分支,设置为它的提示提交,与你的 master 的提示相同的提交。这里没有重命名:您要求他们将master 与您的master 完全相同。
当您 git fetch 时,您的 Git 重命名它们的分支名称,因此将它们全部使用是安全的。不管他们对他们的 分支做了什么,这不能影响你自己的分支名称。但是当您git push 时,您的 Git 会要求他们设置他们的分支名称,根本不需要重命名。如果他们不喜欢请求的设置,他们可以说“不,我不会设置”:他们可以拒绝您的推送。 fetch 不会发生这种情况,这就是您最初的问题所在。
git pull = git fetch + 其他东西
获取只是让您获得他们的新提交。 因为git fetch从不接触自己的分支,所以你经常想要第二步。
这里的主要问题是 正确 采取的第二步取决于您引入了哪些提交,以及您已经拥有哪些提交。有两个主要选项:git merge 和 git rebase。您可以对 Git 进行编程以使 git pull 执行任一操作。默认是git merge。
同样,“正确”命令取决于您在分支中拥有的内容、获取时从远程获取的内容以及您希望如何工作。在实践中,大多数人在这里大多想要git rebase,但git pull 默认运行git merge。在许多情况下,两个命令最终都会做同样的事情,因此默认是 wrong 命令并不重要。但我建议新手避免使用git pull,因为它确实默认是大多数人不想要的命令,而且因为当事情出错时——他们总是最终会这样做——通往的方法从问题中恢复取决于您是否知道自己运行了git rebase 或git merge。如果你真的直接使用它们,你就会知道。