Git 拉取与获取 - 新获取的分支没有区别?

Git pull vs fetch - no difference for newly fetched branches?

我一直在阅读 git pullfetch 命令以及它们之间的区别。

我同意当我们在本地和远程都拥有 master b运行ches 时,这两个命令之间存在差异,因此 pull 将整合我们获取的任何更改。

但是,如果新的 b运行ches 被推送到以前从未获取过的远程服务器,该怎么办。如果我们只使用 git fetchgit pull,在我们拥有 fetched/pulled 那些 b运行ches 之后,从 Git 的角度来看,内部会有什么区别?如果我们只 运行 git fetch ,新的 b运行 是否没有集成?

我想对其进行测试并执行了以下操作:

我有一个克隆了两次的远程存储库,让我们将这些本地存储库称为 repo 1repo 2 - repo 1 将创建新的 b运行ches 并推送它们到远程,repo 2 将从远程 pull/fetch 他们。

我创建了一个新的 b运行ch - side_branch_1 - 从 repo 1 推送到远程仓库。然后我回到 repo 2 并使用 git pull。然后我 运行 git branch -a 看到新的 b运行ch 为 remotes/origin/side_branch_1。我还打开了 .git/FETCH_HEAD 文件并看到了 b运行ch 的行:<sha-1> not-for-merge branch side_branch_1 of <url>.

之后,在 repo 1 中我创建并推送了 side_branch_2,在 repo 2 中我这次使用了 git fetch。然后我再次 运行 git branch -a 并看到新的 b运行ch 为 remotes/origin/side_branch_2。我还再次打开 .git/FETCH_HEAD 文件并看到 b运行ch 的行:<sha-1> not-for-merge branch side_branch_2 of <url>.

pull还是fetch新b运行ches没有区别吗?如果是,那么与 Git 内部观点 有什么区别?

因为 side_branch_1 被标记为 not-for-merge 即使它已被 拉动 。为什么 ?我错过了什么?

TL;DR

git pull 表示 运行 git fetch,然后 运行 第二个 Git 命令 。第一步——git fetch——不会影响任何你的 b运行。如果您正在处理任何事情,它不会改变您正在处理的任何事情。

second 步骤,默认为 运行ning git merge,会影响您的 current b运行ch。它不会创建新的 b运行ch,所以一般来说,在其他 Git 中创建的任何 new b运行ch 名称都不相关除非您在 git pull 命令中明确命名它们。

假设你 运行 git pull 没有额外的参数, remote git pull 运行s git fetch 是与当前 b运行ch 关联的远程,用于 rebase-or-merge 的提交是与当前 b[ 的 upstream 关联的提交=628=]ch 由 git fetch 步骤更新。 Git 对存储库中 b运行ch 名称的上游设置施加限制:特别是,如果您的 Git 还不知道其他 Git 中存在某个名称,您的 Git 不会让您将其设置为上游。所以 "new" b运行ches——我们还没有正确定义,真的——不相关。

如果您向 git pull 命令行添加更多参数,情况会变得更加复杂。

Is there no difference for new branches whether I pull or fetch?

Git pull 总是意味着:运行 git fetch,然后 运行 第二个 Git 命令 。很明显它们是不同的,因为 git fetch 没有 运行 第二个 Git 命令。这与获取步骤是否看到您的 Git 以前没有看到的 b运行ch 名称无关。

And if yes then what is the difference from Git internal point of view?

在这里您需要密切注意 Git 的实际工作原理。为了让这个答案简短(大概),我会说看到很多我的其他答案以获得很多细节,但是:

  • 每个提交都有一个唯一的哈希 ID,它是 random-looking commit-name git log 向您显示的长 random-looking commit-name:例如 commit 1c56d6f57adebf2a0ac910ca62a940dc7820bb68
  • 每个提交存储所有文件的 快照。每个提交中的文件都采用特殊的 read-only、Git-only 压缩格式,并永久冻结。

  • 每个提交还存储一些 元数据: 关于提交的信息,这些信息不是与提交一起保存的文件,而是包含诸如谁制作的东西提交、时间和原因(他们的日志消息)。在此元数据中,每个提交存储其直接 parent 提交的哈希 ID(对于大多数提交;有些存储两个或更多父级,这些是 merge commits,并且至少有一个将是存储库中的第一个提交,因此不会有父项)。

  • A b运行ch name 就像 master 只是保存 last[ 的原始哈希 ID =419=] 在链中提交。因此,如果你有一个名为 master 的 b运行ch 和一些提交,master 持有一些哈希 ID H,并且提交 H 指向一些更早的提交 G,指向 yet-earlier 提交 F,依此类推:

    ... <-F <-G <-H   <--master
    

    为了添加一个提交到一个b运行ch,我们select那个b运行ch的名字,select这就是承诺。这将冻结的 Git-only 文件 out 提交到我们可以处理它们的区域。我们根据需要对它们进行处理,并最终告诉 Git:进行新的提交。 Git 使 new 提交点回到我们得到的那个,保存我们所有文件的新快照,然后在进行新提交后,更改 b运行ch 名称,使其指向新提交:

    ...--F--G--H--I   <-- master
    
  • B运行ch 名称并不是唯一可以记住提交哈希 ID 的名称。不止一个名称也可以标识任何一个提交。

