第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.