git fetch 究竟是做什么的?

What does git fetch exactly do?

编辑:我在提问之前检查了这个What does FETCH_HEAD in Git mean?
很抱歉原来的不准确问题。

我的问题是 fetch 是如何工作的? fetch 是否丢弃所有当前日志?

这是我的情况:我和我的队友使用同一个只有一个分支的存储库。所以我们必须在推送任何东西之前进行获取。
我们通常这样做:

git status
git add .
git commit -m message1
git fetch origin
git reset head
git status
git add .
git commit -m message
git push

但重置后,我之前的提交(message1)似乎消失了。

这是正常的还是有什么问题?
我怎样才能访问我的本地历史记录?
它们已同步,但我的本地历史记录不见了。

老员工,算了:最近在学GitCLI
有人告诉我输入“git fetch head”来跟踪远程分支。
但我想知道这是做什么的?此命令会覆盖我的本地日志吗?
git fetch”和“git fetch head”有什么区别?

您不必进行两次单独的提交,并且git fetch不会丢弃任何日志。

 --o--o--o (origin/master)
          \
           x--x (master: my local commits)

你应该做的是在 git fetch 命令获取的任何新提交之上重新设置你的本地提交:

git fetch

--o--o--o--O--O (origin/master updated)
         \
          x--x (master)

git rebase origin/master

--o--o--o--O--O (origin/master updated)
               \
                x'--x' (master rebased)

git push

--o--o--o--O--O--x'--x' (origin/master, master)

更简单,,我会使用配置:

git config pull.rebase true
git config rebase.autoStash true

然后一个简单的 git pull 将在 origin/master 之上自动重播您的本地提交。那么你可以git push.

git fetch本身真的很简单。复杂的部分前后都有。

这里首先要知道的是Git存储提交。事实上,这就是 Git 的本质:它管理提交的集合。这个集合很少收缩: 在大多数情况下,你对这个提交集合所做的唯一事情就是添加新提交.

提交、索引和 work-tree

每个提交都有几条信息,例如作者的姓名和电子邮件地址以及 time-stamp。每次提交还会保存您指定的所有文件的完整快照:这些文件存储在您的 index(也称为 暂存区) 当时你 运行 git commit。您从其他人那里获得的提交也是如此:他们保存其他用户当时在其他用户索引中的文件 运行 git commit.

请注意,每个 Git 存储库只有一个索引,至少在最初是这样。该索引与 work-tree 链接。在较新的 Git 版本中,您可以使用 git worktree add 添加额外的 work-tree;每个新的 work-tree 都带有一个新的 index/staging-area。该索引的要点是充当中间 file-holder,位于 "the current commit"(又名 HEAD)和 work-tree 之间。最初,HEAD 提交和索引通常匹配:它们包含所有提交文件的相同版本。 Git 将文件从 HEAD 复制到索引中,然后从索引复制到 work-tree.

很容易看到 work-tree:它有普通格式的文件,您可以使用计算机上的所有常规工具查看和编辑它们。如果您为 Web 服务器编写 Java 或 Python 代码,或 HTML,则编译器或解释器或 web-server 可以使用 work-tree 文件。存储在索引中的文件,以及存储在每个 Git 提交中的文件,做 not 具有这种形式并且 not 可由编译器使用、口译员、web-server等。

关于提交要记住的另一件事是,一旦文件处于提交状态,它就无法更改。任何提交的任何部分都不能更改。因此,提交是永久性的——或者至少是永久性的,除非它被删除(这可以做到,但很困难,而且通常是不可取的)。然而,索引和 work-tree 中的内容可以随时修改。这就是它们存在的原因:索引几乎是一个 "modifiable commit"(除非它在您 运行 git commit 之前不会保存),并且 work-tree 将文件保存为其余电脑都可以用。1


1没必要同时索引work-tree. VCS 可以将 work-tree 视为 "modifiable commit"。这就是 Mercurial 所做的;这就是 Mercurial 不需要索引的原因。这可以说是一个更好的设计——但它不是 Git 的工作方式,所以当使用 Git 时,你有一个索引。索引的存在是使 Git 如此之快的很大一部分:没有它,Mercurial 必须是 extra-clever,并且仍然不如 Git.[=144= 快]


提交记住他们的 parent;新提交是 children

当您通过 运行ning git commit 进行 new 提交时,Git 获取索引内容并制作所有内容的永久快照就在那一点上。 (这就是为什么你必须 git add 文件:你从你的 work-tree 复制它们,你已经改变了它们,回到你的索引,这样它们就可以 "photographed" 用于新的快照。)Git 还会收集提交消息,当然还会使用您的姓名和电子邮件地址以及当前时间来进行新提交。

但是Git还在新提交中存储了当前提交的哈希ID。我们说新提交 "points back to" 当前提交。例如,考虑这个简单的 three-commit 存储库:

A <-B <-C   <-- master (HEAD)

