在阅读有关git reset的文档/教程时,我遇到了一个小冲突:例如,对于git reset --mixed
,文档说:
重置的下一个动作是使用
HEAD
现在指向的任何快照的内容更新索引
导致我发生冲突的原因是我期望清除索引,而不是更新索引。是否使用快照HEAD
现在指向的索引清除或更新了索引?
答案 0 :(得分:2)
索引总是更新的。索引保存您打算进行的下一次提交,因此它永远不会为空。 (什么,从来没有?嗯,几乎从来没有:它在您刚刚创建的 new 存储库中为空,没有文件,并且如果您现在运行git commit
则什么也不提交。它也是空的如果您git rm
一切。)
您在这里的困惑几乎可以肯定与the comment PetSerAl made有关。经常被告知或显示给Git的那些新手,或者至少导致他们相信提交和/或Git的索引包含更改,但这是错误的!一旦摆脱了这种错误的信念,Git的一些奥秘就变得更加有意义。 (不是Git的 all 对任何人都有意义,甚至我也没有。因此,grok Git花很长时间也不必担心。)
在Git中, commit 包含所有文件的完整快照。它还包含一些元数据-有关提交本身的信息有关,例如您的姓名,电子邮件地址和时间戳。元数据中包含提交的 parent 提交的哈希ID,或者对于合并提交,它是多个父级的复数形式,并且通过 comparing 提交给其父级的Git显示您的更改。每个提交都有自己唯一的哈希ID,例如8858448bb49332d353febc078ce4a3abcc962efe
(这是Git的Git存储库中的提交ID)。该提交是快照,但是该提交有一个父级(在本例中为67f673aa4a...
),因此Git可以通过提取两个 来显示您8858448bb4...
>较早的67f673aa4a
和 8858448bb4
,然后将两者进行比较。 git show
命令就是这样做的,因此您看到的是8858448bb4
中的更改,而不是8858448bb4
中的
(这就像告诉您今天的温度比昨天的温度高或低5,风或多或少,而不是用一堆数字来表示天气。数据库存储绝对值,但是大多数情况下我们想知道它是否更好
您可以看到Git的提交有多种方式,当然也可以像我上面那样通过其哈希ID对其进行命名。您可以直接看到工作树(Git可以在其中查看和编辑文件):它们以正常的日常形式存在于您的计算机上。但是您不能很好地查看索引。这是一种看不见的东西。这是一个问题,因为它也很关键。
大多数版本控制系统根本没有索引,或者如果它们有类似的索引,则将其保持隐藏得很好,以至于您无需了解它。但是Git做了 force 这样奇怪的事情,您要了解Git的索引,同时还要将其隐藏一些。
如果您真的想立即查看索引中的文件列表,则可以使用git ls-files
:
$ git ls-files | head
.clang-format
.editorconfig
.gitattributes
.github/CONTRIBUTING.md
.github/PULL_REQUEST_TEMPLATE.md
.gitignore
.gitmodules
.mailmap
.travis.yml
.tsan-suppressions
$ git ls-files | wc -l
3454
在此Git的Git存储库中,索引中几乎有3500个文件。有很多文件!这是为什么 Git基本上将其隐藏:里面的东西太多了,无法理解。
但这也是为什么 Git通过将承诺与父母进行比较来向我们展示承诺。显示8858448bb4
的全部内容会太多,因此git show 8858448bb4
向我们展示了8858448bb4
中发生了什么变化,而不是其父项。 Git与索引保持一致,向我们展示了我们发生了更改,而不是倾倒整个内容。
我认为,这就是使人们认为Git正在存储更改的原因。 Git 显示变化,因此Git必须存储它们……但是事实并非如此! Git存储整个快照。每当您要求Git向您展示一些东西时,Git 都会更改。
考虑到这一点,让我们看看如何查看索引。
我们现在知道每次提交都是完整的快照。如果Git在每次提交时都为每个文件都制作了一个新副本,则存储库将变得非常大。因此,它不会这样做,并且 way 的一部分没有做到这一点非常简单。虽然每个提交 是完整的快照,但是每个提交 inside 中的文件都是完全100%只读的。他们都不可以永远更改。这意味着每个提交都可以与一些较早的提交共享其某些或所有文件!
Git只需要确保每次运行git commit
时,它将永久冻结所有文件内容,或者永久冻结(如果不是永久的话),至少要保持此新提交的时间继续存在。因此,每次提交中的文件都被冻结。它们也被压缩到一种特殊的仅Git格式(对于文本文件确实很有效,但对于诸如图像这样的二进制文件通常效果不佳)。这种压缩需要时间,有时会花费很多时间,但是会使存储库变小。
很明显,冻结的仅Git的文件仅对Git本身有用,因此我们需要从 current 提交中取出,解冻,解压缩并使其有用的每个文件的副本。这些有用的副本进入工作所在的工作树。
其他版本控制系统也做同样的事情。在假设的XYZ版本控制系统中,运行xyz checkout commit
,它将提交从深冻仓库中复制出来,解冻,解压缩,然后将其存储在工作树中。您做了一些工作,最终运行了xyz commit
。现在,它会扫描整个工作树,重新压缩每个文件,将其冻结,然后检查是否已在仓库中获得该冻结版本,或者也需要将该文件放入其中。在您喝咖啡或其他任何东西时,每个步骤都需要花费几秒钟或几分钟的时间。
Git的工作及其索引非常聪明:该索引是一个临时区域,介于深度冻结仓库(充满提交的存储库)和有用形式(已解冻)之间文件在您的工作树中)。最初,它包含处于深度冻结状态的 same 文件。它们已解冻(某种),但仍处于特殊的仅Git形式,并且已与工作树中已完全解冻的解压缩版本配对。
当您更改工作树中的文件,或添加和/或删除文件时,索引副本将与工作树不同步。现在,Git可以将索引副本比较与工作树副本,并告诉您已更改但尚未上演的内容。
一旦有了所需的文件,就可以运行git add file
。此随即重新压缩文件为特殊的仅Git格式,并将该副本放入索引中。现在,索引副本(它是一个完整的副本,只是压缩了)与 work-tree 副本匹配,但与 committed 副本不同。
您随时可以让Git将每个文件的 committed (HEAD
)副本与 index 副本进行比较:
git diff --cached
对于相同的文件,Git什么也没说。对于不同的文件,Git会列出文件并显示差异。
类似地,您可以随时让Git将每个文件的 index 副本与 work-tree 副本进行比较:
git diff
对于相同的文件,Git什么也没说。对于不同的文件,Git会列出文件并显示差异。
(注意:添加--name-status
会使git diff
显示文件名,如果文件名被修改,则以M
为前缀。如果Git使用A
一个新添加的文件,D
代表一个已删除的文件,依此类推。只需将其从索引中完全删除,即可将该文件在索引中删除。 (如果它在索引中,但不在HEAD
中)。
git status
命令使用--name-status
限制器运行这两个比较。对于HEAD
和索引之间不同的文件,这些文件被暂存为提交。对于索引和工作树之间不同的文件,它们未上演提交。
图片上:
HEAD index work-tree
---------- ---------- ----------
README.txt README.txt README.txt
main.py main.py main.py
HEAD
副本已冻结,因为它处于提交状态。索引和工作树副本可以更改,但最初,所有三个匹配。您更改工作树副本,并使用git add
将其复制回索引 中,然后对其进行压缩和匹配(如果“ en-Git-ing”是一个单词,并非如此)。如果您不想更改索引,请使用git reset
(带有默认的--mixed
操作,或它在任何单个文件上的工作方式)将冻结的文件复制回索引。
git commit
与xyz commit
相比如此之快的原因当您运行git commit
时,Git已经以正确的格式包含了所有将要提交到新提交中的文件。它不必重新压缩所有工作树文件,并查看它们是否与冻结的提交版本匹配。 index 已准备就绪:只需冻结索引副本即可,如果与上一次提交相同,请 share 与文件以前的提交。
此外,由于索引“知道”哪些文件与工作树匹配,哪些不匹配, 1 ,并且还具有有关存储库中内容的额外信息,因此,{{1} }也更快。假设您在git checkout
上约有3500个文件,并且在master
上还有其他分支,其中约有3300个文件完全相同。两次提交之间大约有200个文件不同(也许是一些新文件或删除了文件)。 Git可以使用 index 来了解在工作树中可能需要触摸的内容,而完全避免触摸那些约3300个文件。
因此,Git扫描并可能触摸200个文件,而不是XYZ系统扫描并可能触摸3500个文件,从而节省了94%的工作。
1 这通常需要扫描工作树。该索引会保留(缓存)数据副本(关于工作树),以便加快工作速度。这就是为什么有时将索引称为 cache 的原因。其他VCS(例如Mercurial)具有工作树缓存(Mercurial将此称为 dirstate ),但与Git的索引不同,它已正确隐藏:您不必了解它。