我基本上了解git add
和git commit
的区别:前者表示“我想要将这个文件添加到我的下一个快照中”,后者表示“进行拍摄快照操作”。
然而,当我运行git add file1
命令后,从工作目录中将file1
移除,然后再运行git commit
命令时,它仍然能正常工作。以某种方式,在添加文件的同时已经进行了快照操作,而不是在提交时才进行。我的理解正确吗?
我基本上了解git add
和git commit
的区别:前者表示“我想要将这个文件添加到我的下一个快照中”,后者表示“进行拍摄快照操作”。
然而,当我运行git add file1
命令后,从工作目录中将file1
移除,然后再运行git commit
命令时,它仍然能正常工作。以某种方式,在添加文件的同时已经进行了快照操作,而不是在提交时才进行。我的理解正确吗?
git commit通过查看索引(您已添加文件的位置)而不是查看工作树(您继续修改内容,包括添加或删除文件)来获取快照。
在您的情况下,在删除文件后(但将其添加到索引之后),git status
会给出以下结果:
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: go.mod
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: go.mod
该文件已经:
git restore -- <myFile>
命令足以在本地恢复它。
工作树(或工作目录)是实际检出的文件树。
工作树通常包含HEAD提交树的内容,加上您尚未提交的任何本地更改。
这个想法是为了准备您的下一个提交,而不是盲目地将您当前的所有修改全部放入一个巨大的提交中。
最好进行小而连贯的提交,以获得逻辑历史记录,并使未来的git bisect
更容易。
您甚至可以部分暂存(添加到索引)一个文件的一部分(交互式暂存)。
OP补充道:
想象一下,提交既完成实际提交,还完成添加的工作。
我们称之为虚拟提交。
您仍然可以使用虚拟提交逐步完成这项工作。
首先:该命令(添加所有并提交)确实存在:
git commit -am "Let's add everything"
实际上,你所知道的是缺少一些东西的。
当你添加文件时,你实际上有两个副本。第一个是工作树副本,它是正常的文件系统副本,你可以用普通文本编辑器等工具查看和编辑。
然而,此外你还有一个副本在索引中。 git add
将文件及其内容从工作树复制到索引中。这是创建该特定文件的实际快照的地方。
当你随后执行 git commit
命令时,索引中存储的内容会被保存到一个提交中。此时工作树中有什么或没有什么(即硬盘上的文件)并不重要,只有索引才是最重要的。
这就是为什么你会发现文件仍在被添加的原因。文件被复制到了索引中,即使你随后将其从磁盘中删除,git commit
仍使用索引作为提交的来源。
独立的索引组成了下一次提交的内容,这意味着你可以决定下一次提交包含哪些内容,而不仅仅是“我目前硬盘上的所有东西”。好的 Git 工具甚至可以让你将文件的部分更改复制到索引中,这样如果你对一个文件进行了 2 次或多次更改,你可以决定将所有更改都包含在下一次提交中,还是只包含其中的一部分。
git commit -a ...
命令,它会在实际提交之前自动添加所有已更改的文件,从本质上将 git add
和 git commit
结合为一个命令。 - Lasse V. Karlsen你说得非常正确。不仅如此,文件在你添加它时保持原样。如果您之后更改它并提交更改,它将保存为您要求添加时的状态,并非当前状态。对于已修改的文件也是如此(以防您认为它只适用于新文件)。如果您提交了一个文件并对其进行修改,只有当您再次添加该文件时,Git才会在以下版本中将其持久化为新内容。