编辑:这个我查过了Git中FETCH_HEAD是什么意思?在问问题之前。< br >抱歉,原来的问题不准确。
我的问题是,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
但是重置之后,我之前的提交(带有< code>message1)好像就没了。
这是正常的还是有什么问题
如何访问我的本地历史记录
它们已同步,但我的本地历史记录已消失。
老员工,算了:我最近一直在学习Git CLI。
有人告诉我输入“git fetch head
”来跟踪远程分支。
但是我想知道这是做什么的?这个命令会覆盖我的本地日志吗?
“git fetch
”和“git fetch head
”有什么区别?
Git fetch
本身非常简单。复杂的部分有之前和之后。
首先要知道的是Git存储提交。事实上,这就是Git的本质:它管理一个提交集合。这个集合很少收缩:在大多数情况下,使用这个提交集合所做的唯一事情就是添加新的提交。
每次提交都有几条信息,比如作者的姓名、电子邮件地址和时间戳。每次提交还会保存您指定的所有文件的完整快照:这些文件是在您运行< code>git commit时存储在您的索引(也称为临时区域)中的文件。从其他人那里获得的提交也是如此:当其他用户运行< code>git commit时,它们保存其他用户索引中的文件。
请注意,每个 Git 存储库只有一个索引,至少在最初是这样。此索引与一个工作树链接。在较新的 Git 版本中,您可以使用 git 工作树添加来添加其他工作树
;每个新工作树都带有一个新的索引/暂存区域。此索引的要点是充当中间文件持有者,位于“当前提交”(又名 HEAD)
和工作树之间。最初,HEAD
提交和索引通常匹配:它们包含所有已提交文件的相同版本。Git 将文件从 HEAD
复制到索引中,然后从索引复制到工作树中。
很容易看到工作树:它以普通格式保存文件,您可以使用计算机上的所有常规工具查看和编辑它们。如果您为web服务器编写Java或Python代码或HTML,则编译器、解释器或web服务器可以使用工作树文件。存储在索引中的文件和存储在每个Git提交中的文件都没有这种形式,编译器、解释器、web服务器等都无法使用。
关于提交要记住的另一件事是,一旦文件处于提交状态,就无法更改。任何提交的任何部分都无法更改。因此,提交是永久性的,或者至少是永久性的,除非它被删除(这可以做到,但很困难,而且通常是不可取的)。但是,索引和工作树中的内容可以随时修改。这就是它们存在的原因:索引几乎是一个“可修改的提交”(除了在你运行 git commit
之前它不会被保存),并且工作树将文件保留为计算机其余部分可以使用的形式。1
1不需要同时拥有索引和工作树。VCS可以将工作树视为“可修改的提交”。这就是Mercurial所做的;这就是为什么Mercurial不需要索引。这可以说是一个更好的设计,但它不是Git的工作方式,所以当使用Git时,你有一个索引。索引的存在是Git如此之快的一个重要原因:没有索引,Mercurial必须格外聪明,而且速度仍然不如Git。
当您通过运行 git 提交进行新提交时,Git
会获取索引内容,并在该点上对其中的所有内容进行永久快照。(这就是为什么你必须git add
文件:你从你更改它们的工作树中复制它们,回到你的索引中,以便它们准备好被“拍摄”为新快照。Git 还会收集一条提交消息,当然还会使用您的姓名、电子邮件地址和当前时间进行新的提交。
但是Git还在新提交中存储了当前提交的哈希ID。我们说新提交“指向”当前提交。例如,考虑这个简单的三提交存储库:
A <-B <-C <-- master (HEAD)
在这里,我们说分支名称 master
“指向”我标记为 C
的第三个提交,而不是使用 Git 难以理解的哈希 ID 之一,如 b06d364...
。(名称 HEAD
是指分支名称,主。
这就是 Git 如何将字符串 HEAD 转换为正确的哈希 ID:Git 跟随
HEAD
到 master,然后从 master
读取哈希 ID。但是,提交
C
本身“指向”(保留提交 B
)的哈希 ID;并提交 B
点以提交 A
。(由于提交 A
是有史以来的第一个提交,因此它没有指向更早的提交,因此它根本不指向任何地方,这使得它有点特别。这称为根提交。
为了进行新的提交,Git将索引打包到一个快照中,并将其与您的姓名和电子邮件地址等一起保存,并包含提交的哈希ID<code>C</code>,以使用新的哈希ID进行新提交。我们将使用<code>D</code<而不是新的哈希标识,因为我们不知道新的哈希标识符是什么:
A <-B <-C <-D
注意< code>D是如何指向< code>C的。现在< code>D已经存在,Git改变了存储在名称< code>master下的散列ID,以存储< code>D的散列ID,而不是< code>C的散列ID。存储在< code>HEAD中的名称本身根本没有改变:它仍然是< code>master。所以现在我们有了这个:
A <-B <-C <-D <-- master (HEAD)
从这个图中可以看到Git是如何工作的:给定一个名称,如<code>master</code>,Git只需按照箭头查找最新提交。该提交有一个指向其早期提交或父提交的向后箭头,该提交有另一个指向自己父提交的向下箭头,依此类推,所有祖先都指向根提交。
请注意,虽然孩子记得父母,但父母的promise并不记得孩子。这是因为任何提交的任何部分都不可能改变:Git确实无法将子项添加到父项,它甚至不会尝试。Git必须始终向后工作,从新到旧。提交箭头都会自动向后指向,因此通常我甚至不会绘制它们:
A--B--C--D <-- master (HEAD)
当我们使用git fetch
时,我们有两个不同的Git,具有不同但相关的存储库。假设我们在两台不同的计算机上有两个Git存储库,它们都以相同的三个提交开始:
A--B--C
因为它们开始时的提交完全相同,所以这三个提交也具有相同的哈希ID。这一部分非常聪明,这也是哈希ID是这样的原因:哈希ID是提交内容的校验和<sup>2
现在,您在 Git 和存储库中添加了一个新的提交 D
。同时,他们(无论他们是谁)可能已经添加了自己的新提交。我们将使用不同的字母,因为它们的提交必然具有不同的哈希值。我们还将主要从您(哈利)的角度看待这个问题;我们称他们为“莎莉”。我们将在您的存储库图片中添加另一件事:它现在看起来像这样:
A--B--C <-- sally/master
\
D <-- master (HEAD)
现在让我们假设Sally犯了两次错误。在她的存储库中,她现在有了:
A--B--C--E--F <-- master (HEAD)
或者(如果她从您那里获取,但尚未运行< code>git fetch):
A--B--C <-- harry/master
\
E--F <-- master (HEAD)
当你运行<code>git fetch</code>时,你将你的git连接到Sally的git,并询问她自提交<code>C</code〕以来是否有任何新的提交添加到她的<code>master</code}中。她有新的提交E
和F
。所以你的Git从她那里得到了这些提交,以及完成这些提交的快照所需的一切。然后,Git将这些提交添加到存储库中,这样您现在就有了:
E--F <-- sally/master
/
A--B--C
\
D <-- master (HEAD)
如您所见,< code>git fetch为您做的是收集她所有的新提交并将它们添加到您的存储库中。
为了记住她的master
在哪里,现在你已经和她的Git谈过了,你的Git将她的master复制到你的sally/master
。你自己的master
和你自己的HEAD
根本不会改变。只有这些“另一个Git存储库的内存”名称,Git称之为远程跟踪分支名称,会改变。
阿拉伯数字此哈希是加密哈希,部分原因是很难欺骗 Git,部分原因是加密哈希对于 Git 的目的自然表现良好。当前的哈希使用SHA-1,这是安全的,但已经看到了暴力攻击,现在被放弃用于加密。Git 可能会迁移到 SHA2-256 或 SHA3-256 或其他更大的哈希。会有一个过渡期,有一些不愉快。:-)
请注意,从 Sally 获取后,只有您的存储库才能完成你们双方的所有工作。莎莉仍然没有你的新提交 D
。
即使您的另一个Git被称为< code>origin,而不是“Sally”,这仍然是正确的。现在您已经有了< code>master和< code>origin/master,您必须做些什么来将新的提交< code>D与它们的最新提交< code>F连接起来:
A--B--C--D <-- master (HEAD)
\
E--F <-- origin/master
(出于绘制图表的原因,我将< code>D移动到了顶部,但这是与之前相同的图表,
这里的两个主要选择是使用gitmerge
或Gitrebase
。(有其他方法可以做到这一点,但这是需要学习的两种方法。)
合并实际上更简单,因为 git rebase
会做一些涉及动词形式的合并,合并。git merge
所做的是运行合并的动词形式,然后将结果提交为称为合并提交或简称为“合并”的新提交,这是合并的名词形式。我们可以这样绘制新的合并提交 G
:
A--B--C--D---G <-- master (HEAD)
\ /
E--F <-- origin/master
与常规提交不同,合并提交有两个父级。3 它连接回用于进行合并的两个早期提交。这使得可以将您的新提交 G 推送到源
:G
会带走您的
D
,但也连接回他们的 F
,因此他们的 Git 可以接受此新更新。
这种合并是通过合并两个分支得到的相同类型的合并。事实上,您确实在这里合并了两个分支:您将master
与Sally的(或origin
的)master
合并。
使用 git rebase
通常很容易,但它的作用更复杂。git rebase
所做的不是将您的提交 D
与其提交 F
合并以进行新的合并提交 G 来制作新的合并提交 G
,而是复制您的每个提交,以便新副本(新的和不同的提交)出现在上游的最新提交之后。
在这里,您的上游是<code>origin/master</code>,而您所拥有的它们没有的提交只是您的一次提交<code>D。因此,gitrebase
创建了一个D
的副本,我将其称为D‘
,将副本放在提交F
之后,这样,D‘
的父级就是F
。中间图如下所示:<sup>5</sup>
A--B--C--D <-- master
\
E--F <-- origin/master
\
D' <-- HEAD
复制过程使用的合并代码与 git merge
用于执行动词形式相同的合并代码,以合并来自提交 D
的更改。4 但是,一旦复制完成,rebase 代码就会看到没有更多的提交要复制,因此它会更改您的主
分支以指向最终复制的提交 D'
:
A--B--C--D [abandoned]
\
E--F <-- origin/master
\
D' <-- master (HEAD)
这将放弃最初的提交。< sup>6这意味着我们也可以停止绘制它,所以现在我们得到:
A--B--C--E--F <-- origin/master
\
D' <-- master (HEAD)
现在很容易将您的新提交< code>D'推回< code >原点。
3在Git(但不是Mercurial)中,合并提交可以有两个以上的父级。这不会做重复合并所不能做的任何事情,所以主要是为了炫耀。:-)
4技术上,至少在这种情况下,合并基础提交是提交C
,两个提示提交是D
和F
。如果您重新设置多个提交的基础,它会变得稍微复杂一些,但原则上它仍然很简单。
5这种中间状态,其中HEAD
与主节点
分离,通常是不可见的。只有在动词形式的合并过程中出现问题时,您才会看到它,以便 Git 停止并且必须从您那里获得帮助才能完成合并操作。但是,当这种情况确实发生时(当变基期间发生合并冲突时),重要的是要知道 Git 处于这种“分离的 HEAD”状态,但只要变基自行完成,你就不必太在意这一点。
6原始提交链通过 Git 的 reflogs 和名称 ORIG_HEAD
暂时保留。ORIG_HEAD
值被进行“重大更改”的下一个操作覆盖,并且 reflog 条目最终过期,通常在此条目的 30 天后过期。之后,git gc
将真正删除原始提交链。
请注意,在< code>git fetch之后,您通常需要运行第二个git命令,或者是< code>git merge或者是< code>git rebase。
如果你事先知道你会立即使用这两个命令之一,你可以使用 git pull
,它运行 git fetch
,然后运行这两个命令之一。您可以通过设置 pull.rebase
或提供 --rebase
作为命令行选项来选择要运行的第二个命令。
但是,在您非常熟悉< code>git merge和< code>git rebase的工作方式之前,我建议不要使用< code>git pull,因为有时< code>git merge和< code>git rebase无法独立完成。在这种情况下,你必须知道如何处理这种失败。您必须知道您实际运行了哪个命令。如果您自己运行该命令,您将知道您运行了哪个命令,以及在必要时到哪里寻求帮助。如果您运行< code>git pull,您可能甚至不知道您运行了第二个命令!
除此之外,有时您可能需要在运行第二个命令之前进行查看。gitfetch
带来了多少次提交?合并和重新基础需要做多少工作?现在是合并比重基更好,还是重基比合并更好?要回答这些问题,必须将gitfetch
步骤与第二个命令分开。如果使用gitpull
,则必须事先决定要运行哪个命令,然后才能知道要使用哪个命令。
简而言之,只有在您熟悉 git
pull 的两个部分(git fetch 和您选择的第二个命令)真正工作的方式之后,才使用 git
pull。
您不必进行两次单独的提交,并且< code>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 2.6开始,我会使用配置:
git config pull.rebase true
git config rebase.autoStash true
然后一个简单的< code>git pull会在< code>origin/master之上自动重放您的本地提交。然后就可以< code>git push了。