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