git fetch到底做了什么?

问题描述 投票:10回答:2

编辑:我在问这个问题之前检查了这个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)已经消失。

这是正常的还是有什么不对? 我如何访问我的本地历史记录? 它们已经同步但我的当地历史已经消失。

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

git fetch
2个回答
21
投票

git fetch本身非常简单。前后复杂的部分。

这里要知道的第一件事是Git存储提交。事实上,这基本上就是Git的意义:它管理一系列提交。这个集合很少收缩:在大多数情况下,你对这个提交集合做的唯一事情就是添加新的提交。

Commits, the index, and the work-tree

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

请注意,每个Git存储库只有一个索引,至少最初。该索引与一个工作树链接。在较新的Git版本中,您可以使用git worktree add添加其他工作树;每个新的工作树都带有一个新的索引/临时区域。该索引的要点是充当中间文件持有者,位于“当前提交”(又名HEAD)和工作树之间。最初,HEAD提交和索引通常匹配:它们包含所有提交文件的相同版本。 Git将文件从HEAD复制到索引中,然后从索引复制到工作树中。

很容易看到工作树:它以普通格式存储您的文件,您可以使用计算机上的所有常规工具查看和编辑它们。如果为Web服务器编写Java或Python代码或HTML,则编译器或解释器或Web服务器可以使用工作树文件。存储在索引中并存储在每个Git提交中的文件没有此格式,编译器,解释器,Web服务器等也无法使用。

要记住提交的另一件事是,一旦文件处于提交状态,就无法更改。任何提交的任何部分都不能改变。因此,提交是永久性的 - 或者至少是永久性的,除非它被删除(这可以做但很难并且通常是不合需要的)。但是,索引和工作树中的内容可以随时修改。这就是它们存在的原因:索引几乎是一个“可修改的提交”(除非它在运行git commit之前不会保存),而工作树将文件保存为计算机其余部分可以使用的形式。


1没有必要同时拥有索引和工作树。 VCS可以将工作树视为“可修改的提交”。这就是Mercurial的作用;这就是Mercurial不需要索引的原因。这可以说是一个更好的设计 - 但它不是Git的工作方式,所以当使用Git时,你有一个索引。索引的存在是使Git如此之快的重要原因:没有它,Mercurial必须非常聪明,并且仍然没有Git那么快。


Commits remember their parent; new commits are children

当您通过运行git commit进行新的提交时,Git会获取索引内容,并在该点生成其中所有内容的永久快照。 (这就是为什么你必须git add文件:你从工作树中复制它们,你已经将它们更改回索引,以便它们可以为新快照“拍照”。)Git也收集提交消息,当然还会使用您的姓名和电子邮件地址以及当前时间来进行新的提交。

但是Git还在新提交中存储了当前提交的哈希ID。我们说新提交“指向”当前提交。例如,考虑这个简单的三提交存储库:

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

在这里我们说分支名称master“指向”第三个提交,我标记为C,而不是使用Git的难以理解的哈希ID之一,如b06d364...。 (名称HEAD指的是分支名称,master。这就是Git可以将字符串HEAD转换为正确的哈希ID:Git跟随HEADmaster,然后从master读取哈希ID。)它提交C本身“点但是“-retains-commit B的哈希ID;并提交B指向提交A。 (因为提交A是有史以来第一次提交,所以没有先前的提交指向它,因此它根本不指向任何地方,这使得它有点特殊。这称为根提交。)

为了进行新的提交,Git将索引打包成快照,用你的名字和电子邮件地址等保存它,并包含commit 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只需按照箭头找到最新的提交。该提交有一个向后箭头到它的早期或父提交,它有另一个向后箭头到它自己的父,依此类推,在其所有的祖先返回根提交。

请注意,虽然孩子们记得他们的父母,但父母的遗体却不记得他们的孩子。这是因为任何提交的任何部分都不能改变:Git字面上不能将子项添加到父项,它甚至都没有尝试。 Git必须始终向后工作,从较新到较旧。提交箭头全部自动指向后方,所以通常我甚至不绘制它们:

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

Distributed repositories: what git fetch does

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

A--B--C

因为它们以完全相同的提交开始,所以这三个提交也具有相同的哈希ID。这部分非常聪明,并且是散列ID的原因:散列ID是提交内容的校验和2,因此任何两个完全相同的提交始终具有相同的散列ID。

现在,您在Git和您的存储库中添加了一个新的提交D。与此同时,他们 - 无论他们是谁 - 可能已经添加了他们自己的新提交。我们将使用不同的字母,因为它们的提交必然会有不同的哈希值。我们也会从你的(哈利的)观点来看这个;我们称他们为"Sally"。我们将在您的存储库图片中再添加一个内容:它现在看起来像这样:

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

现在让我们假设Sally做了两次提交。在她的存储库中,她现在拥有:

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

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

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

当你运行git fetch时,你将你的Git连接到Sally的Git,并询问她自从提交master以来她是否有任何新的提交添加到她的C。她确实 - 她有新的承诺EF。所以你的Git会从她那里获得这些提交,以及完成这些提交的快照所需的一切。然后,您的Git会将这些提交添加到您的存储库中,以便您现在拥有:

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