这里说的是b运行ch namemaster"points to"第三次提交,我已经标注了C,而不是使用 Git 中难以理解的哈希 ID 之一,例如 b06d364...。 (名称 HEAD 指的是 b运行ch 名称,master。这就是 Git 如何将字符串 HEAD 转换为正确的哈希 ID:Git 跟随 HEADmaster,然后从 master 中读取散列 ID。)提交 C 本身 "points to" — 保留散列 ID of—提交 B,虽然;并提交 B 指向提交 A。 (因为 commit A 是有史以来的第一个提交,所以它没有指向更早的提交,所以它根本没有指向任何地方,这使得它有点特殊。这被称为 根提交.)

要进行 new 提交,Git 将索引打包到快照中,将其与您的姓名和电子邮件地址等一起保存,and 包含提交 C 的哈希 ID,以使用新的哈希 ID 进行新提交。我们将使用 D 而不是新的哈希 ID,因为我们不知道新的哈希 ID 是什么:

A <-B <-C <-D

注意 D 如何指向 C。现在 D 存在,Git 更改 存储在名称 master 下的哈希 ID,改为存储 D 的哈希 ID C 个。存储在 HEAD 中的名称本身并没有改变:它仍然是 master。所以现在我们有了这个:

A <-B <-C <-D   <-- master (HEAD)

你可以从这个图表中看出 Git 是如何工作的:给定一个名字,比如 master,Git 只需按照箭头找到 latest 提交。该提交有一个指向其早期或 parent 提交的向后箭头,它有另一个指向其自身 parent 的向后箭头,依此类推,贯穿其所有祖先回到根提交。

请注意,虽然 children 记住了他们的 parent,但 parent 提交不记得他们的 children。这是因为 任何提交的任何部分都不能更改: Git 字面上 不能 添加 children 到parent,它甚至都没有尝试。 Git 必须始终向后工作,从新到旧。 commit 的箭头都是自动向后的,所以一般我都不画:

A--B--C--D   <-- master (HEAD)

分布式存储库:git fetch 的作用

当我们使用 git fetch 时,我们有 两个不同的 Gits,它们不同但 相关 - 存储库。假设我们在两台不同的计算机上有两个 Git 存储库,它们都以相同的三个提交开始:

A--B--C

因为它们从完全相同的提交开始,所以这三个提交也具有相同的哈希 ID。这部分非常聪明,这就是散列 ID 是这样的原因:散列 ID 是 contents 的校验和2提交,以便任何两个完全相同的提交总是具有 相同 哈希 ID。

现在,,在你的 Git 和你的存储库中,添加了一个新的提交 D。与此同时,他们——无论他们是谁——可能已经添加了他们自己的新提交。我们将使用不同的字母,因为它们的提交必然具有不同的哈希值。我们还将主要从您(Harry)的角度来看待这个问题;我们称他们为 "Sally"。我们将在 您的 存储库的图片中再添加一件事:它现在看起来像这样:

A--B--C   <-- sally/master
       \
        D   <-- master (HEAD)

现在假设 Sally 进行了两次提交。在 her 存储库中,she 现在有这个:

A--B--C--E--F   <-- master (HEAD)

或者也许(如果她从你那里获取,但还没有 运行 git fetch):

A--B--C   <-- harry/master
       \
        E--F   <-- master (HEAD)

运行git fetch,你把你的Git连接到莎莉的Git,问她有没有自提交 C 以来添加到 her master 的任何新提交。她做到了——她有她的新提交 EF。因此,您的 Git 从她那里获得了这些提交,以及完成这些提交的快照所需的一切。您的 Git 然后将这些提交添加到 您的 存储库,因此您现在拥有:

        E--F   <-- sally/master
       /
A--B--C
       \
        D   <-- master (HEAD)

如您所见,git fetch 为您所做的是收集她所有的 new 提交并将它们添加到您的存储库.

为了记住master在哪里,既然你已经和她Git交谈过,你的Git副本 的主人到你的 sally/master。你自己的master,还有你自己的HEAD,一点也不要变。只有这些 "memory of another Git repository" 名称,Git 调用 remote-tracking b运行ch 名称,更改。


2这个散列是一个加密散列,部分原因是它很难被愚弄 Git,部分原因是加密散列自然表现良好 Git的目的。当前的散列使用 SHA-1, 安全的,但已经遇到 brute-force 攻击,现在被放弃用于加密。 Git 可能会转移到 SHA2-256 或 SHA3-256 或其他一些更大的哈希值。会有一个t运行sition period,会有一些不愉快。 :-)


你现在应该合并或变基——git reset 通常是错误的

请注意,从 Sally 获取后,您的 存储库, 您的存储库,其中包含来自你们俩。 Sally 仍然没有你的新提交 D.

即使不是 "Sally",您的另一个 Git 被称为 origin,这仍然是正确的。现在您同时拥有 masterorigin/master,您必须做一些事情来将您的新提交 D 与他们最新的提交 F:

