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

Bob*_*orn 7 git

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)

tor*_*rek 5

除了现有的(正确的)答案之外,值得注意的是,在使用 时git commit [flags] file1 file2 ... fileN,您可以放入标志--only--include

\n\n
    \n
  • --only是默认值,这意味着忽略我到目前为止所进行的操作,只需添加这些文件即可提交

  • \n
  • --include意味着我到目前为止所进行的操作,也添加这些文件

  • \n
\n\n

这很简单,但有些微妙的错误,因为--only还必须采取提交后的操作。

\n\n

正确理解需要知道 Git 的索引是什么,以及如何git commit真正提交索引中的内容,而不是工作树中的内容。

\n\n

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

\n\n

索引是一件相当复杂的事情,但大部分都可以归结为:索引保存将进入下一次提交的文件集。 也就是说,如果您git commit现在运行\xe2\x80\x94而不列出任何文件\xe2\x80\x94,则提交将是当前索引中所有文件的快照,保存索引中的内容现在

\n\n

是什么紧接着:

\n\n
$ git clone <some-url>\n$ cd <repository>\n$ git checkout <branch>\n
Run Code Online (Sandbox Code Playgroud)\n\n

您的索引中有所有相同的文件,内容相同与工作树中看到的

\n\n

也就是说,每次提交都是该提交中所有文件的完整快照,以一种特殊的、压缩的、仅限 Git 的形式永久冻结。这意味着您始终可以通过让 Git 提取和解压缩它们,以原始形式取回所有这些文件,这就是它的作用git checkout:它找到分支上的最新提交,并提取和解压缩文件。(这过于简单化了:git checkout确实很奇特。但这是它所做的最基本的事情。)

\n\n

有用的格式文件位于您的工作树中中,您可以在其中查看它们并对其进行处理。这很好,因为提交的内容被冻结并且仅限 Git,这显然会是一个问题。:-)

\n\n

但是,要进行新的提交,Git 所做的不是重新压缩所有工作树文件\xe2\x80\x94,这在很多情况下会花费很长时间\xe2\x80\x94,而是保存未冻结但仍然压缩的和仅限 Git 的文件)在这个东西中,分别称为索引暂存区域缓存。因此,如果您的工作树中有README.txt和,那么您当前的提交中也有它们\xe2\x80\x94(仅 Git 形式\xe2\x80\x94)(它们被冻结的位置) ,并且main.py索引(它们在其中解冻,但仍然仅限 Git)。

\n\n

运行git add README.txt告诉 Git 用工作树副本覆盖索引副本:获取普通格式文件并将其重新压缩为仅 Git 格式,将索引README.txt中的 替换为工作树中的文件。它还没有冻结\xe2\x80\x94你可以在提交之前再次覆盖它\xe2\x80\x94但它已经准备好了

\n\n

在不指定任何文件的情况下运行git commit,告诉 Git:立即打包索引中的所有内容并从中进行新的提交。 这个过程非常快,因为文件已经采用正确的形式:Git 只需将它们冻结到新的提交中。(当然,除非你让索引中的文件与当前的文件不同),否则就没有意义。)

\n\n

请注意,从索引git commit进行新的提交后,您通常会回到索引和当前提交匹配的正常情况。如果您git add更改了所有文件,则每个文件\xe2\x80\x94 、索引和工作树\xe2\x80\x94的所有三个副本都匹配。HEAD

\n\n

介绍--only--include

\n\n

git commit 使用列出的某些文件运行有点不同,这就是--onlyvs 的--include用武之地。如果您使用--include,Git 基本上会对git add列出的文件执行操作。它只是git add <those files> && git commit或多或少的简写。

\n\n

但是,如果您使用--only\xe2\x80\x94 或不指定,这意味着 --only\xe2\x80\x94 Git 所做的就是将常规索引推开,而是从冻结中的任何内容创建一个新的临时索引犯罪。对于这个新的临时索引,Git 将git add列出您列出的每个文件。然后 Git 将从另一个提交新的提交。但现在有一个问题,因为 Git 现在需要返回到正常索引,这就是它变得有点棘手的地方。

\n\n

从临时索引进行新的提交后,Git 现在需要以相同的方式更新真实索引。本质上,从临时索引提交后,Git 会将您列出的同一组文件重新添加到真实索引中。索引中,以便它们再次匹配。

\n\n

让我们再次使用两个文件的示例,使用README.txtmain.py。这次,我们在每个文件后面添加一个版本号。这不是名字的一部分的一部分,只是为了帮助我们记住:

\n\n
    HEAD           index          work-tree\n-------------   -------------   -------------\nREADME.txt(1)   README.txt(1)   README.txt(1)\nmain.py(1)      main.py(1)      main.py(1)\n
Run Code Online (Sandbox Code Playgroud)\n\n

他们一开始每个文件的所有三个版本都是相同的。(请注意,您无法更改HEAD副本。您只能进行新的提交,然后该提交将成为副本HEAD,因为HEAD命名了新提交。)

\n\n

现在您可以编辑工作树中的两个文件:

\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)\n
Run Code Online (Sandbox Code Playgroud)\n\n

假设你现在做git add main.py工作树版本复制到索引中:

\n\n
    HEAD           index          work-tree\n-------------   -------------   -------------\nREADME.txt(1)   README.txt(1)   README.txt(2)\nmain.py(1)      main.py(2)      main.py(2)\n
Run Code Online (Sandbox Code Playgroud)\n\n

git commit如果你现在运行一个普通的,新的HEAD将有旧的README.txt,因为索引有旧的README.txt。但相反,让我们运行git commit --only README.txt。这使得临时索引,这样我们就有:

\n\n
    HEAD         temp-index       work-tree\n-------------   -------------   -------------\nREADME.txt(1)   README.txt(2)   README.txt(2)\nmain.py(1)      main.py(1)      main.py(2)\n
Run 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)\n
Run Code Online (Sandbox Code Playgroud)\n\n

与此同时,真实指数尚未改变。向上滚动一下并查看:main.py其中包含哪个版本?README.txt里面有哪个版本的?

\n\n

如果 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)\n
Run Code Online (Sandbox Code Playgroud)\n\n

也就是说,您的工作树是所有最新文件。您的提交已更新README.txt。但这种丑陋的状态意味着下一次提交将使用旧的/错误的版本README.txt!这就是为什么 Git 现在重新添加README.txt到真实索引中,这样你就得到:

\n\n
    HEAD            index         work-tree\n-------------   -------------   -------------\nREADME.txt(2)   README.txt(2)   README.txt(2)\nmain.py(1)      main.py(2)      main.py(2)\n
Run Code Online (Sandbox Code Playgroud)\n\n

main.py现在您可以根据需要进行第二次提交并更新。

\n


iBu*_*Bug 3

如果您只想提交暂存版本,请运行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)

现在foobar都已承诺

$ 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提交中没有更改,但保留了暂存状态。