第5章 Git暂存区

    上一章主要学习了三个命令:git init、git add和git commit,这三个命令可以说是版本库创建的三部曲。同时还通过对几个问题的思考了解了Git版本库在工作区中的布局,Git三个等级的配置文件及Git的别名命令等内容。

    在上一章的实践中,DEMO版本库经历了两次提交,可以用git log查看提交日志(附加的—stat参数可以看到每次提交的文件变更统计)。


    $cd/path/to/my/workspace/demo $git log—stat commit a0c641e92b10d8bcca1ed1bf84ca80340fdefee6 Author:Jiang Xin<jiangxin@ossxp.com> Date:Mon Nov 29 11:00:06 2010+0800 who does commit? commit 9e8a761ff9dd343a1380032884f488a2422c495a Author:Jiang Xin<jiangxin@ossxp.com> Date:Sun Nov 28 12:48:26 2010+0800 initialized. welcome.txt|1+ 1 files changed,1 insertions(+),0 deletions(-)

    可以看到第一次(最早的)提交对文件welcome.txt有一行变更,而第二次(最新的)提交因为是使用—allow-empty参数进行的一次空提交,所以提交说明中看不到任何对实质内容的修改。

    下面我们将仍在这个工作区继续新的实践和学习,以掌握Git的一个最重要概念:暂存区。

    5.1 修改不能直接提交吗

    首先更改welcome.txt文件,在这个文件后面追加一行。可以使用下面的命令实现内容的追加:


    $echo "Nice to meet you.">>welcome.txt

    这时可以通过执行git diff命令看到修改后的文件与版本库中的文件的差异。(实际上这句话有问题,与本地比较的不是版本库中的文件,而是一个中间状态的文件。)


    $git diff diff—git a/welcome.txt b/welcome.txt index 18832d3..fd3c069 100644 —-a/welcome.txt +++b/welcome.txt @@-1+1,2@@ Hello. +Nice to meet you.

    对差异输出是不是很熟悉?在之前介绍版本库的“黑暗的史前时代”时,曾经展示了diff命令的输出,两者的格式是一样的。

    既然文件修改了,那么就提交吧。提交能够成功吗?


    $git commit-m "Append a nice line." #On branch master #Changes not staged for commit: #(use "git add<file>…"to update what will be committed) #(use "git checkout—<file>…"to discard changes in working directory) # #modified:welcome.txt # no changes added to commit(use "git add" and/or "git commit-a")

    提交成功了吗?好像没有!

    提交没有成功的证据:

    (1)先来看看提交日志,如果提交成功,应该有新的提交记录出现。

    下面使用了精简输出来显示日志,以便更简洁和清晰地看到提交的历史。从其中能够看出版本库中只有两个提交,都是在上一章的实践中完成的。也就是说,刚才针对修改文件的提交没有成功!


    $git log—pretty=oneline a0c641e92b10d8bcca1ed1bf84ca80340fdefee6 who does commit? 9e8a761ff9dd343a1380032884f488a2422c495a initialized.

    (2)执行git diff可以看到与之前相同的差异输出,这也说明了之前的提交没有成功。

    (3)执行git status查看文件状态,可以看到文件处于修改状态,而且git status命令的输出和git commit提交失败的输出信息完全一样!

    (4)对于习惯了像CVS和Subversion那样精简的状态输出的用户,可以在执行git status时附加上-s参数,显示精简格式的状态输出。


    $git status-s M welcome.txt

    提交为什么会失败呢?再回过头来仔细看看刚才git commit命令提交失败后的输出:


    #On branch master #Changes not staged for commit: #(use "git add<file>…"to update what will be committed) #(use "git checkout—<file>…" to discard changes in working directory) # #modified:welcome.txt # no changes added to commit(use "git add" and/or "git commit-a")

    把它翻译成中文则是:


    #位于您当前工作的分支master上 #下列修改还没有加入到提交任务(提交暂存区,stage)中,不会被提交: #(使用"git add<file>…"命令后,改动就会加入到提交任务中, #要在下一次提交操作时才被提交) #(使用"git checkout—<file>…"命令,工作区中当前您不打算 #提交的修改会被彻底清除!) # #已修改:welcome.txt # 警告:提交任务是空的噻,您不要再搔扰我啦(除非使用 "git add"和/或"git commit-a"命令)

    也就是说,需要对修改的welcome.txt文件执行git add命令,将修改的文件添加到“提交任务”中,然后才能提交!

    这个行为真的很奇怪,对于其他版本控制系统来说执行add操作是向版本库中添加新文件用的,修改的文件(已被版本控制跟踪的文件)在下次提交时会直接被提交。Git的这个古怪的行为会在下面的介绍中得到解释,大家会逐渐习惯并喜欢Git的这个设计。

    好了,现在就将修改的文件“添加”到提交任务中吧:


    $git add welcome.txt

    现在再执行一些Git命令,看看当执行完“添加”操作后,Git库发生了什么变化:

    执行git diff没有输出,难道是被提交了?可是只是执行了“添加”到提交任务的操作,相当于一个“登记”的命令,并没有执行提交哇?


    $git diff

    这时如果与HEAD(当前版本库的头指针)或master分支(当前工作分支)进行比较,就会发现有差异。这个差异才是正常的,因为尚未真正提交嘛。


    $git diff HEAD diff—git a/welcome.txt b/welcome.txt index 18832d3..fd3c069 100644 —-a/welcome.txt +++b/welcome.txt @@-1+1,2@@ Hello. +Nice to meet you.

    执行git status命令,状态输出和之前的不一样了。


    $git status #On branch master #Changes to be committed: #(use "git reset HEAD<file>…"to unstage) # #modified:welcome.txt #

    再对新的Git状态输出进行翻译:


    $git status #位于分支master上 #下列修改将被提交: #(如果你后悔了,可以使用"git reset HEAD<file>…"命令 #将下列改动撤出提交任务(提交暂存区,stage),否则执行提交命令 #可真的要提交喽) # #已修改:welcome.txt #

    不得不说,Git太人性化了,它把各种情况下可能使用到的命令都告诉给用户了,虽然这显得有点啰嗦。如果不要这么啰嗦,可以像下面这样用简洁的方式显示状态:


    $git status-s M welcome.txt

    上面的精简状态输出与执行git add之前的精简状态输出相比,有细微的差别,发现了吗?

    虽然都是M(Modified)标识,但是位置不一样。在执行git add命令之前,这个M位于第二列(第一列是一个空格),在执行完git add之后,字符M位于第一列(第二列是空白)。

    位于第一列的字符M的含义是:版本库中的文件与处于中间状态——提交任务(提交暂存区,stage)中的文件相比有改动。

    位于第二列的字符M的含义是:工作区当前的文件与处于中间状态——提交任务(提交暂存区,stage)中的文件相比有改动。

    是不是还有一些不明白?为什么Git的状态输出中提示了那么多让人不解的命令?为什么存在一个提交任务的概念而又总是把它叫作暂存区(stage)?不要紧,马上就会专题讲述“暂存区”的概念。当了解了Git版本库的设计原理之后,理解相关Git命令就易如反掌了。

    这时如果直接提交(git commit),加入提交任务的welcome.txt文件的更改就会被提交入库了。但是先不忙着执行提交,再执行一些操作,看看是否会被彻底地搞糊涂。

    (1)继续修改一下welcome.txt文件(在文件后面再追加一行)。


    $echo "Bye-Bye.">>welcome.txt

    (2)然后执行git status,查看一下状态:


    $git status #On branch master #Changes to be committed: #(use "git reset HEAD<file>…"to unstage) # #modified:welcome.txt # #Changes not staged for commit: #(use "git add<file>…" to update what will be committed) #(use "git checkout—<file>…" to discard changes in working directory) # #modified:welcome.txt #

    状态输出居然是之前出现的两种不同状态输出的杂合体。

    (3)如果显示精简的状态输出,也会看到前面两种精简输出的杂合体。


    $git status-s MM welcome.txt

    上面的更为复杂的Git状态输出可以这么理解:不但版本库中最新提交的文件与处于中间状态——提交任务(提交暂存区,stage)中的文件相比有改动,而且工作区当前的文件与处于中间状态——提交任务(提交暂存区,stage)中的文件相比也有改动。

    即现在welcome.txt有三个不同的版本,一个在工作区,一个在等待提交的暂存区,还有一个是版本库中最新版本的welcome.txt。通过不同的参数调用git diff命令可以看到不同状态下welcome.txt文件的差异。

    (1)不带任何选项和参数调用git diff显示工作区的最新改动,即工作区与提交任务(提交暂存区,stage)中相比的差异。


    $git diff diff—git a/welcome.txt b/welcome.txt index fd3c069..51dbfd2 100644 —-a/welcome.txt +++b/welcome.txt @@-1,2+1,3@@ Hello. Nice to meet you. +Bye-Bye.

    (2)将工作区和HEAD(当前工作分支)相比,会看到更多的差异。


    $git diff HEAD diff—git a/welcome.txt b/welcome.txt index 18832d3..51dbfd2 100644 —-a/welcome.txt +++b/welcome.txt @@-1+1,3@@ Hello. +Nice to meet you. +Bye-Bye.

    (3)通过参数—cached或—staged调用git diff命令,看到的是提交暂存区(提交任务,stage)和版本库中文件的差异。


    $git diff—cached diff—git a/welcome.txt b/welcome.txt index 18832d3..fd3c069 100644 —-a/welcome.txt +++b/welcome.txt @@-1+1,2@@ Hello. +Nice to meet you.

    好了,现在是时候提交了。执行git commit命令进行提交:


    $git commit-m "which version checked in?" [master e695606]which version checked in? 1 files changed,1 insertions(+),0 deletions(-)

    这次提交终于成功了。如何证明提交成功了呢?

    (1)通过查看提交日志,看到了新的提交:


    $git log—pretty=oneline e695606fc5e31b2ff9038a48a3d363f4c21a3d86 which version checked in? a0c641e92b10d8bcca1ed1bf84ca80340fdefee6 who does commit? 9e8a761ff9dd343a1380032884f488a2422c495a initialized.

    (2)查看精简的状态输出。

    状态输出中,文件名的前面少了一个字母M,即只剩下第二列的字母M。那么第一列的M哪里去了?被提交了呗。即提交任务(提交暂存区,stage)中的内容被提交到版本库中。所以,第一列会因为提交暂存区(提交任务,stage)与版本库中的状态一致而显示一个空白。


    $git status-s M welcome.txt

    提交的welcome.txt是哪个版本呢?可以通过执行git diff或git diff HEAD命令查看差异。虽然命令git diff和git diff HEAD的比较过程并不相同(可以通过strace命令跟踪命令执行过程中的文件访问),但是会看到如下面所示的相同的差异输出结果。


    $git diff diff—git a/welcome.txt b/welcome.txt index fd3c069..51dbfd2 100644 —-a/welcome.txt +++b/welcome.txt @@-1,2+1,3@@ Hello. Nice to meet you. +Bye-Bye.