我无意中将一堆文本文件添加到了我的git repo中,并试图将它们取消暂存(在提交之前):
git reset dir/*.txt
当运行该命令时,它会显示以下内容:
unstaged changes after reset:
dir2/file.h
dir4/file2.cc
...
这些文件与重置通配符无关。 据我所知,这些文件仍被标记为已修改并准备提交,看起来是完好无损的。Git试图告诉我什么?
我无意中将一堆文本文件添加到了我的git repo中,并试图将它们取消暂存(在提交之前):
git reset dir/*.txt
unstaged changes after reset:
dir2/file.h
dir4/file2.cc
...
这些文件与重置通配符无关。 据我所知,这些文件仍被标记为已修改并准备提交,看起来是完好无损的。Git试图告诉我什么?
README.md
或其他文件,是当前提交版本,只读的,在您选择为当前提交的任何提交中,并且只有 Git 可以看到和读取它,以及您的工作树版本,它实际上不在 Git 中。Git 已将其提取到一个工作区,您现在正在使用它,但它不在存储库中。它从存储库复制出来了;自那时以来,它可能已更改或未更改。README.md
和有用的版本之间插入第三个副本。这第三个副本位于 Git 称之为索引、暂存区或者罕见情况下称为缓存的东西中。这三个名称都是指同一物体。.git/index
。相当古老的git apply
命令有两个单独的标志,--index
和 --cached
,它们执行不同的操作。但是 git rm --cached
的真正含义是从索引中移除;例如,在这种情况下,“索引”和“缓存”这两个词确实是同义词。
path/to/file.ext
,以及一堆内部信息,其中一些可以使用git ls-files --stage
查看。(尝试一下,但要注意它会不间断地输出大量内容。)git checkout
某个特定的提交时——例如分支feature
上的最新或tip提交——Git将使用来自该提交的文件填充其索引,并在你的工作树中填充这些文件。结果是,所有三个活动副本匹配。只读的提交副本与可替换的索引副本匹配。索引副本与提交副本和你的工作树副本都匹配。git add
。这让Git读取你的工作树副本,压缩它,将其与所有存储的文件去重复,并更新其索引副本。现在Git的索引副本与你的工作树副本相匹配。git add
,然后再次修改该文件。现在,时光冻结的提交副本与索引副本不同,后者是你先前添加过的副本,而你的工作副本仍然与它不同,因为你又没有运行git add
。
git status
通过执行两个差异来工作git status
命令时,它首先会输出一些人们认为有用的总体信息,例如当前分支名称4,此分支与其他分支或远程跟踪名称之间的差距等。然后它会进入到文件。HEAD
的某个位置。每个工作树都有一个HEAD
和一个索引;主要的HEAD
和索引通常是.git/HEAD
和.git/index
,而任何添加的工作树都会获得一对新的HEAD
和索引。您不应该需要知道这一点,但有时只需查看.git/HEAD
(它目前只是一个普通文本文件)即可了解情况。但是,所有这些都可能在未来发生变化:例如,HEAD
曾经是符号链接。
git reset
git reset
命令比较复杂5。我们将忽略大部分复杂性,只关注您运行以下命令时所使用的git reset
:git reset dir/*.txt
git reset
现在可以通过新的(自Git 2.23以来)git restore
实现。它将文件从当前提交复制到Git的索引中,而不触及您的工作树。6
当您这样做时,您需要找到一个文件名列表。这有点复杂,因为“某物”可能是您的shell,也可能是Git,如果是Git,则Git找到的文件名集可能与Shell找到的集合不同。为简单起见,让我们假设双方发现的文件集是相同的:Shell使用dir/*.txt
发现的所有文件是Git在当前提交中与dir/*.txt
匹配发现的文件的同一组文件。因此,Git将所有这些文件从当前提交复制到Git的索引中。git add
- 这都会覆盖更新的索引副本,并将其设置回与提交的副本相匹配。因此,这会撤销您先前运行的任何与dir/*.txt
匹配的git add
操作。git reset
会执行部分的git status
。也就是说,它比较Git索引中每个文件与您工作树中相同文件的副本。对于那些不同的文件,Git将它们列出为“未暂存”。Git没有触及Git索引中的dir2/file.h
,但是它在您的工作树中已经与Git索引中的版本不同。因此,git reset
在此处的输出包括它。其他列出的文件也是如此。
完成上述步骤后,git reset
现在执行部分的git status
。也就是说,它比较Git索引中每个文件与您工作树中相同文件的副本。对于那些不同的文件,Git将它们列出为“未暂存”。Git没有触及Git索引中的dir2/file.h
,但是它在您的工作树中已经与Git索引中的版本不同。因此,git reset
在此处的输出包括它。其他列出的文件也是如此。
git checkout
拆分为git switch
和git restore
一样进行拆分。当然,为了兼容性,仍然会有git reset
,就像在分裂之后Git 2.23及更高版本仍然有git checkout
一样。
6git restore
实际上更强大,因为您可以选择任何一个提交,而不仅仅是当前的提交,并且您可以选择将其复制到Git的索引、您的工作树或两者兼而有之。因此,如果git reset
按照我在脚注5中考虑的方式进行拆分,其中一条命令将是git restore
。
成功修改一些文件的索引副本后,git reset
会运行部分的git status
。它会比较文件的索引副本和工作树副本。它不仅仅比较单个重新设置的文件,而是比较所有的索引条目。由于索引列出了下一次提交中的每个文件,所以这可能是很多文件。
请注意,当您修改文件然后运行git add
时,您正在安排每个文件的中间副本,在Git的“暂存区”(索引)中,以便为下一次提交做好一切准备。这就是我们称之为“暂存区”的原因:我们把特定的文件副本放在“舞台”上,然后拍摄一张称为提交的照片快照。该提交是从“舞台”上的内容构建的,这可能与您正在使用的内容不同。
其他版本控制系统不会这样做:它们没有单独的暂存区,当您进行新的提交时,它们会对您的工作树进行快照。这有其优点和缺点,并需要一个文件列表(通常称为“清单”),因为工作树往往有许多不应提交的文件。Git使用其索引来实现此目的:如果您没有将文件复制到索引中,即未将其放在稍后的快照中的“舞台”上,它将不会出现在快照中。
但是,由于索引具有每个文件的完整副本,因此存在三个副本,这导致了这些奇怪的情况。由于您无法“看到”索引副本,因此需要一些东西(通常是git status
),以比较索引副本和工作树副本,并让您知道是否要更新拟议的下一次提交。我们通过它的影子“看到”索引:当它匹配当前提交和/或工作树时,我们什么也看不到。较少的影子使那些存在的影子更加突出,因此这相当有效。但是这很棘手!
git status
说了什么? - Lasse V. Karlsen