5.1 修改不能直接提交
首先修改welcome.txt文件,在这个文件后面追加一行:
echo "Nice to meet you." >> welcome.txt
使用git diff命令查看修改后的文件与暂存区(并不是版本库,后面会有相关讨论)中的文件的差异:
diff --git a/welcome.txt b/welcome.txt
index a8f9fd8..b0e5c6e 100644
--- a/welcome.txt
+++ b/welcome.txt
@@ -1 +1,2 @@
Hello
+Nice to meet you.
差异的输出格式:
- 以---开头的文件表示源文件;
- 以+++开头的文件表示目标文件;
- @@ 中间的数字表示行号和行数,-1表示源文件第一行开始共一行,+1,2表示目标文件从第一行开始共2行;
- 文件内容中以空格开头的表示源文件和目标文件共同拥有的行,-开头的行表示只在源文件中出现的行,+开头的行表示只在目标文件出现的行。
此时使用git commit命令并不会提交修改,反而会报错:
git commit -m "Append a nice line."
On branch master
Changes not staged for commit:
modified: welcome.txt
no changes added to commit
使用git status查看文件状态,加上-s参数显示精简格式的状态输出:
git status
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"
git status -s
M welcome.txt
welcome.txt文件处于修改状态。
根据git status输出的提示,需要对修改的文件执行git add命令,将修改的文件添加到“提交任务”中,然后才能提交。此处与svn差别很大,在svn中执行add操作是向版本库中添加新文件,修改的文件在下次提交时会直接提交。
按照提示将修改的文件添加到提交任务中:
git add welcome.txt
在执行完添加操作,而没有执行提交操作时,查看一下Git工作区发生了什么变化:
1、执行git diff没有输出,说明是本地文件与暂存区的比较;
2、执行git diff HEAD(当前版本库的头指针),会有差异输出,说明此时比较的是本地文件和版本库的最新版本;
3、执行git status,输出与添加前不一样了:
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: welcome.txt
4、执行git status -s,以简洁方式显示状态:
git status -s
M welcome.txt
乍一看貌似与添加前的输出一样,其实它们有细微的差别:
- 虽然都是M(Modified)标识,但是位置不一样。在执行git add命令之前,M位于第二列(第一列是一个空格),在执行git add命令之后,M位于第一列(第二列是空白)。
- 位于第一列的M的含义是:版本库中的文件与暂存区的文件相比有改动;
- 位于第二列的M的含义是:工作区当前的文件与处于赞春去的文件相比有改动。
为了加深理解,暂不提交之前的添加任务,而继续修改文件:
echo "Bye-Bye." >> welcome.txt
查看一下状态:
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
这里同时出现了前文讨论的两种状态。再查看精简的状态:
git status -s
MM welcome.txt
同时出现了两个M:不但版本库中最新提交的文件与处于暂存区中的文件相比有改动,而且工作区与暂存区中的文件相比也有改动。
即现在有三个不同版本的welcome.txt:一个在工作区,一个在暂存区,一个是版本库中最新版本的welcome.txt。
使用不同参数调用git diff命令可以看到不同状态下welcome.txt文件的差异:
- 不带任何参数的git diff比较工作区和暂存区的差异:
-
git diff diff --git a/welcome.txt b/welcome.txt index b0e5c6e..45cf837 100644 --- a/welcome.txt +++ b/welcome.txt @@ -1,2 +1,3 @@ Hello Nice to meet you. +Bye-Bye.
- git diff HEAD比较工作区和版本库最新版本的差异:
-
git diff HEAD diff --git a/welcome.txt b/welcome.txt index a8f9fd8..45cf837 100644 --- a/welcome.txt +++ b/welcome.txt @@ -1 +1,3 @@ Hello +Nice to meet you. +Bye-Bye.
- 带--cached 或 --staged参数查看暂存区和版本库之间的差异:
-
git diff --staged diff --git a/welcome.txt b/welcome.txt index a8f9fd8..b0e5c6e 100644 --- a/welcome.txt +++ b/welcome.txt @@ -1 +1,2 @@ Hello +Nice to meet you.
执行提交操作:
git commit -m "which version checked in?"
[master 326f237] which version checked in?
1 file changed, 1 insertion(+)
提交成功,使用git status -s查看状态:
git status -s
M welcome.txt
文件名前少了第一个M,只剩下第二个M,说明暂存区的任务被提交到了版本库,现在只剩下工作区和暂存区的差异。
5.2 理解Git暂存区
在版本库.git目录下有一个index文件,看一下这个文件:
1、撤销工作区中welcome.txt文件尚未提交的修改(5.1中添加的Bye-Bye那一行)并查看工作区状态:
git checkout -- welcome.txt
git status -s
git status -s无输出,说明未提交的修改已经被撤销了。
2、查看.git/index的时间戳:
ls --full-time .git/index
-rw-r--r-- 1 x250 197121 145 2016-07-16 07:52:20.640437700 +0800 .git/index
时间戳是刚刚运行git status -s的时间。
3、再次查看工作区状态并查看.git/index的时间戳:
git status -s
ls --full-time .git/index
-rw-r--r-- 1 x250 197121 145 2016-07-16 07:52:20.640437700 +0800 .git/index
时间戳没有变化。
4、更改welcome.txt文件的时间戳,但并不改变它的内容(可以使用touch命令,它用来修改文件时间戳,或者新建一个不存在的文件)。然后查看工作区状态并查看.git/index的时间戳:
touch welcome.txt
git status -s
ls --full-time .git/index
-rw-r--r-- 1 x250 197121 145 2016-07-16 07:58:10.512449200 +0800 .git/index
发现git status -s依然无输出,说明工作区没有新的修改;但是.git/index时间戳改变了。
以上的实验说明当执行git status命令(或者git diff命令)扫描工作区改动的时候,先依据.git/index文件中记录的时间戳、长度等信息判断工作区文件是否改变。如果工作区文件的时间戳改变了,说明文件的内容可能被改变了,需要打开文件、读取文件内容,与更改前的原始文件相比较,判断文件内容是否被更改。如果文件内容没有改变,则将该文件新的时间戳记录到.git/index文件中。因为如果要判断文件是否更改,使用时间戳、文件长度等信息进行比较要比通过文件北荣比较快得多,所以Git这样的实现方式可以让工作区状态扫描更快速的执行。
文件.git/index是包含文件索引的目录树,记录了文件名和文件的状态信息,文件的内容没有存储在其中,而是保存在Git对象库.git/objects目录中,文件索引建立了文件和对象库的对应关系。
下图展示了工作区、版本库中的暂存区和版本库之间的关系:
- 图中左侧为工作区,右侧为版本。在版本库中标记为index的区域是暂存区,标记为master的是master分支所代表的目录树;
- HEAD指向master分支,所以图中所示命令中出现HEAD地方可以用master来替换;
- 图中的objects表示的区域为Git的对象库,位于.gti/objects目录下;
- 当对工作区修改(或新增)的文件执行git add命令时,暂存区的目录树将被更新,同时工作区修改(或新增)的文件内容会被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中;
- 当执行提交操作时,暂存区的目录树会写到版本库中,master指向新写入的目录树(内容与提交的暂存区的目录树一致);
- 当执行git reset HEAD时,暂存区的目录树会被重写,会被master分支指向的目录树的内容所替换,但是工作区不受影响;
- 当执行git rm --cahced <file> 命令时,会直接从暂存区删除文件,工作区不会改变;
- 当执行git checkout . 或 git checkout -- <file>命令时,会用暂存区全部的文件或指定的文件替换工作区的文件,即这个命令会清除工作区中未添加到暂存区的改动;
- 当执行git checkout HEAD . 或 git checkout HEAD <file> 命令时,会用HEAD指向的master分支中的全部或部分文件替换暂存区和工作区的文件,即它会清除工作区中未添加的改动还会清除暂存区中未提交的改动。
5.3 浏览暂存区和版本库的目录树、git diff
1、浏览暂存区和版本库的目录树
对于HEAD指向的目录树,可以使用git ls-tree来查看,-l参数可现实文件的大小:
git ls-tree -l HEAD
100644 blob b0e5c6e24bc84d489773b2fda5accf005bc912f1 25 welcome.txt
输出中的信息从左到右:
- 第一个字段是文件的读写属性(rw-r--r--);
- 第二个字段说明是Git对象库中的一个blob对象(文件);
- 第三个字段是该文件在对象库中的ID(一个40位的SHA1哈希值);
- 第四个字段是文件大小(单位是字节);
- 第五个字段是文件名。
下面的命令清除当前工作区中没有加入版本库的文件和目录:
git clean -fd
下面的命令用暂存区内容刷新工作区:
git checkout .
对工作区做一些修改:
$ echo "Bye-Bye." >> welcome.txt
$ mkdir -p a/b/c
$ echo "Hello." > a/b/c/hello.txt
$ git add .
x250@x250-PC MINGW64 ~/demo (master)
$ echo "Bye-Bye." >> a/b/c/hello.txt
$ git status -s
AM a/b/c/hello.txt
M welcome.txt
查看工作区文件的大小:
$ find . -path ./.git -prune -o -type f -printf "%-20p\t%s\n"
./a/b/c/hello.txt 16
./welcome.txt 36
显示暂存区的目录树:
git ls-files -s
100644 18832d35117ef2f013c4009f5b2128dfaeff354f 0 a/b/c/hello.txt
100644 45cf8376b221e28cb9c5afb382cfe15ceb3dc520 0 welcome.txt
-s命令要去显示暂存区中Object的对象名。输出中的0不是文件大小而是暂存区编号。如果想针对暂存区的目录树使用git ls-tree命令,需要先将暂存区的目录树写入Git对象库(git write-tree):
$ git write-tree
1343285bb5205fe9eed661efebcc9d65bf0cea7e
$ git ls-tree -l 1343285
040000 tree 53583ee687fbb2e913d18d508aefd512465b2092 - a
100644 blob 45cf8376b221e28cb9c5afb382cfe15ceb3dc520 34 welcome.txt
git write-tree的输出是写入Git对象库中的TreeID,这个ID将作为下一条命令的参数。
在git ls-tree命令中,没有把40位的ID写全,而是使用了前几位,实际上只要不与其他对象的ID冲突,就可以使用缩写ID。
git ls-tree输出的第一条显示目录a是一个tree对象。
如果想要递归显示目录内容,则使用-r参数。使用参数-t可以把递归中遇到的每棵树都显示出来,而不是只显示最终文件。
2、git diff
通过调用git diff并添加不同参数,可以对工作区、暂存区和HEAD中的内容进行两两比较。如下图所致(1)工作区和暂存区的比较:
$ git diff
diff --git a/a/b/c/hello.txt b/a/b/c/hello.txt
index 18832d3..e8577ea 100644
--- a/a/b/c/hello.txt
+++ b/a/b/c/hello.txt
@@ -1 +1,2 @@
Hello.
+Bye-Bye.
(2)暂存区和HEAD比较
git diff --cached
diff --git a/a/b/c/hello.txt b/a/b/c/hello.txt
new file mode 100644
index 0000000..18832d3
--- /dev/null
+++ b/a/b/c/hello.txt
@@ -0,0 +1 @@
+Hello.
diff --git a/welcome.txt b/welcome.txt
index b0e5c6e..45cf837 100644
--- a/welcome.txt
+++ b/welcome.txt
@@ -1,2 +1,3 @@
Hello
Nice to meet you.
+Bye-Bye.
(3)工作区和HEAD比较
git diff HEAD
diff --git a/a/b/c/hello.txt b/a/b/c/hello.txt
new file mode 100644
index 0000000..e8577ea
--- /dev/null
+++ b/a/b/c/hello.txt
@@ -0,0 +1,2 @@
+Hello.
+Bye-Bye.
diff --git a/welcome.txt b/welcome.txt
index b0e5c6e..45cf837 100644
--- a/welcome.txt
+++ b/welcome.txt
@@ -1,2 +1,3 @@
Hello
Nice to meet you.
+Bye-Bye.
5.4 不要使用git commit -a
Git的提交命令(git commit)可以带上-a参数,对本地所有变更的文件执行提交操作,包括对本地修改的文件和删除的文件,但不包括未被版本库跟踪的文件。
这个命令的确可以简化一些操作,减少用git add命令表示变更文件的步骤,但是如果习惯了使用这种偷懒的提交命令,机会丢掉Git暂存区最大的好处:对提交内容进行控制的能力。作者推荐不要使用git commit -a。
5.5 保存当前的工作进度
git stash
Saved working directory and index state WIP on master: 326f237 which version checked in?
HEAD is now at 326f237 which version checked in?
$ git status
On branch master
nothing to commit, working directory clean
git stash命令备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到Git栈中。
运行完git stash后使用git status命令,发现工作区中尚未提交的改动都不见了。
来源:oschina
链接:https://my.oschina.net/u/2453016/blog/713066