正如您所看到的,git fetch为您做的是收集她的所有新提交并将它们添加到您的存储库中。

为了记住她的master在哪里,现在你已经和她的Git交谈了,你的Git将她的主人复制到你的sally/master。你自己的master和你自己的HEAD,根本不会改变。只有这些“另一个Git存储库的内存”名称(Git称之为远程跟踪分支名称)才会发生变化。


2这个哈希是一个加密哈希,部分原因是它很难欺骗Git,部分原因是加密哈希自然表现得很好用于Git的目的。当前的哈希使用SHA-1,这是安全的,但已经看到暴力攻击,现在被放弃用于加密。 Git可能会转向SHA2-256或SHA3-256或其他更大的哈希。会有一段不愉快的过渡期。 :-)


You should now merge or rebase—git reset is generally wrong

请注意,从Sally获取后,它是您的存储库,只有您的存储库,它具有您和您的所有工作。莎莉仍然没有你的新提交D

即使代替“莎莉”,你的另一个Git也被称为origin,这仍然是正确的。现在你有masterorigin/master,你必须做一些事情来连接你的新提交D与他们最新的提交F

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

(由于图形绘制的原因,我将D移到了顶部,但这与之前的图形相同,

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

合并实际上更简单,因为git rebase做了涉及动词形式的合并,合并的东西。 git merge所做的是运行合并的动词形式,然后将结果作为新的提交提交,称为合并提交或简称为“合并”,这是合并的名词形式。我们可以这样绘制新的合并提交G

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

与常规提交不同,合并提交有两个parent.3。它连接回用于进行合并的两个早期提交。这使得有可能将你的新提交G推送到originG带上你的D,但也连接回他们的F,所以他们的Git可以使用这个新的更新。

此合并与合并两个分支所获得的合并类型相同。事实上,你在这里合并了两个分支:你将你的master与莎莉(或origin的)master合并。

使用git rebase通常很容易,但它的作用更复杂。 D不是将你的提交F和它们的提交G合并成一个新的合并提交git rebase,而是复制你的每个提交,以便新的拷贝,即新的和不同的提交,在你上游的最新提交之后。

在这里,你的上游是origin/master,你拥有的那些他们没有的提交只是你的一次提交D。所以git rebase制作了D的副本,我将其称为D',在他们的提交F之后放置副本,以便D'的父母是F。中间图如下所示:5

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

复制过程使用git merge使用的相同合并代码来执行动词形式,合并来自commit D的更改。然而,一旦复制完成,rebase代码就会看到没有更多的提交要复制,所以它然后更改您的master分支指向最终复制的提交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)中,合并提交可以有两个以上的父级。这不会做任何你不能通过重复合并做的事情,所以它主要是为了炫耀。 :-)

4技术上,合并基础提交,至少在这种情况下,提交C和两个提示提交是DF,所以在这种情况下它实际上是完全相同的。如果你修改多个提交,它会变得有点复杂,但原则上它仍然是直截了当的。

5这个中间状态,HEADmaster分离,通常是看不见的。只有在动词形式的合并过程中出现问题时才会看到它,因此Git会停止并且必须得到您的帮助才能完成合并操作。当确实发生这种情况时 - 虽然在重新定位期间存在合并冲突 - 重要的是要知道Git处于这种“分离的HEAD”状态,但只要rebase自行完成,您就不必关心这么多。

6原始提交链通过Git的reflogs和名称ORIG_HEAD暂时保留。 ORIG_HEAD值被下一个进行“大变更”的操作覆盖,reflog条目最终到期,通常在此条目30天后。在那之后,git gc将真正删除原始的提交链。


The git pull command just runs git fetch and then a second command

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

如果您事先知道您将立即使用这两个命令中的一个,则可以使用运行git pullgit fetch然后运行这两个命令之一。您可以通过设置pull.rebase或提供--rebase作为命令行选项来选择要运行的第二个命令。

然而,在你非常熟悉git mergegit rebase如何工作之前,我建议不要使用git pull,因为有时git mergegit rebase无法自行完成。在这种情况下,您必须知道如何处理此故障。您必须知道实际运行的命令。如果您自己运行该命令,您将知道您运行的命令以及必要时寻求帮助的位置。如果你运行git pull,你甚至可能不知道你运行了哪个第二个命令!

除此之外,有时您可能希望在运行第二个命令之前查看。 git fetch带来了多少次提交?合并与rebase相比需要做多少工作?现在合并比rebase更好,还是rebase比合并更好?要回答上述任何问题,您必须将git fetch步骤与第二个命令分开。如果使用git pull,则必须事先确定要运行哪个命令,然后才能知道哪个命令要使用。

简而言之,只有在你熟悉它的两个部分-git pull之后才使用git fetch,以及你选择的第二个命令 - 真的有效。


4
投票

您不必执行两次单独的提交,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)

更简单,since Git 2.6,我会使用配置:

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

然后一个简单的git pull会自动重播你在origin/master上的本地提交。然后你可以git push

© www.soinside.com 2019 - 2024. All rights reserved.