2

这可能是一个菜鸟问题。

假设我有一个 Git 存储库,它已经使用 git add 在暂存区域中有一些文件。然后我做一个git reset --soft @~

我很高兴看到我上次提交的一些文件现在被放入暂存区。

但是怎么做?我检查.git文件夹。唯一改变的是当前分支的参考。还有一个我认为不相关的“ORIG_HEAD”。最可疑的索引文件根本没有被触及。还有谁能告诉我如何查看它的内容?

那么 git 怎么能做到这一点呢?谢谢。

4

2 回答 2

3

在最简单的形式中,1 git reset做了两件事:

  • 移动当前分支,和/或
  • 撤消索引中的内容

要了解它的工作原理和原因以及它的作用,您需要知道提交是如何工作的以及索引是如何工作的,至少在相对较高的层次上是这样。无论如何,这些都是紧密联系在一起的。

提交、树和 blob

首先,提交只是“提交”类型的存储库对象,它的数据、提交消息和一些其他信息(树、父级、作者和提交者):

$ git cat-file -p 5f95c9f850b19b368c43ae399cc831b17a26a5ac
tree 972825cf23ba10bc49e81289f628e06ad44044ff
parent 9c8ce7397bac108f83d77dfd96786edb28937511
author Junio C Hamano <gitster@pobox.com> 1392406504 -0800
committer Junio C Hamano <gitster@pobox.com> 1392406504 -0800

Git 1.9.0

Signed-off-by: Junio C Hamano <gitster@pobox.com>

此提交是 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

这些条目是构成 git 源blob的所有文件(以及每个 的子目录tree;那些有更多s)。blob每个blob都有一个唯一的 SHA-1 ID,基于文件的内容。tree保留文件的“模式”列表(实际上只是它的位x- 这些模式都是100644and 100755)和文件名以及存储库中 blob 对象的 SHA-1 名称。(其他模式,如040000上面所见,跟踪子树、符号链接和子模块。只有 blob 被限制为100644100755。)

每个 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-1 tree,然后编写一个新commit对象。新提交将具有以下属性:

  • tree是根据索引写入的任何内容
  • parentHEAD现在的任何东西(或多或少——在进行合并提交时,有一些与多个父母有关的摆弄)
  • authorand和这些committer日期取自当前时间和您的 git 配置user.nameand user.email,或者取自参数 ( --author) 或环境变量(如果它们设置为覆盖事物)
  • 该消息是您作为提交消息编辑或作为-m参数提供的任何内容。

所以 git 写了那个提交,它产生了一个新的、唯一的 SHA-1。然后它将 SHA-1 本身写入某处。

分支机构和HEAD

如果你“在分支上master”,就像git status说的那样,这意味着文件.git/HEAD包含文字 string ref: refs/heads/master。这就是 git 所说的“间接引用”:只是说“去寻找另一个引用,这是名称”的引用。通常你在某个分支上,并且HEAD是对该分支的间接引用。

分支本身可以以几种不同的方式存储,但最简单的是 中的另一个文件.git,在本例中为文件.git/refs/heads/master. 如果该文件存在并且您阅读了它,它将包含一个 SHA-1,例如5f95c9f850b19b368c43ae399cc831b17a26a5ac. 这是当前的提交,也是 git 如何知道你“在”哪个提交,就像ref: refs/heads/mastergit 如何知道你在 branch 上一样master

为了进行的提交,git 如上所述写入提交,这会产生一个新的唯一 SHA-1。然后,由于您在 branch 上master,git 只需将新的 commit-ID 写入.git/refs/heads/master,现在您在新的提交上,这是 branch 的提示master

你也可以有一个“分离的 HEAD”,尽管听起来像是来自法国大革命的东西,但这只是意味着它HEAD不是间接引用。相反,HEAD它包含一个原始 SHA-1。在这种情况下,为了进行新的提交,git 以与以前相同的方式进行提交,但它不是更新.git/refs/heads/master,而是将新的提交 ID 直接写入HEAD.

git 重置

因此,考虑到所有这些,让我们具体看看是什么git reset

如果您进行--soft重置,git 会完全保持索引不变。这意味着它只更新当前分支。

要更新当前分支,git 做的事情与进行新提交时相同:它找到HEAD间接指向的分支,并将新的 SHA-1 写入该引用。如果HEAD指向master,则只需将新的 SHA-1 写入.git/refs/heads/master

git 写入的 SHA-1 是您在命令行中提供的:

git reset --soft @~   # @~ means @~1, which means HEAD~1, aka HEAD^

您可以通过运行查看 SHA-1 的内容git rev-parse(对于HEAD-relative ref,您必须在更改之前执行此操作,当然):resetHEAD

$ git rev-parse @~
9c8ce7397bac108f83d77dfd96786edb28937511

如果你告诉git reset使用--mixed,它也会更新索引。它放入索引中的内容来自它将写入分支的提交 SHA-1:

$ git reset --mixed HEAD -- COPYING

在这里,通过告诉它将 更改HEADHEAD,您将重置为将分支移动到与以前的位置完全没有距离的地方,因此分支根本不会更新;但上面写着“从目标修订中-- COPYING提取文件的 SHA-1 ,并将该 SHA-1 放入文件的索引中。” 所以这意味着下一次提交不会对文件 COPYING 进行更改,因为我们已将旧的 SHA-1 放回索引中。COPYINGHEADCOPYING

如果您告诉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,比简单的情况做得更多。这有点像关于西班牙宗教裁判所的巨蟒短剧:“我们的三种模式是软、混合、硬和合并……四种! 我们的四种模式是软、混合、硬、合并和保持……”

2存储库中的对象“永远存在”,但有一个非常大的例外:未引用的对象(git fsck显示为dangling)是垃圾收集的候选对象。未引用的 blob、提交等是完全正常的。它们占用磁盘空间(通常很少:对象被压缩存储),以便您可以恢复事物,并且当 git 认为清理是个好主意时,它们可以被收集和丢弃。

HEAD当某些外部标签(分支名称、标签、 或其他)直接或间接指向对象时,对象就会被“引用”(因此永远存在) 。分支名称指向该分支上最尖端的提交。该提交指向它的树,它指向任何子树和 blob,因此所有这些都永远存在;并且该提交指向其父提交,因此这些父提交永远存在。每个父级提交点依次指向自己的父级,并且这些也永远存在。

当您将分支标签移离提交时,提交将变为未引用:

A <- B <- C   <-- HEAD=master

这里master(我们当前的分支)指向CCtoBBto A。但是如果我们:

$ git reset --hard HEAD^

我们master指向B,指向A。CommitC现在是未引用的:它已被放弃,最终将与它的树以及任何子树和 blob 一起被垃圾收集。类似的事件发生在,例如,git commit --amend执行软重置和新提交,进行指向 的新提交DBmaster指向D

A - B - D   <-- HEAD=master
      \
        C   [abandoned]

rebase操作复制然后放弃整个提交序列,生成大量候选对象用于垃圾收集。这就是为什么悬挂物体是正常的。

于 2014-04-10T04:04:56.807 回答
1

索引文件是纯元数据,显示已暂存的内容及其文件系统统计信息。当您执行 git checkout 时,索引是从提交的树中加载的——它具有所有这些文件的 SHA 以及写入工作树的文件系统大小/时间。

当您暂存 aka track aka add content 时,该内容将被放入 repo 并将其 SHA 写入索引文件。

git reset --soft不涉及任何内容,它只会更改HEAD指向的内容。因此git status,您所要做的就是向您展示索引与HEAD提交或工作树的不同之处。

于 2014-04-10T04:30:26.253 回答