git clone 命令通过调用另一个 Git 存储库来工作。你告诉你的系统:

  1. 创建一个新的空目录/文件夹(或使用您指向 git clone 的空文件夹)。
  2. 创建一个新的空存储库git init.
  3. 存储一个 URL 供以后使用,名称为 origin(或您告诉 Git 使用的任何其他名称):git remote add.
  4. 使用 git clone 命令执行您告诉 Git 执行的任何其他配置。
  5. origin 调用另一个 Git——在存储的 URL——并让它列出它的 b运行ch(和其他)名称及其原始名称哈希 ID。然后,要求 Git 提交……在这种情况下,所有。将其提交的 all 复制到我们的 otherwise-empty 存储库中。取其 b运行ch 名称并 重命名 它们:使其 master 成为我们的 origin/master,例如,并使其 develop成为我们的origin/develop,等等。
  6. 最后,对于其中一个名字——可能是 master——使用重命名的 origin/ 版本的名字来制作 b运行ch 名称,并指出运行ch 名称与我的 origin/ 版本的名称相同。

所以在初始 git clone 之后,您有 remote-tracking 个名称,通常采用 origin/* 的形式,其他每个 Git的b运行ch名字。然后你就有了一个你自己的 b运行ch 名字,通常是 master,指向与你的 origin/master 相同的 提交。如果他们有 masterdevelop,也许您现在有:

...--G--H   <-- master, origin/master
      \
       I--J   <-- origin/develop

上述six-stepgit clone序列中的第5步,实际上是git fetch。然而,获取每个提交git fetch所做的是另一个Git交谈,以查看哪些提交他们有你没有的。在初始克隆期间,您没有 any 提交,因此这自动成为他们的全部。后来是他们的

当你 运行 git fetch 之后,如果他们仍然有他们的 master 识别提交 H 和他们的 develop 识别提交 J ,您的 Git 将在您的存储库中查找,使用 HJ 代表的真实哈希 ID,并查看您已经拥有它们。您的 Git 不需要获得任何新的提交。但是,如果他们向 他们的 develop 添加了另一个提交,他们将有新的提交 K,你会得到它:

...--G--H   <-- master, origin/master
      \
       I--J   <-- origin/develop
           \
            K

然后您的 git fetch 将更新您的 remote-tracking 名称 origin/develop 以指向提交 K:

...--G--H   <-- master, origin/master
      \
       I--J--K   <-- origin/develop

如果他们做了一些不寻常的事情并迫使他们 develop 后退 一步,而你又 运行 git fetch,你将 保持提交K一段时间——通常默认至少30天——但是你的Git会调整你的origin/develop以匹配他们的develop

...--G--H   <-- master, origin/master
      \
       I--J   <-- origin/develop
           \
            K   [no name: hard to find!]

Git 通常 finds 从某个名字开始提交——无论是你的 b运行ch 名字,还是你的 remote-tracking 名字,或任何其他名称 - 然后向后工作。

(每个名字都有previously-stored哈希ID的隐藏日志,通过它你可以找到K。这些日志中的条目最终会过期,这就是30天限制的来源from: 30 天后,保留 K 的条目过期。一段时间后,Git 的 垃圾收集器 git gc 将抛出 K 是真的,如果没有人创建一个新的 名称 来保护它。)

运行 git fetch 像这样,完全没有名称——通常默认为 origin,或者只使用遥控器的名称,例如 origin,将——只要你没有特别设置——从其他 Git 获取 all 个 b运行ch 名称,并创建或更新 所有 你的 remote-tracking 相应地命名。但是,设置一个叫做 single-branch clone 的东西会以不同的方式配置您的 Git,因此 git fetch 只会更新一个 remote-tracking 名称。您可以稍后重新配置它,或者使用 refspec 覆盖要更新的名称集,但我们不会在这里详细介绍。

到目前为止,这就是关于 git fetch 的所有内容;让我们开始使用 b运行ch name

同样,Git 的 fetch 是从另一个 Git 获得新提交的部分。获得新的提交后,如果有一些要获得,git fetch 会调整您的 remote-tracking 名称。它对您的任何 b运行ch 名称 都没有影响。你的b运行ch名字都是原封不动的。

如果你从来没有自己的 b运行ch 名字——这很奇怪,尽管这样做是可能的——并且永远不要做任何 work就你自己而言,这对于某些应用程序(例如归档存储)来说不那么奇怪和明智,就足够了。但是你可能 do 使用 b运行ches.

假设您您自己的 b运行ch 名称,dave 或您喜欢的任何名称。假设您将此名称指向现有提交 H:

...--G--H   <-- dave, master, origin/master
      \
       I--J--K   <-- origin/develop

既然您有多个 b运行ch 名称,我们希望 Git 记住您实际使用的是哪一个。我们将把特殊名称 HEAD 附加到其中之一:

...--G--H   <-- dave (HEAD), master, origin/master
      \
       I--J--K   <-- origin/develop

现在我们可以知道您正在使用 name davecommit Hdavemaster 以及 origin/master 三个名称现在都标识提交 H

我们上面提到commit中保存的文件是一种特殊的read-only、Git-only压缩和冻结格式,只有Git可以使用。所以 Git 已将这些文件复制出来,放入 Git 的 index 和您的工作区中。工作区是您的工作树work-tree。它具有以您计算机的普通格式存储的普通文件。

您通常通过操作这些普通文件进行新提交,然后使用 git add 将它们复制回 Git 的索引。这 re-compresses 文件进入冻结格式,准备进入新的提交。当你运行git commit,Git会打包里面的文件它当时的索引。因此我们可以说索引的主要功能是存储你打算放入next commit中的内容。 (它还有其他功能,但我们不会在这里介绍它们。)

最终,您的文件已整理好,git add-ed,您 运行 git commit。 Git 收集适当的元数据并写出一个新的提交,这会为新提交分配其唯一的哈希 ID。 Git 然后将新提交的哈希 ID 存储到当前 b运行ch name,给我们:

          L   <-- dave (HEAD)
         /
...--G--H   <-- master, origin/master
      \
       I--J--K   <-- origin/develop

您同样可以在 masterdevelop 上工作,它开始指向提交 K 或其他任何内容,但无论如何,您都会进行新的提交,它指向你告诉 Git 开始使用的任何提交。

现在,如果您 运行 git fetch他们,无论他们是谁,做出或以其他方式获得了您尚未看到的新提交,这些新提交已添加到他们的 b运行ches。您的 Git 在他们的存储库中看到它们,看到您还没有它们,并获取它们。让我们画一个(停止画 I-J-K,因为它们挡住了路,但是字母已经用完了,所以接下来我将在这里画 M):

          L   <-- dave (HEAD)
         /
...--G--H   <-- master
         \
          M   <-- origin/master

您可能希望以某种方式合并他们的新提交。

具体如何 合并他们的新提交取决于您。例如,您可以:

  • git checkout master 然后 git merge origin/master
  • git merge origin/master 现在在 b运行ch dave
  • 上提交 L

或做任何其他事情。

如果你:

git checkout master; git merge origin/master

不过,您的 Git 会执行 Git 所谓的 fast-forward 合并 。这根本不是合并——它的命名有点糟糕——但它有这样的效果:

          L   <-- dave
         /
...--G--H--M   <-- master (HEAD), origin/master

事实上,如果您 运行 git checkout master; git rebase origin/master,在这种特殊情况下 同样的事情会发生 。在其他情况下,可能会发生不同的事情。

这就是 git pull 发挥作用的地方

通常,一旦您使用 git fetch 从其他 Git 带来新的提交,您往往想要 对它们做一些事情.如果您在 master 上并且他们已经更新了他们的 master,您可能想要做的就是更新您的 master。最常见的两种方法是 运行 git mergegit rebase

git pull 命令可以被告知 运行 其中任何一个作为它的第二个命令。默认值为 运行 git mergegit mergegit rebase都是对当前b运行ch进行操作。也就是看特殊名称HEAD。只要它附加到某个 b运行ch 名称(通常如此),它们就会影响您的 b运行ch 名称。他们对 Git 的索引和您的 work-tree 进行了更改;两者都可能改变哪个提交是由当前 b运行ch 名称 select 编辑的; git merge 可能会进行新的合并提交,或执行 fast-forward 操作,或者有时什么都不做。

我不喜欢 git pull 的部分之一是你并不总是知道,当你点击 Enter 时,到底提交了什么 git fetch 将结束抓取,并可能将任何 remote-tracking 名称移动到哪里。但是您 死定了 运行 宁 git mergegit rebase 使用那些新的提交和更新的名称。 (这在技术上有点偏差,正如我们将要看到的——它不直接使用更新的 origin/* 名称——但它在这里已经足够接近了。)

即使新的提交不是你想用来影响你当前b运行ch的东西,你还是会发生这种情况。你无法判断它是否会发生。您可以使用一些查看器先检查另一个 Git 存储库,但是如果您查看它会发生什么,然后就在您按 Enter 之前,其他人 更改 其他存储库中的内容?尽管如此,人们还是很喜欢它,并且一直在使用它,所以让我们来回答您的详细问题。

I also opened the .git/FETCH_HEAD file again and saw the line for that branch: <sha-1> not-for-merge branch side_branch_2 of <url>.

这是关于 git fetchgit pull 的历史秘密(或不是那么秘密):它们太古老了,以至于 git pull 本身就存在于 remote-tracking 名称之前,例如 origin/master 做到了。遥控器和 remote-tracking 名称是在 Git 版本 1.4 和 1.5 之间的某个时间发明的,并且有一些关于不同想法的摸索。 git pull 命令一直按照人们希望的方式工作,在整个 t运行 过渡时期,随着新奇的遥控器和 remote-tracking 名称的开发。

为了避免经常更改太多代码,and/or 因为遥控器和 remote-tracking 名称还不存在,git fetch 总是写 一切 变成 .git/FETCH_HEAD。为了让早期的 git pull 脚本找出哪个 提交哈希 ID git mergegit fetch 记录 我们的哪个 b运行ch 我们现在使用的名称——这是 "where is HEAD attached" 检查——以及要使用 other Git。然后它将每个 .git/FETCH_HEAD 行标记为 wih not-for-merge,或者不标记,取决于你给git fetch.

的参数

当你运行git pull时,你可以给git pull命令一堆参数:

git pull                 # no arguments at all
git pull origin          # just a remote
git pull origin master   # a remote and a branch name *on the remote*

git pull 字面上 运行 git fetch 时,它将这些参数传递给 git fetch。它现在内置了 git fetch,但它仍然可以正常工作。如果你在这里给出一个或多个b运行ch名称,也就是说,或者那些是git fetch 标记为[=155=的那些] 在 .git/FETCH_HEAD 文件中。

类似地,当 git pull 仍然是一个 shell 脚本时——它是最近用 C 语言重写的——这就是 git pull 决定将哪个哈希 ID 传递给 git merge 或者,如果您选择 git rebase 作为您的第二个命令,则 git rebase。它现在所做的事情更加模糊。由于获取部分现在内置为 C-coded 函数调用,它可以只保留内存中的原始哈希 ID。

在 Git 版本 1.8.4 中,Git 人员决定 git fetch origin master 应该更新 origin/master。在此之前,git fetch origin 会更新 all remote-tracking 名称,但 git fetch origin master 会更新 none.从 Git 1.8.4 开始,git fetch origin master 更新 origin/master。它仍然不会更新其他 remote-tracking origin/* 名称,因为它不会带来与任何更新名称相对应的提交。 (在某些情况下它仍然可以更新 remote-tracking 名称,但它不会。)

结论

git pull运行的git fetch:

  • 主要获取您提供的参数:例如,git pull xyzzy one two three 运行s git fetch xyzzy one two three。 "Mostly" 之所以出现在这里,是因为某些选项会影响使用哪个 second 命令,and/or 被 git pull 本身吃掉, and/or 被传递到第二个命令而不是传递给 git fetch.
  • 从命名的远程(或从给定的 URL 获取,但这会改变很多事情),从而更新一些 remote-tracking names ;
  • 记录它在 .git/FETCH_HEAD 中所做的一切,以防您仍在使用旧的 git pull shell 脚本。

总的来说,git fetch对运行任何时候都是安全的。 (如果你真的愿意,你可以将它配置为不安全的,方法是不恰当地设置 remote.<em>name</em>.fetch 或者传递一个不安全的 refspec 参数。值得注意的是,git fetch 有 built-in 安全检查 即使你这样做 。旧的 pull 脚本 关闭它们!)

随后的 git mergegit rebasecurrent b运行ch 进行操作,让如果您有未提交的工作,就会发生这些情况。 Git 通常会检测到这种情况,并在这些情况下完全阻止 运行ning 的第二个命令。然而,在遥远的过去,pull 命令可能(并且确实)破坏了 in-progress 的工作,无法恢复,因为 git pull——不管怎样,旧脚本——关闭了很多 safety-checks.

无论如何,第二个命令——merge-or-rebase步骤——得到了一堆额外的参数,使它在 Git 1.4 到 1.6 t运行sitional 期间工作相同遥控器和 remote-tracking 名称发生变化的时期。那是将近 15 年前的事了,但它仍然以同样的方式运作。如果您使用:

git fetch
git merge

并且您的 Git 进行了合并提交,默认的合并消息类似于:

merge branch origin/dave into dave

但是如果你使用:

git pull

默认的合并消息更像:

merge branch dave of <url> into dave

"something like" 是因为这里每条消息的准确拼写取决于 b运行ch 名称(很明显),以及你是否合并到 master——这个省略into <branch> 部分 — 插入了一些我不想在这里打扰的引号。 :-)