我认为该git checkout命令仅更新工作树中的文件。事实上,手册页上写着:
git-checkout - 切换分支或恢复工作树文件
但是,我刚刚运行了这个命令:
git checkout 11cb5b6 -- hello.txt
Run Code Online (Sandbox Code Playgroud)
而且,除了更新我的工作树副本之外,此命令还更新了我的索引。在命令之前,git status给了我一个干净的结果:
nothing to commit, working tree clean
Run Code Online (Sandbox Code Playgroud)
但紧接着checkout,它写道:
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.txt
Run Code Online (Sandbox Code Playgroud)
即工作树文件已更新并暂存。我缺少什么?
tor*_*rek 19
命令git checkout非常复杂。它是如此复杂,以至于有人最终从 Git 2.23 开始将其分成两个单独的命令:
git switch,执行“选择其他分支或提交已签出”操作;和git restore,它执行“更新索引和/或工作树中的一些文件”操作。这仍然没有提到几种额外的操作模式(git checkout -m例如),1但至少分离出了所有“恢复文件”选项,其中有很多。
您正在使用“恢复文件”模式git checkout,正如shrey deshwal 指出的那样,此操作将:
当使用git restore而不是 时git checkout,您-S可以使用(暂存区域) 和(工作树) 选项来控制更新哪些索引/暂存区域和工作树文件-W。这是不可能的git checkout:git checkout始终写入工作树,并且如果您指定提交或树对象作为要写入工作树的文件源,也会写入索引/暂存区域。
如果您有 Git 2.23 或更高版本,请使用git restore这样做:它的操作不那么混乱并且更直接。您可以为文件指定--source,或者让它默认使用索引:
git restore --source 11cb5b6 -- hello.txt\nRun Code Online (Sandbox Code Playgroud)\n这仅写入工作树。或者,添加-S和/或-W使用 和/或使用 写入暂存区域(索引)-S和/或工作树-W:
git restore --source 11cb5b6 -SW -- hello.txt\nRun Code Online (Sandbox Code Playgroud)\n这会写入暂存区和工作树(因为 和 都已-S给出-W)。
相比之下,git checkout -- file使源不那么明显(与 中的相同git restore,但不太明显),并且无法选择目标(总是-W,但-S如果源是提交或树,则添加)。该git restore命令还正确记录--overlayvs--no-overlay模式。此选项仅适用于“恢复文件”模式git checkout(现在已对此进行了记录,但尚不清楚它是否仅适用于此模式!)。
1选项:-mgit checkout
第二个操作有些危险:正如文档现在指出的那样,
\n\n\n当使用 切换分支时
\n--merge,分阶段的更改可能会丢失。
第一个操作会愉快地销毁您在文件的工作树副本中开始的任何合并工作。因此,git checkout -m这种方式始终是“危险的” git restore:它将在不询问的情况下消除未提交的工作。我有点希望这些没有留在git switch命令中,但它们确实如此。
Git存储库在很大程度上只是一个大型提交数据库。您对该存储库所做的就是添加更多提交。每个提交本身是:
\n已编号。每个提交都有一个大的、丑陋的、难以理解的哈希 ID号,例如e9e5ba39a78c8f5057262d49e261b42a8660d5b9(通常缩写为e9e5ba3)。这些看起来是随机的,但实际上它们完全是非随机的。
存储:每次提交都会存储两件事:
\n一次提交有每个文件的完整快照。提交不存储更改,因此当 Git 显示更改时,它实际上是在执行git diff两个快照。
提交还存储一些元数据或有关提交本身的信息。这包括提交作者的姓名和电子邮件地址(来自user.name和user.email)等内容。它包括一些日期和时间戳。它包括一条日志消息,该消息git log或git show将在任何差异之前显示。而且,对于 Git 的内部操作来说至关重要的是\xe2\x80\x94,尽管我们不会在这里介绍任何细节\xe2\x80\x94,每个提交都会存储先前提交哈希 ID 的列表。大多数提交只存储一个这样的哈希 ID,它是提交的“父级”。这就是 Git 查找先前提交的方式,以便它可以向您显示更改的内容。
提交中的所有这些内容都是完全、完全、100% 只读的:任何提交的任何部分一旦完成就不能更改。2 但这给我们带来了一个困境:如果任何提交的任何部分都不能更改,我们如何才能完成任何新工作?
\nGit 的答案与其他版本控制系统的答案相同:当然,提交永远是只读的,但您不会使用已提交的文件。我们将文件从提交复制到工作区。您处理/使用这些文件。该区域是您的工作树或工作树。复制出来的文件是普通的读/写文件,您的计算机可以对其进行普通工作。
\n到目前为止,这一切都不是特别奇怪或难以理解的。提交就像文件的存档,就像由其他文件组成的tar或rar文件,但具有特殊的 Gitty 功能,例如元数据和奇怪的随机数字。我们使用git checkout或git switch来选择一个:Git 提取文件,现在我们可以处理它们。
但这就是 Git 变得奇怪的地方。如果您使用过其他版本控制系统,您可能已经习惯了这个想法:您处理文件,然后告诉 VCS 提交它们,它就会提交。那很简单,所以 Git 不会这样做。
\n当 Git 构建新的提交时,Git根本不使用您的文件!相反,Git 使用秘密的额外副本。只是它实际上并不是秘密,而且通常也不是副本。它是什么,是隐藏的。每个文件的这个额外“副本”在 Git 中被称为三个不同的名称:
\n指数:一个毫无意义的术语。无意义有时是好的,因为这样就不会因为某些奇怪的技术原因而排除先入为主的观念。但这让人有点难以记住。
\n暂存区:这就是您使用索引的方式,因此它是有意义的。但这掩盖了确实重要的技术细节。您需要了解它们。
\n缓存:这是最糟糕的名称,因为它是 Git 本身有时使用索引的方式,而不是你使用它的方式,并且不涵盖 Git 使用索引的所有方式。这个术语基本上已经不存在了,除了它出现在像git rm --cached或 之类的标志中git diff --cached。
有时,该--cached标志具有--staged同义词:git diff --staged与 执行完全相同的操作git diff --cached。有时他们不会:完全git rm --staged拒绝--staged。奇怪的是,git restore只有. --staged完全摆脱--cached可能是一个好的方向;也许 Git 最终会这么做。但无论如何,您需要知道所有三个名称,因为“索引”出现在不同的地方。特别是,索引在冲突合并期间具有特殊作用,它确定文件是否被跟踪或未跟踪。我们不会在这里详细讨论这一点;我们只会讨论与进行新提交有关的索引。
当您运行 时git commit,Git 不会从工作树中读取以找出您更改了哪些文件,而是简单地将此时位于其索引中的文件打包起来。3 为了实现这一点,首字母git checkout或git switch步骤首先填写 Git 的索引。
这意味着,在您检查了一些提交以对其进行处理后,您将拥有每个文件的三个“副本”:
\n当 Git将文件永久存储在提交中时,它:
\n换句话说,这些文件是 Git 化的并放入数据库中,而不是作为常规文件保存。每当 Git 执行此操作时,它都会自动删除重复的内容。因此,即使每次提交都存储每个文件,当您有数百万次提交时,存储库也不会膨胀失控,因为许多提交都具有某些文件的相同副本,并且这些都是共享的。我们在提交内获得了文件的“副本”,而不是文件的副本:因此有引号。
\n索引以与提交相同的方式存储预 Git 化、预压缩和预去重复的“副本”。因此该git add命令:
如果该文件是重复的,git add则丢弃刚刚制作的副本:我们不需要该文件,我们在存储库中已经有一个文件了。Git 使用副本更新其索引,文件现在已准备好提交,所有内容都存储在 Git 的索引中。
如果该文件不是重复的,git add则获取现在准备好的压缩文件并准备将其放入数据库中,类似于临时添加。4 现在 Git 有了一个可以提交的副本,存储在其索引中。
所以:
\npath/to/file.ext,准备提交;file.ext文件夹中的(真实的、操作系统级别的)文件进行 Git 化topathgit addgit add根据需要更新索引“副本”,现在我们已经path/to/file在索引中,准备提交。因此,在 before 之前 git add,索引包含建议的下一次提交。 之后 git add,索引仍然包含建议的下一次提交。刚刚git add所做的是更新提议的下一次提交。
如果您添加一个全新的文件,则会发生相同的顺序:git add读取并压缩普通的操作系统级文件,确定内容是否重复\xe2\x80\x94也许您已经添加了world.txt其中也包含hello world,即hello.txt例如现有的\xe2\x80\x94 的副本,并git add更新了建议的下一次提交,以便新文件world.txt也列在那里。
在所有情况下,Git 始终在其索引中设置建议的下一次提交。5 运行git commit意味着使用现在建议的下一次提交,这就是为什么您可以部分暂存内容:您正在做的是将一些与工作树副本匹配的文件的副本添加到索引中,以及与工作树副本不匹配的其他文件。由于索引拥有自己的副本(或由于预重复数据删除而导致的“副本”),这意味着每个文件始终有三个“活动副本”:
有HEAD(或当前提交)副本。这是 Git 化的,无法更改,因为它是在提交中。
有索引副本。这是 Git 化的,但可以更改,因为它只是建议的提交,还不是真正的提交。
\n最后,还有工作树副本。这是您唯一可以看到并使用的。
\n您使用和修改工作树副本。您可以使用git add或git rm创建或删除索引副本,然后使用将建议的git commit下一次提交转变为实际提交。
2这意味着这git commit --amend是一个谎言。它不会修改提交,而是进行新的和改进的替换。旧的提交仍然存在!git rebase这也是事实。在 Git 中,看似会改变提交的事情实际上根本不会改变它们。你可以通过保存和比较这些哈希 ID\xe2\x80\x94 来判断,但人类通常只是在它们上面发出嘟嘟声,这是一件好事:它让我们用更好的提交替换坏的提交,而人类却没有注意到我们这样做了。
3该git commit命令提供了一个标志 ,-a这实际上意味着:首先运行git add -u,然后运行git commit。这有很多微妙之处,但这里要注意的关键是它运行git add -u。该-u选项只会更新跟踪的文件,因此您不能将其用于新文件。因此,Git 迫使您学习git add.
4事实上,Git 只是立即添加对象,然后在适当的情况下将其丢弃,但如果您愿意,可以将其视为“临时添加”。
\n5当您处于冲突合并中时,Git知道您处于冲突合并中,因为有些索引条目的“暂存编号”高于 staging-number-0。在这种模式下,Git 根本不会从索引进行新的提交,因此有人可以说,在这种模式下,索引不保存建议的下一次提交。
\n