TL;DR:当一个文件已暂存和未暂存更改时,一次提交将提交两个版本,以及对该文件的最新更改。为什么?我认为提交只会提交暂存版本,如下所示: https: //stackoverflow.com/a/19892657/279516。
假设我们的工作目录中有一个文件。(它之前已提交给回购协议。)
$ ls
foo.txt
Run Code Online (Sandbox Code Playgroud)
该文件的内容当前只有一个字符(数字 1):
$ cat foo.txt
1
Run Code Online (Sandbox Code Playgroud)
让我们将文件的内容更改为“12”。现在我们有这个:
$ cat foo.txt
12
Run Code Online (Sandbox Code Playgroud)
我们的工作目录显示了更改(为简洁起见,删除了说明性 git 输出):
$ git status
modified: foo.txt
Run Code Online (Sandbox Code Playgroud)
现在 agit add将该文件添加到暂存索引中。
$ git add foo.txt
Run Code Online (Sandbox Code Playgroud)
您在这里看不到它,但文件名现在是绿色的,表明它已经上演:
$ git status
modified: foo.txt
Run Code Online (Sandbox Code Playgroud)
此时,我们可以提交该文件,它将成为我们本地存储库的一部分。不过,让我们foo.txt先改变一下,看看会发生什么。
$ cat foo.txt
123
Run Code Online (Sandbox Code Playgroud)
如果我们检查,git status我们会看到foo.txt 的两个版本:
$ git status
On branch master
Changes to be committed:
modified: foo.txt
Changes not staged for commit:
modified: foo.txt
Run Code Online (Sandbox Code Playgroud)
第一个foo.txt是绿色的。第二个是红色的。第一个的内容是“12”。第二个是“123”。如果我们现在承诺会发生什么?foo.txt仅应提交分阶段的内容。因此,foo.txt以“12”为主体的内容将位于我们本地的存储库中。另一个版本foo.txt仍然在我们的工作目录中。
提交foo.txt到我们的本地存储库,就像我们添加它时一样:
$ git commit -m "Added 2 to foo." foo.txt
Run Code Online (Sandbox Code Playgroud)
然而,事实并非如此。我们的工作目录现在没有变化。两个版本均已提交。为什么?
$ git status
On branch master
nothing to commit, working tree clean
Run Code Online (Sandbox Code Playgroud)
除了现有的(正确的)答案之外,值得注意的是,在使用 时git commit [flags] file1 file2 ... fileN,您可以放入标志--only或--include:
--only是默认值,这意味着忽略我到目前为止所进行的操作,只需添加这些文件即可提交。
--include意味着我到目前为止所进行的操作,也添加这些文件。
这很简单,但有些微妙的错误,因为--only还必须采取提交后的操作。
正确理解需要知道 Git 的索引是什么,以及如何git commit真正提交索引中的内容,而不是工作树中的内容。
索引是一件相当复杂的事情,但大部分都可以归结为:索引保存将进入下一次提交的文件集。 也就是说,如果您git commit现在运行\xe2\x80\x94而不列出任何文件\xe2\x80\x94,则新提交将是当前索引中所有文件的快照,保存索引中的内容现在。
这是什么紧接着:
\n\n$ git clone <some-url>\n$ cd <repository>\n$ git checkout <branch>\nRun Code Online (Sandbox Code Playgroud)\n\n您的索引中有所有相同的文件,内容相同与工作树中看到的
\n\n也就是说,每次提交都是该提交中所有文件的完整快照,以一种特殊的、压缩的、仅限 Git 的形式永久冻结。这意味着您始终可以通过让 Git 提取和解压缩它们,以原始形式取回所有这些文件,这就是它的作用git checkout:它找到分支上的最新提交,并提取和解压缩文件。(这过于简单化了:git checkout确实很奇特。但这是它所做的最基本的事情。)
有用的格式文件位于您的工作树中中,您可以在其中查看它们并对其进行处理。这很好,因为提交的内容被冻结并且仅限 Git,这显然会是一个问题。:-)
\n\n但是,要进行新的提交,Git 所做的不是重新压缩所有工作树文件\xe2\x80\x94,这在很多情况下会花费很长时间\xe2\x80\x94,而是保存(未冻结但仍然压缩的和仅限 Git 的文件)在这个东西中,分别称为索引、暂存区域或缓存。因此,如果您的工作树中有README.txt和,那么您当前的提交中也有它们\xe2\x80\x94(仅 Git 形式\xe2\x80\x94)(它们被冻结的位置) ,并且main.py索引(它们在其中解冻,但仍然仅限 Git)。
运行git add README.txt告诉 Git 用工作树副本覆盖索引副本:获取普通格式文件并将其重新压缩为仅 Git 格式,将索引README.txt中的 替换为工作树中的文件。它还没有冻结\xe2\x80\x94你可以在提交之前再次覆盖它\xe2\x80\x94但它已经准备好了。
在不指定任何文件的情况下运行git commit,告诉 Git:立即打包索引中的所有内容并从中进行新的提交。 这个过程非常快,因为文件已经采用正确的形式:Git 只需将它们冻结到新的提交中。(当然,除非你让索引中的文件与当前的文件不同),否则就没有意义。)
请注意,从索引git commit进行新的提交后,您通常会回到索引和当前提交匹配的正常情况。如果您git add更改了所有文件,则每个文件\xe2\x80\x94 、索引和工作树\xe2\x80\x94的所有三个副本都匹配。HEAD
--only和--includegit commit 使用列出的某些文件运行有点不同,这就是--onlyvs 的--include用武之地。如果您使用--include,Git 基本上会对git add列出的文件执行操作。它只是git add <those files> && git commit或多或少的简写。
但是,如果您使用--only\xe2\x80\x94 或不指定,这意味着 --only\xe2\x80\x94 Git 所做的就是将常规索引推开,而是从冻结中的任何内容创建一个新的临时索引犯罪。对于这个新的临时索引,Git 将git add列出您列出的每个文件。然后 Git 将从另一个提交新的提交。但现在有一个问题,因为 Git 现在需要返回到正常索引,这就是它变得有点棘手的地方。
从临时索引进行新的提交后,Git 现在需要以相同的方式更新真实索引。本质上,从临时索引提交后,Git 会将您列出的同一组文件重新添加到真实索引中。索引中,以便它们再次匹配。
\n\n让我们再次使用两个文件的示例,使用README.txt和main.py。这次,我们在每个文件后面添加一个版本号。这不是名字的一部分的一部分,只是为了帮助我们记住:
HEAD index work-tree\n------------- ------------- -------------\nREADME.txt(1) README.txt(1) README.txt(1)\nmain.py(1) main.py(1) main.py(1)\nRun Code Online (Sandbox Code Playgroud)\n\n他们一开始每个文件的所有三个版本都是相同的。(请注意,您无法更改HEAD副本。您只能进行新的提交,然后该提交将成为副本HEAD,因为HEAD命名了新提交。)
现在您可以编辑工作树中的两个文件:
\n\n HEAD index work-tree\n------------- ------------- -------------\nREADME.txt(1) README.txt(1) README.txt(2)\nmain.py(1) main.py(1) main.py(2)\nRun Code Online (Sandbox Code Playgroud)\n\n假设你现在做git add main.py工作树版本复制到索引中:
HEAD index work-tree\n------------- ------------- -------------\nREADME.txt(1) README.txt(1) README.txt(2)\nmain.py(1) main.py(2) main.py(2)\nRun Code Online (Sandbox Code Playgroud)\n\ngit commit如果你现在运行一个普通的,新的HEAD将有旧的README.txt,因为索引有旧的README.txt。但相反,让我们运行git commit --only README.txt。这使得临时索引,这样我们就有:
HEAD temp-index work-tree\n------------- ------------- -------------\nREADME.txt(1) README.txt(2) README.txt(2)\nmain.py(1) main.py(1) main.py(2)\nRun Code Online (Sandbox Code Playgroud)\n\n接下来,这会从临时索引进行新的提交:
\n\n HEAD temp-index work-tree\n------------- ------------- -------------\nREADME.txt(2) README.txt(2) README.txt(2)\nmain.py(1) main.py(1) main.py(2)\nRun Code Online (Sandbox Code Playgroud)\n\n与此同时,真实指数尚未改变。向上滚动一下并查看:main.py其中包含哪个版本?README.txt里面有哪个版本的?
如果 Git 现在只是切换回真实索引,同时保留刚刚所做的提交,那么您将得到以下结果:
\n\n HEAD ugly-index work-tree\n------------- ------------- -------------\nREADME.txt(2) README.txt(1) README.txt(2)\nmain.py(1) main.py(2) main.py(2)\nRun Code Online (Sandbox Code Playgroud)\n\n也就是说,您的工作树是所有最新文件。您的提交已更新README.txt。但这种丑陋的状态意味着下一次提交将使用旧的/错误的版本README.txt!这就是为什么 Git 现在重新添加README.txt到真实索引中,这样你就得到:
HEAD index work-tree\n------------- ------------- -------------\nREADME.txt(2) README.txt(2) README.txt(2)\nmain.py(1) main.py(2) main.py(2)\nRun Code Online (Sandbox Code Playgroud)\n\nmain.py现在您可以根据需要进行第二次提交并更新。
如果您只想提交暂存版本,请运行git commit而不指定任何文件。
例子:
$ echo 2 > foo
$ git add foo
$ echo 3 > foo
$ git commit -m haha
Run Code Online (Sandbox Code Playgroud)
现在,暂存版本已提交,未暂存的更改仍保留在您的工作目录中。这可以很容易地验证:
$ git show HEAD:foo
2
$ git diff
--- a/foo
+++ b/foo
@@ -1 +1 @@
-2
+3
Run Code Online (Sandbox Code Playgroud)
也许让我git commit用另一个例子来演示这种行为(使用文件):
执行这些操作:
$ git init
$ echo 1 > foo
$ echo 1 > bar
$ git add foo bar
$ git commit -m 1
Run Code Online (Sandbox Code Playgroud)
现在foo和bar都已承诺
$ echo 2 > foo
$ echo 2 > bar
Run Code Online (Sandbox Code Playgroud)
现在两者都已更改,让我们暂存foo并提交bar:
$ git add foo
$ git commit -m 2 bar
$ git status
Changes to be committed:
modified: foo
$ git diff --name-only HEAD~ HEAD
bar
Run Code Online (Sandbox Code Playgroud)
您会看到foo提交中没有更改,但保留了暂存状态。