这可能是一个菜鸟问题。
假设我有一个Git存储库,通过使用git add,它在暂存区中已经有一些文件。然后我做一个git reset --soft @~
我很高兴看到我上次提交的一些文件现在已放入暂存区。
但是如何?我检查.git
文件夹。唯一改变的是当前分支的引用。和一个我认为不相关的“ ORIG_HEAD”。最可疑的索引文件根本没有被触及。还有谁能告诉我如何查看内容?
那么git怎么做呢?谢谢。
以其最简单的形式,1 git reset
做两件事:
要了解它的工作方式,原因以及工作原理,至少在相对较高的水平上,您需要知道提交的工作方式以及索引的工作方式。无论如何,这些都是紧密联系在一起的。
首先,提交只是类型为“ commit”的存储库对象,该对象具有数据,提交消息和一些其他信息(树,父项,作者和提交者):
$ git cat-file -p 5f95c9f850b19b368c43ae399cc831b17a26a5ac
tree 972825cf23ba10bc49e81289f628e06ad44044ff
parent 9c8ce7397bac108f83d77dfd96786edb28937511
author Junio C Hamano <[email protected]> 1392406504 -0800
committer Junio C Hamano <[email protected]> 1392406504 -0800
Git 1.9.0
Signed-off-by: Junio C Hamano <[email protected]>
该提交是git来源的一部分(这是git版本1.9.0的提交)。与所有存储库对象一样,其名称为40个十六进制字符的SHA-1值。
提交的工作目录由决定tree
,它是另一个git对象,因此具有另一个SHA-1名称。来自的输出git cat-file -p 972825cf23ba10bc49e81289f628e06ad44044ff
太长,无法完全包含,但其开始于:
100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f .gitattributes
100644 blob b5f9defed37c43b2c6075d7065c8cbae2b1797e1 .gitignore
100644 blob 11057cbcdf4c9f814189bdbf0a17980825da194c .mailmap
100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42 COPYING
040000 tree 47fca99809b19aeac94aed024d64e6e6d759207d Documentation
100755 blob 2b97352dd3b113b46bbd53248315ab91f0a9356b GIT-VERSION-GEN
这些blob
条目是构成git源的所有文件(以及每个子目录;每个子目录tree
都有blob
s)。每个文件blob
都有一个基于文件内容的唯一SHA-1 ID。在tree
保持文件的“模式”(其实只是它的列表x
位这些模式都是100644
和100755
)和文件名与资源库中的BLOB对象的SHA-1名一起。(如040000
上面所见,其他模式跟踪子树,符号链接和子模块。只有blob限制为100644
和100755
。)
每个git信息库对象都是只读的。ID为的提交5f95c9f...
将永远不会更改。它始终tree
将ID作为其(单个)972825c...
。其ID536e555...
始终是该文件的特定版本的文件COPYING
。如果文件已更新,则会输入具有不同的SHA-1的不同的Blob。
Git的“索引”(也称为“暂存区”,有时也称为“缓存”)是一个文档记录较差的文件,从本质上讲,它表示“下一次提交将执行什么操作”。
与存储库对象不同,索引是完全可写的。为了使“下一次提交”有所不同,git会在索引中添加或删除条目。例如,要更新名为的文件COPYING
,您将在运行后运行它git add COPYING
。这将获取文件的新内容,COPYING
并将其复制到存储库中(它们最终将在其中永久存在),2为结果计算SHA-1“真名”。然后,这个新的SHA-1进入索引(连同模式和名称,COPYING
基本上是提交所需要的所有内容)。
因为索引已经准备好了所有东西,所以很容易进行新的提交。所有正确的blob
s已经在存储库中。Git只需要将索引变成一些tree
对象,将它们写入存储库,获得最新顶层的最终SHA-1tree
并编写一个新commit
对象。新提交将具有以下属性:
tree
是根据索引写入的内容parent
就是HEAD
现在的状态(或多或少-在进行合并提交时有些与多位父母纠缠不清)author
和committer
这些日期是从当前时间采取和你的git的配置user.name
和user.email
,或从参数(--author
)或者环境变量,如果这些被设置为覆盖东西-m
参数进行编辑的内容。因此git编写了该提交,从而生成了一个新的唯一的SHA-1。然后,它将SHA-1本身写入某处。
HEAD
如您所说,如果您在“分支上master
”,则git status
意味着该文件.git/HEAD
包含文字字符串ref: refs/heads/master
。这就是git所谓的“间接引用”:只是说“去查找另一个引用,这里是名称”的引用。通常,您在某个分支上,并且HEAD
是对该分支的间接引用。
分支本身可以用几种不同的方式存储,但是最简单的是另一个文件.git
,在本例中为file .git/refs/heads/master
。如果该文件存在并且您已阅读,则该文件将包含SHA-1之类的5f95c9f850b19b368c43ae399cc831b17a26a5ac
。这就是当前的提交,也是git知道您“在”哪个提交的方式,就像ref: refs/heads/master
git知道您在branch上的方式一样master
。
为了进行新的提交,git如上所述编写了提交,这产生了一个新的唯一SHA-1。然后,由于您在branch上master
,因此git只需将新的commit-ID写入.git/refs/heads/master
,现在您就在新的commit上,这是branch的技巧master
。
你也可以有一个“分离的头”,其中,尽管听起来像来自法国大革命,只是表示事情HEAD
是不是间接引用。而是HEAD
包含原始SHA-1。在这种情况下,要进行新的提交,git会像以前一样进行提交,但不是更新.git/refs/heads/master
,而是将新的提交ID直接写入HEAD
。
因此,考虑到所有这些,让我们具体看一下是什么git reset
。
如果您进行--soft
重置,则git会使索引完全保持不变。这意味着它仅更新当前分支。
为了更新当前分支,git做与进行新提交时相同的操作:它查找HEAD
间接指向哪个分支,并将新的SHA-1写入该引用中。如果HEAD
指向master
,则只需将一个新的SHA-1写入.git/refs/heads/master
。
git编写的SHA-1是您在命令行上提供的SHA-1:
git reset --soft @~ # @~ means @~1, which means HEAD~1, aka HEAD^
您可以通过运行查看SHA-1的内容git rev-parse
(对于HEAD
-relative ref,当然必须在更改之前执行此操作):reset
HEAD
$ git rev-parse @~
9c8ce7397bac108f83d77dfd96786edb28937511
如果您告诉git reset
您使用--mixed
,它还会更新索引。它放入索引的东西来自它将写入分支的SHA-1提交:
$ git reset --mixed HEAD -- COPYING
在这里,通过告诉它将更改HEAD
为HEAD
,您将得到重置,以使分支与原来的位置完全没有距离,因此该分支毕竟不会得到更新。但是-- COPYING
说“COPYING
从目标修订版中提取文件的SHA-1 HEAD
,然后将SHA-1放入文件的索引中” COPYING
。因此,这意味着下一次提交将不会更改文件COPYING,因为我们已经将旧的SHA-1放回了索引中。
如果您告诉git reset
您使用--hard
,它还会更新工作目录(它已经在更新分支和索引)。通过从存储库中获取实际文件内容(从唯一的blob SHA-1查找它们)并覆盖工作目录版本来实现此目的。如果您尚未git add
-ed和git commit
-ed这些工作目录版本,则意味着更改已消失。(如果这样做了 git add
,则它们在存储库中,但是如果您没有这样做,则git commit
它们有资格进行垃圾回收-请参见脚注。)
既然您使用过--soft
,就抑制了对索引的更改,因此唯一git reset
可以做的就是更改分支提示文件的内容.git/refs/heads/master
。
1git reset
曾经只有这三种操作模式。现在,它具有--merge
和--keep
,加上的功能--patch
比简单的情况还要多。这有点像关于西班牙宗教裁判所的Monty Python片段:“我们的三种模式分别是软,混合,硬和合并。...四种!我们的四种模式是软,混合,硬,合并和保留...”
2存储库中的对象“永远存在”,但有一个非常大的例外:未引用的对象(git fsck
显示为dangling
)是垃圾回收的候选对象。未引用的Blob,提交等都是完全正常的。它们位于占用的磁盘空间周围(通常很少:对象是压缩存储的),以便您可以恢复事物,以便稍后在git认为清理是个好主意时可以一次将它们收集和丢弃。
当某个外部标签(分支名称,标签等)HEAD
直接或间接指向对象时,这些对象将被“引用”(并因此永远存在)。分支名称指向该分支上最尖端的提交。该提交指向其树,该树指向任何子树和blob,因此所有这些树将永远存在。并且该提交指向其父提交,因此这些父始终存在。每个父母的承诺依次指向其自己的父母,而这些父母也将永远存在。
当您将分支标签从其移开时,提交将变为未引用:
A <- B <- C <-- HEAD=master
这里master
(我们的当前分支)点C
,C
到B
和B
到A
。但是,如果我们:
$ git reset --hard HEAD^
我们master
指出了B
,指出了A
。提交C
现在是未引用的:它已被废弃,最终将连同其树,所有子树和Blob一起被垃圾回收。发生类似的事件,例如git commit --amend
使用进行软复位和新提交,进行D
指向的新提交B
,并master
指向D
:
A - B - D <-- HEAD=master
\
C [abandoned]
该rebase
操作将复制并放弃整个提交序列,从而生成大量用于垃圾回收的候选对象。这就是为什么悬挂物体正常的原因。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句