Git add 和 git commit 混淆

11

我基本上了解git addgit commit的区别:前者表示“我想要将这个文件添加到我的下一个快照中”,后者表示“进行拍摄快照操作”。

然而,当我运行git add file1命令后,从工作目录中将file1移除,然后再运行git commit命令时,它仍然能正常工作。以某种方式,在添加文件的同时已经进行了快照操作,而不是在提交时才进行。我的理解正确吗?

3个回答

8

git commit通过查看索引(您已添加文件的位置)而不是查看工作树(您继续修改内容,包括添加或删除文件)来获取快照。

  • 参考索引中您已添加的文件
  • 而不是查看工作树(您继续修改内容,包括添加或删除文件)

请参阅"Git中HEAD、工作树和索引的区别是什么?"

在您的情况下,在删除文件后(但将其添加到索引之后),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,然后提交(commit)。
提交(commit)会将索引(index)中的所有内容都提交上去。

1
工作树是否等同于工作目录? - LivBanana
1
@Giant8 是的,这些术语指的是同一个概念。 - VonC
@Giant8 我经常使用它。当你进行大量修改时,最不想做的事情就是提交一个巨大的提交。你只添加一个小而连贯的子集,进行第一次提交,然后逐步添加其余部分,一路上进行小的提交。如果你在同一个文件中进行了多个修改,甚至可以添加文件的部分内容。请参见“交互式暂存”:https://git-scm.com/book/en/v2/Git-Tools-Interactive-Staging - VonC
想象一下,commit 命令可以同时完成实际的提交和添加操作。我们称之为虚拟提交。你仍然可以使用虚拟提交逐步完成这项工作。 - LivBanana
1
@Giant8 我已经编辑了我的答案来回应你的评论。"little by little" 的意思是先将那个 "little" 添加到索引中,然后再提交。 - VonC
显示剩余2条评论

5

实际上,你所知道的是缺少一些东西的。

当你添加文件时,你实际上有两个副本。第一个是工作树副本,它是正常的文件系统副本,你可以用普通文本编辑器等工具查看和编辑。

然而,此外你还有一个副本在索引中。 git add 将文件及其内容从工作树复制到索引中。这是创建该特定文件的实际快照的地方。

当你随后执行 git commit 命令时,索引中存储的内容会被保存到一个提交中。此时工作树中有什么或没有什么(即硬盘上的文件)并不重要,只有索引才是最重要的。

这就是为什么你会发现文件仍在被添加的原因。文件被复制到了索引中,即使你随后将其从磁盘中删除,git commit 仍使用索引作为提交的来源。

独立的索引组成了下一次提交的内容,这意味着你可以决定下一次提交包含哪些内容,而不仅仅是“我目前硬盘上的所有东西”。好的 Git 工具甚至可以让你将文件的部分更改复制到索引中,这样如果你对一个文件进行了 2 次或多次更改,你可以决定将所有更改都包含在下一次提交中,还是只包含其中的一部分。


工作树是工作目录的另一个名称,我说得对吗? - LivBanana
是的,尽管我认为已经确立的git术语是“工作树”。但基本上它就是您在磁盘上的文件和文件夹,是正常操作系统工作流程的一部分,就好像您完全忽略了整个git部分所拥有的内容。 “我相信”意味着我不是100%确定。 - Lasse V. Karlsen
@ Giant8 不是,恰恰相反。您可能会提交而没有添加所有更改的文件,因为您实际上进行了两个或多个单独的更改。例如,您可能已经重构了一个类以更好地分离其中的职责,并在其他地方添加了对从数据库读取数据的支持。这两件事可能作为两个提交具有适当的注释,而不是将它们合并在一起。因此,在提交之前,您可能只想添加一些文件,然后在之后添加其余文件,然后如此往复。 - Lasse V. Karlsen
假设提交(commit)既完成实际提交(commit)的工作,也完成添加(add)的工作。我们可以称之为虚拟提交(imaginary commit)。您仍然可以使用虚拟提交(imaginary commit)进行“逐步”工作。 - LivBanana
1
如果您的虚构提交只是提交了当前工作文件夹的状态,那么您只需要将更改分成较小的部分并在其间进行提交。您可以使用 git commit -a ... 命令,它会在实际提交之前自动添加所有已更改的文件,从本质上将 git addgit commit 结合为一个命令。 - Lasse V. Karlsen
显示剩余2条评论

2

你说得非常正确。不仅如此,文件在你添加它时保持原样。如果您之后更改它并提交更改,它将保存为您要求添加时的状态,并非当前状态。对于已修改的文件也是如此(以防您认为它只适用于新文件)。如果您提交了一个文件并对其进行修改,只有当您再次添加该文件时,Git才会在以下版本中将其持久化为新内容。


这还允许一种工作流程。您可以“添加”您认为已准备好的内容...然后您就可以修改文件以查看是否可以改进它们。如果您确实改进了它们,那么您再次添加...如果没有,那么无需做其他任何事情,只需提交(这将使用您之前添加的内容)。 - eftshift0
1
我的建议是:给自己一些时间来广泛使用git,这样你就能看到编码会遇到的所有不同疯狂情况,并了解git如何应对它们。只要给足够的时间,一切都会变得清晰明了。 - eftshift0
在“添加”过程中是否创建了SHA-1? - LivBanana
1
所有放入git数据库的对象都会获得一个ID(目前使用SHA-1,但git将逐渐转向SHA-256...此过程已经开始)。当您添加文件时,内容将放入对象数据库中,并使用ID指向它,文件还将添加到索引中(可能在树对象下,因此还将创建一些树对象,每个对象都有自己的ID)。当您“提交”时,将创建一个新的修订对象,并在那一刻获得其ID。 - eftshift0
这很棒:https://git-scm.com/book/en/v2/Git-Internals-Git-Objects - eftshift0
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接