git fetch到底做了什么?

Har*_*heF 12 git git-fetch

编辑:我已经检查过这个在Git中的FETCH_HEAD是什么意思? 在提出问题之前.
抱歉原始的不准确的问题.

我的问题是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
Run Code Online (Sandbox Code Playgroud)

但重置后,似乎我之前的提交(with message1)已经消失.

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

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

tor*_*rek 25

git fetch本身真的很简单.前后复杂的部分.

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

提交,索引和工作树

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

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

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

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


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


提交记住他们的父母; 新提交的是儿童

当您通过运行进行新的提交时git commit,Git会获取索引内容并为该点上的所有内容创建一个永久快照.(这就是你必须git add文件的原因:你从工作树中复制它们,你已经将它们更改回索引,这样它们就可以为新快照"拍照"了.)Git还收集提交消息,当然还会使用您的姓名和电子邮件地址以及当前时间来进行新的提交.

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

A <-B <-C   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

这里我们说分支名称 master "指向"我标记的第三个提交C,而不是使用Git难以理解的哈希ID之一b06d364....(这个名字HEAD指的是分行的名称,master这是Git的是如何打开的字符串.HEAD到正确的哈希ID:混帐遵循HEADmaster,然后读取散列ID出来的master.)这是承诺C本身"指向" -retains哈希ID B但是,提交; 并提交提交BA.(因为commit A是有史以来第一次提交,所以没有先前的提交指向它,因此它根本不指向任何地方,这使得它有点特殊.这称为根提交.)

为了进行新的提交,Git将索引打包成快照,用你的名字和电子邮件地址等保存它,包括提交的哈希ID C,以使用新的哈希ID进行新的提交.我们将使用D而不是新的哈希ID,因为我们不知道新的哈希ID是什么:

A <-B <-C <-D
Run Code Online (Sandbox Code Playgroud)

注意如何D指向C.现在D存在,Git 改变存储在名称下的哈希ID master,以存储D哈希ID而不是C's.存储在其中的名称HEAD根本不会改变:它仍然存在master.所以现在我们有了这个:

A <-B <-C <-D   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

你可以从这个图中看到Git是如何工作的:给定一个名字,比如master,Git只需按照箭头找到最新的提交.该提交有一个向后箭头到它的早期或提交,它有另一个向后箭头到它自己的父,依此类推,在其所有的祖先返回根提交.

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

A--B--C--D   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

分布式存储库:什么git fetch做的

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

A--B--C
Run Code Online (Sandbox Code Playgroud)

因为它们以完全相同的提交开始,所以这三个提交也具有相同的哈希ID.这部分是非常聪明,是哈希ID是现在这个样子的原因:哈希ID是校验2内容的提交,使得任何两个提交是完全相同总是有相同的哈希ID.

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

A--B--C   <-- sally/master
       \
        D   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

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

A--B--C--E--F   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

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

A--B--C   <-- harry/master
       \
        E--F   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

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

        E--F   <-- sally/master
       /
A--B--C
       \
        D   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,git fetch为您做的是收集所有提交并将其添加到您的存储库.

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


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


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

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

即使代替"Sally",你的其他Git也会被调用,这仍然是正确的origin.现在你有两个masterorigin/master,你必须做一些事情来连接新承诺D与他们的最新承诺F:

A--B--C--D   <-- master (HEAD)
       \
        E--F   <-- origin/master
Run Code Online (Sandbox Code Playgroud)

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

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

合并实际上更简单git rebase,涉及动词形式的合并,合并.什么git merge确实是运行合并的动词形式,然后提交的结果提交被称为合并提交或简单的"合并",这是合并的名词形式.我们可以这样绘制新的合并提交G:

A--B--C--D---G   <-- master (HEAD)
       \    /
        E--F   <-- origin/master
Run Code Online (Sandbox Code Playgroud)

与常规提交不同,合并提交两个父项.3 它连接回用于进行合并的两个早期提交.这使得你可以将你的新提交推G送到origin:G接受你的D,然后连接回他们的F,所以他们的Git可以使用这个新的更新.

此合并与合并两个分支所获得的合并类型相同.事实上,你确实在这里合并了两个分支:你将你master与Sally(或者origins)合并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
Run Code Online (Sandbox Code Playgroud)

复制过程使用相同的合并代码,git merge用于执行动词形式,合并来自提交的更改D.4 然而,一旦复制完成,rebase代码就会发现没有更多的提交需要复制,因此它会将您的master分支更改为指向最终复制的提交D':

A--B--C--D   [abandoned]
       \
        E--F   <-- origin/master
            \
             D'   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

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

A--B--C--E--F   <-- origin/master
             \
              D'   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

现在git push你的新提交很容易D'回复origin.


3在Git(但不是Mercurial)中,合并提交可以有两个以上的父级.这不会做任何你不能通过重复合并做的事情,所以它主要是为了炫耀.:-)

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

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

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


git pull命令刚刚运行git fetch,然后是第二个命令

请注意,之后git fetch,您通常必须运行第二个Git命令,git merge或者git rebase.

如果您事先知道您将立即使用这两个命令中的一个,您可以使用git pull,运行git 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,而你选择的第二个命令才真正起作用.

  • 之所以投赞成票,是因为详尽、准确、正确使用了脚注来提供更多细节,通常写得很好*而且*幽默。您考虑过从事技术写作吗?特别是 IMDB 中与提问者姓名相符的参考资料让我笑了。 (3认同)
  • 真的吗?*真的吗?*我的意思是,当然,努力+1,但在这种情况下,拉--rebase,解决冲突,推动并完成它;) (2认同)