考虑一下git reset(在它的三种模式中,软、混合和硬,而不是其他根本不属于这三种模式之一的其他东西)的作用。你跑:
git reset --<mode> <commit-specifier>
它做了以下三件事,或者可能是 2 或 1:
(--soft, --mixed, --hard) 它改变了存储在HEAD 中的当前分支名称,以指向给定的提交。使用--soft,它现在停止了。
(--mixed 和 --hard) 它将现有的索引内容替换为您在步骤 1 中选择的 commit 的内容。使用 --mixed(或默认值),它现在停止了。
(仅限--hard)它将工作树内容替换为在步骤 2 中更新的文件。
现在,您可以,如果您选择,在步骤 1 中选择 当前提交 作为目标,例如运行 git reset HEAD。如果您这样做,步骤 1 中更新的当前提交就是当前提交。也就是说,第 1 步没有实际改变。
如果您运行 git reset --soft HEAD,则第 1 步不会进行任何更改,然后 git reset 会退出。所以这绝对没有任何作用。
如果你运行 git reset 而不使用 --soft,但是 - 根本没有参数,默认为 --mixed,或者使用 --hard - 它会继续第 2 步:将索引的内容替换为一直冻结到当前提交中。实际上,这会取消您之前所做的任何 git add。
如果您的目标是之后再次重新执行所有 git add,那么这没有用。但如果你的目标是不重新做所有这些git adds,它可以。例如,考虑一下这个例子——它本身并不是非常有用,但只是为了说明:
git checkout somebranch
<edit for a while, test, `git add .`>
# think: wait, I don't want to commit everything
git reset # index now matches HEAD
<edit README file to announce that the NEXT commit changes a lot of things>
git add README
git commit # make a commit in which everything is the same except README
git add .
git commit # make final commit in which everything is changed
为了理解这一切,请记住,每个文件在任何时候都有三个副本:
当前提交中的每个文件(README.md、main.py 等,无论您拥有什么文件)都有一个只读的、永久冻结的副本时间>。您可以随时使用git checkout 或git reset 更改哪个提交是当前提交,但提交本身一直被冻结:它的任何文件都不会改变,它的提交信息、作者等也不会。
在 Git 的索引中有一个文件的冻结格式副本。1您可以随意更改:您可以覆盖该文件的副本具有另一个文件的副本或同一文件的另一个版本的文件。您甚至可以使用git rm 从索引中完全删除该文件。索引副本处于冻结格式,准备好进入提交,但它本身不是提交,因此它可以更改。
最后,有一个可用的文件版本。它不是某种特殊的仅 Git 格式,您的计算机程序的 rest 实际上可以使用这个版本的文件。该版本位于您的工作树中。
文件的索引副本匹配至少一个其他副本(HEAD 副本、工作树副本或两者兼有)是非常典型的,但实际上,您可以让所有三个文件都不同。为此,只需:
- 编辑工作树副本(务必保存以便
git add 可以看到);
- 使用
git add 将工作树副本复制到索引;和
- 再编辑工作树副本(并确保再次保存)。
现在文件都为提交暂存,这意味着HEAD和索引副本不同,并且没有为提交暂存,这意味着索引和工作-树副本不同。
1从技术上讲,索引中的内容只是模式、文件名和对内部 Git blob 对象的引用。 blob 对象表示文件内容 的冻结格式副本。 Reset (git reset) 将模式、名称和哈希 ID 复制到索引中。添加 (git add) 通过创建一个新的 blob 对象或查找已经存在的 blob 对象来压缩(准备冻结)工作树文件,该对象稍后将重新扩展到工作树文件。