Git commit提交已暂存和未暂存的文件

时间:2018-12-04 15:17:09

标签: git

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

3 个答案:

答案 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

现在foobar都已提交

$ 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实际上如何提交索引中的 而不是工作中的内容-树。

Git程序员使用Git设想您的方式

索引是一件相当复杂的事情,但其中的可以归结为:索引包含将要放入您的 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.txtmain.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 索引中,以便它们再次匹配。

让我们再次使用两个文件的示例,分别使用--onlygit 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进行第二次提交。