连接起来
A--B--C--D   <-- master (HEAD)
       \
        E--F   <-- origin/master

(出于 graph-drawing 的原因,我将 D 移到了顶部,但这与之前的图表相同,

您在这里的主要两个选择是使用 git mergegit rebase。 (还有其他方法可以做到这一点,但要学习的是这两种方法。)

Merge 实际上更简单,因为 git rebase 做的事情涉及合并的动词形式,to merggit merge 所做的是 运行 合并的动词形式,然后将结果作为 new 提交提交,称为 merge 提交 或简称 "a merge",这是合并的名词形式。我们可以这样绘制新的合并提交 G

A--B--C--D---G   <-- master (HEAD)
       \    /
        E--F   <-- origin/master

与常规提交不同,合并提交两个 parents.3 它连接回用于进行合并的两个早期提交。这使得将你的新提交 G 推送到 origin 成为可能:G 带走了你的 D,但也连接回他们的 F,所以他们的Git 这个新更新没问题。

此合并与合并两个 b运行ches 得到的合并相同。事实上,您 确实 合并了两个 b运行 分支:您将 master 与 Sally 的(或 origin 的)合并 master.

使用git rebase通常很容易,但它的作用比较复杂。而不是合并你的提交D和他们的提交F来创建一个新的合并提交Ggit rebase 所做的是 copy 您的每个提交,以便新的 copies,它们是新的和不同的提交,在您的 upstream.

上的最新提交

在这里,您的上游是 origin/master,而您拥有而他们没有的提交只是您的一个提交 D。所以 git rebase 制作了 Dcopy,我称之为 D',将副本放在他们的提交 F 之后,这样D' 的 parent 是 F。中间图如下所示:5

A--B--C--D   <-- master
       \
        E--F   <-- origin/master
            \
             D'   <-- HEAD

复制过程使用与 git merge 相同的合并代码来执行动词形式,合并,提交 D 的更改。 4 然而,一旦复制完成,变基代码发现没有更多的提交要复制,所以它然后 更改 你的 master b运行ch 指向最终复制的提交 D':

A--B--C--D   [abandoned]
       \
        E--F   <-- origin/master
            \
             D'   <-- master (HEAD)

这放弃了原来的提交 D.6 这意味着我们也可以停止绘制它,所以现在我们得到:

A--B--C--E--F   <-- origin/master
             \
              D'   <-- master (HEAD)

现在很容易 git push 您的新提交 D' 回到 origin


3在Git(但不是Mercurial)中,合并提交可以有两个以上的parent。重复合并做不了的事,主要是炫耀一下。 :-)

4从技术上讲,至少在这种情况下,合并基础提交是提交 C 并且两个提示提交是 DF,所以在这种情况下,它实际上是完全一样的。如果你 rebase 不止一次提交,它会变得有点复杂,但原则上它仍然很简单。

5这种中间状态,其中 HEADmaster 分离,通常是不可见的。只有在 verb-form-of-merge 期间出现问题时您才会看到它,因此 Git 停止并且必须从您那里获得帮助才能完成合并操作。但是,当 确实 发生时——当在变基期间存在合并冲突时——知道 Git 处于此 "detached HEAD" 状态很重要,但只要rebase 自己完成,你不必太关心这个。

6原始提交链通过Git的reflogs和名称[=120=暂时保留]. ORIG_HEAD 值被下一个生成 "big change" 的操作覆盖,并且 reflog 条目最终会过期,通常是在该条目的 30 天后。之后,git gc 将真正删除原始提交链。


git pull 命令只是 运行s git fetch 然后是第二个命令

请注意,在 git fetch 之后,您通常必须 运行 第二个 Git 命令,git mergegit rebase

如果您事先知道您肯定会立即使用这两个命令之一,您可以使用 git pull,其中 运行s git fetch 然后 运行s 这两个命令之一。您可以通过设置 pull.rebase 或提供 --rebase 作为 command-line 选项来选择 second 命令到 运行。

但是,在您非常熟悉 git mergegit rebase 的工作原理之前,我建议 不要 使用 git pull,因为有时 git mergegit rebase 无法自行完成。在这种情况下,您必须知道如何处理这种失败。您 必须 知道您实际 运行 哪个命令。如果您自己 运行 命令,您将知道您 运行 哪个命令,以及必要时到哪里寻求帮助。如果你运行git pull,你可能连第二条命令都不知道运行!

除此之外,有时您可能希望在 运行 第二个命令之前 查找 git fetch 带来了多少提交?进行合并与变基需要多少工作量?现在 merge 比 rebase 好,还是 rebase 比 merge 好?回答任何这些问题,你必须git fetch步骤与第二个命令分开。如果你使用 git pull,你 必须 提前决定要 运行 哪个命令,然后你甚至不知道要使用哪个命令。

简而言之,只有在熟悉了它的两个部分的方式后才使用 git pull——git fetch,并且你选择的第二个命令——真的有效。