这里似乎没有错误。尽管如此,您的输出中仍有一些轻微可疑之处,这表明您可能正在运行 Git 版本 1.8.3 或更早版本,也可能不是。 (您可以使用git --version 轻松检查。如果您的 Git 是 2.0 之前的版本,则可能是时候升级了。截至本答案,当前版本是 v2.21。)
有趣的是这一点:
➜ dir git:(master) git fetch boilerplate phaser3
From https://github.com/lean/phaser-es6-webpack
* branch phaser3 -> FETCH_HEAD
将此与我今天下午在我的 Linux 存储库中 git fetch origin master 得到的结果进行比较:
$ git fetch origin master
remote: Counting objects: 235, done.
remote: Compressing objects: 100% (160/160), done.
remote: Total 235 (delta 120), reused 124 (delta 71)
Receiving objects: 100% (235/235), 273.62 KiB | 230.00 KiB/s, done.
Resolving deltas: 100% (120/120), done.
From git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
* branch master -> FETCH_HEAD
9e98c678c2d6..54c490164523 master -> origin/master
除了额外的冗长(计数、压缩、接收等)之外,最后两行在一个非常重要的方面有所不同:
* branch master -> FETCH_HEAD
9e98c678c2d6..54c490164523 master -> origin/master
它们表明我的 Git 已将新的哈希 ID 写入 origin/master。具体来说,之前我跑了git fetch我得到了:
$ git rev-parse origin/master
9e98c678c2d6ae3a17cb2de55d17f69dddaa231b
之后我跑了git fetch,我得到了:
$ git rev-parse origin/master
54c490164523de90c42b1d89e7de3befe3284d1b
这是因为git fetch origin master 更新了我自己的 Git 存储库中的 两个 关键项目。其中之一是我的远程跟踪名称,refs/remotes/origin/master,通常缩写为origin/master。另一个是特殊文件.git/FETCH_HEAD,git fetch 总是根据您告诉它获取的内容以及它从另一个 Git 获得的内容来写入。
您的输出显示您的 Git 仅更新了这些项目中的 一个,即文件 .git/FETCH_HEAD。但这可能是正常的,不是旧版本 Git 的标志,因为如果我之后立即重新运行 same git fetch,就会发生这种情况:
$ git fetch origin master
From git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
* branch master -> FETCH_HEAD
这看起来和你自己的输出一样。所以我怀疑你的git fetch 基本上什么也没做,就像我的second git fetch,因为没有什么可做的。这将导致您看到的其他行为。
这里发生了什么
了解正在发生的事情(git fetch 正在做什么)的关键在于意识到分支名称对 Git 来说并不是很重要。 对 Git 重要的是每个 commit。每个提交都由其哈希 ID 唯一标识,看起来是随机的,但实际上是其内容的加密校验和。
Git 提交存储所有文件的快照,以及一些元数据——一些关于提交的数据。元数据包括提交者的姓名和电子邮件地址、何时他们提交(时间戳)、为什么他们提交(他们的日志消息),以及—至关重要 — 提交的 父哈希 ID。
这是来自此特定存储库的示例提交。它的哈希ID是d1f0301b3333eef5efbfa1fe0f0edbea01863d5d:
$ git cat-file -p d1f0301b3333eef5efbfa1fe0f0edbea01863d5d | sed 's/@/ /'
tree 86c897e94952090eee579ed47f49cc7006c41fd6
parent 6b4703768268d09ac928c64474fd686adf4574f9
author Thomas Gleixner <tglx linutronix.de> 1533300299 +0200
committer Thomas Gleixner <tglx linutronix.de> 1533302341 +0200
genirq: Make force irq threading setup more robust
The support of force threading interrupts which are set up with both a
primary and a threaded handler wreckaged the setup of regular requested
threaded interrupts (primary handler == NULL).
[snip]
这不是我们通常看待提交的方式——大多数时候,我们可能会使用 git show 或 git log,例如,这些提交比较之前的提交,以便我们可以看到这两个快照有什么不同。这就像比较今天的温暖或多雨与昨天的温暖或多雨:它告诉我们什么改变了,这对我们来说有时比绝对状态更重要。但这确实显示了提交中的确切内容:它有一个 tree(带有一些内部 Git 哈希 ID)、一个带有另一个提交哈希 ID 的 parent,以及作者和提交者并记录消息。 (一些提交有更多的东西。)树是 Git 存储快照的方式。 parent 行告诉我们哪个提交在之前这个提交。
结果是,给定一些 Git 提交哈希 ID,Git 总能找到该提交之前 的提交。实际上,父行指向另一个更早的提交。所以如果我们有一些很长的提交链:
... <-F <-G <-H ...
我们可以选择其中一个提交并按照其箭头向后上一个提交:从H 到G,然后到F,依此类推。
这里的问题是这些哈希 ID出现完全随机,并且部分取决于什么时间,精确到秒,有人制作它们。例如,Thomas Gleixner 在1533300299 +0200 进行了上述提交。1所有这些细节都在提交哈希 ID 中。这意味着我们无法知道哈希 ID 是什么——它实际上是随机的。那么我们如何知道哪个提交是最新的提交呢?
答案是 Git 通过让我们创建分支名称来帮助我们。我们告诉 Git:以 master 或 phaser3 之类的名称存储 最新/最新 提交的哈希 ID。给定 ID,Git 可以找到 commit 本身。该提交存储了父 ID,因此 Git 也可以找到父 ID。父级存储另一个父级 ID(可以说是此提交的祖父级),因此 Git 可以找到祖父级,并且该提交存储另一个父级 ID,以此类推。
事实上,历史只不过是通过从每个提交到其父级找到的一组提交。访问的提交,从某些起点和向后工作,是 Git 存储库中的历史记录。起点是存储库中的所有名称:分支名称、标签名称(如v2.1)和远程跟踪名称(如origin/master 或boilerplate/phaser3)。2
1嗯,那是他创作(原始版本)提交的时候,但后来他提交在 1533302341 提交, 34 分 2 秒后。
2存储库可以在经过各种操作(如git rebase 或git commit --amend 将包含未访问过的提交通过这个从某个名称开始遍历所有可访问的提交过程。这些剩余或垃圾提交最终会被 Git 的 垃圾收集器 清理并丢弃,git gc。请注意,git gc 至少做了很多其他的内务管理和性能改进的事情,或者旨在提高性能的事情。这一切都是自动完成的——通常不需要手动运行。
Checkout 有时会创建分支
你跑了git checkout phaser3,你的 Git 说:
Branch 'phaser3' set up to track remote branch 'phaser3' from 'boilerplate'.
Switched to a new branch 'phaser3'
通常,当您说git checkout <em>somebranch</em> 时,Git 所做的是在 your 存储库中查找名为 somebranch 的 your 分支。该分支名称标识应该被视为分支的 tip 的 newest / latest 提交。您的 Git 会将该提交提取到您的索引和工作树中,以便您可以查看并使用它。
不过,有时你告诉 Git 签出一个分支,而你没有那个分支。而不是失败——这仍然可能发生:
$ git checkout nosuchbranch
error: pathspec 'nosuchbranch' did not match any file(s) known to git
——你的 Git 从列出你自己的分支开始,比如git branch:
$ git branch
* master
您要求的名称不在列表中,但 Git 还没有准备好放弃。现在它会检查您的所有远程跟踪名称,您可以使用git branch -r 列出这些名称。结果取决于您拥有哪些遥控器——大多数人在他们的存储库中只有一个,名为origin——以及你的Git 记住了哪些名称来自 那些遥控器:
$ git branch -r
origin/develop
origin/feature2
origin/master
upstream/master
upstream/develop
我要求nosuchbranch,但这些都不是那样的。但是,如果我要求 feature2 呢?
Git 会扫描这些,并且会有恰好一个名称匹配feature2,即origin/feature2。所以我的 Git 会说:啊哈,你想让我创建一个新的 feature2,使用 origin/feature2 作为提交!现在我可能有:
... <-H <-I <-J <-- feature2 (HEAD), origin/feature2
名称feature2 和origin/feature2 都记住提交J 的哈希ID。
请注意,在这种特殊情况下——我没有develop 但origin 和 upstream 都有一个——如果我运行:
git checkout develop
我的 Git 会放弃,因为它不知道是应该从 origin/develop 还是从 upstream/develop 创建一个新的 develop。即使它们都标识了 same 提交——比如说,提交L,我没有在此处的图表中绘制——我的 Git 也不知道将这两者中的哪一个用作 我的新develop 的上游设置。我可以在这里做的是:
git checkout --track origin/develop
它告诉我的 Git:使用origin/develop,创建一个新的develop,其中develop 更新并推送到origin 的develop。
我们在这里进展得非常快,没有详细说明如何所有这些是如何工作的,但我会在这里再次提到,这会设置新创建的 upstream 设置分支。分支的上游是当你运行 git status 时 Git 如何决定说“前面”和/或“后面”,以及如果你运行 git merge 或 git rebase 没有参数,Git 如何知道要使用哪些提交。如果你不提供额外的参数,上游决定了git pull 或git push 将做什么,以及当你有多个遥控器时git fetch 将使用哪个遥控器。所以上游设置非常有用。
总结
git pull --rebase 什么都不做,因为没有新的提交:当按照我喜欢在 StackOverflow 上绘制提交的方式绘制时,您的图表看起来像这样:
...--G--H <-- phaser3 (HEAD), boilerplate/phaser3
或者像这样:
...--G--H <-- boilerplate/phaser3
\
I <-- phaser3
git rebase 所做的工作是复制您拥有但他们没有的提交(例如此图中的提交 I)到上次提交之后的新提交。在这种情况下,将 I 复制到紧随 H 之后的新提交,而不是 I 现在的位置,但 I 已经紧随 H 之后,所以没有必要做任何这样的复制。 (并且在两个名称都指向相同提交H的情况下,没有提交要复制,这也意味着没有工作要做。)
phaser3 的上游是boilerplate/phaser3。 boilerplate 这个名字本身——通常——只是https://github.com/lean/phaser-es6-webpack 的简称; Git 称其为 remote。
在https://github.com/lean/phaser-es6-webpack 上有一个完整的 Git 存储库;它有自己的四个分支。您的存储库已将其 phaser3 分支(即存储在该名称中的哈希 ID c9584e75521a3b94d4f883a246aad134b83dc12d)复制到您自己的 remote-tracking 名称 boilerplate/phaser3。
git fetch 和 git push 命令是将提交从一个存储库传输到另一个存储库的命令。通常,发送者发送接收者尚未拥有的提交,因此最终,接收者拥有发送者拥有的一切,如果它已经拥有发送者没有的东西,可能还会更多。将提交从发送方发送到接收方后,接收方现在需要更新一些名称或名称以记住新的提交。
当使用git fetch 时,接收者——你!——或者更确切地说,你的 Git——更新你的远程跟踪名称。您取他们的名字phaser3,并在其前面加上您的远程名称boilerplate,并在其间加上一个斜线。这给了你boilerplate/phaser3 作为记住他们提交的一种方式。
当使用git push 时,发送者——又是你——负责要求接收者在接收者中设置一些名称。 您选择您要求他们设置的名称。如果该名称是 phaser3,您是在要求他们更改 他们的 phaser3。他们没有远程跟踪名称:这里没有quantum_spaghetti/phaser3。您要求他们更改他们的 名称。因此,一般而言,您应该确保无论您向它们发送什么新提交,这些新提交都将其现有提交作为历史记录的一部分。