前言

这是介绍git的第二篇博客,第一篇博客 http://zhangfengzhe.blog.51cto.com/8855103/1720049 初步介绍了git,下面我们来对git进行些深入的介绍。


COMMIT对象

在git中有好多种对象,COMMIT就是其中一种。HISTORY完整的叫法,应该是COMMIT HISTORY。


我们先来看一个图:


深入Git_第1张图片


HEAD如同游标似的,指向最新提交的COMMIT对象


那么这个COMMIT对象里面到底包含了什么东西呢?


COMMIT对象包含了下面一些重要信息:

  • TREE      目录树【我们所有的文件都必须在一个目录里面】

  • PARENT    指向前面一个COMMIT


如果我们想取得前一个COMMIT对象,那该怎么做呢?


当前COMMIT对象的前一个COMMIT  用HEAD~   OR    HEAD~1表示

HEAD~~  等价于  HEAD~2


[root@localhost hadoop]# git log
commit 54cf628bf72462bc37804fcc5df3850eacf9cf7e
Author: zhangfengzhe 
Date:   Sat Dec 5 18:12:13 2015 -0800
    bug fix
commit b714e9882e287957061ce6a06bca26092a0eea48
Author: zhangfengzhe 
Date:   Sat Dec 5 18:05:56 2015 -0800
    commit

log命令用于查看历史提交记录。


每一个COMMIT都有一个编号,如同那个字符串54cf628bf72462bc37804fcc5df3850eacf9cf7e,其实就是一个HASH码。


[root@localhost hadoop]# git cat-file -t HEAD
commit
[root@localhost hadoop]# git cat-file -p HEAD
tree 1aabc54505caf74bbe716649a974484cdc954438
parent b714e9882e287957061ce6a06bca26092a0eea48
author zhangfengzhe  1449367933 -0800
committer zhangfengzhe  1449367933 -0800
bug fix
[root@localhost hadoop]# git cat-file -p HEAD~
tree bffc7fb973539b5560cafbea421b65c7de5630ce
parent 9ea1deaa2bafdf6061ff1dcffdf8ede25b3c0e73
author zhangfengzhe  1449367556 -0800
committer zhangfengzhe  1449367556 -0800
commit
[root@localhost hadoop]# git cat-file -p HEAD~~
tree ac2e6ed4de163a6fd92f88bd1516728fc1f7b9a9
parent b1a45f0a85ff0e720f240c978a31f7afc2a812b5
author zhangfengzhe  1449366049 -0800
committer zhangfengzhe  1449366049 -0800
commit
[root@localhost hadoop]# git cat-file -p HEAD~2
tree ac2e6ed4de163a6fd92f88bd1516728fc1f7b9a9
parent b1a45f0a85ff0e720f240c978a31f7afc2a812b5
author zhangfengzhe  1449366049 -0800
committer zhangfengzhe  1449366049 -0800
commit
[root@localhost hadoop]#

注意:

git cat-file -p XXX   eq  git show XXX

git cat-file -t XXX


-p表示打印内容,-t表示取得类型,那么这个XXX,实际上是一个标示,可以是HEAD,HEAD~N,或者HASM码什么的,甚至可以是不完整的HASH都可以。

[root@localhost hadoop]# git cat-file -t ac2e
tree
[root@localhost hadoop]# git cat-file -p ac2e
100644 blob 5e6618e52979f6f581ce848dda15a0bfc24bac24HelloWorld.java
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391delete.me
100644 blob 4ca70693ed50750b52b2a5d1c289adc554a251aflove.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391t1.txt
[root@localhost hadoop]#


通过上面的打印 ,其实也清楚了,文件其实也是一个BLOB对象,目录其实也就是一个TREE对象,也看到了他们的HASH码。


那么完整的示意图如下:


深入Git_第2张图片



tree-ish表达式

我们先来看一下.git目录的结构:

[root@localhost hadoop]# ll -A
total 36
-rw-r--r-- 1 root root   26 Dec  5 18:12 delete.me
drwxr-xr-x 8 root root 4096 Dec  5 18:12 .git
-rw-r--r-- 1 root root   37 Dec  5 18:11 HelloWorld.java
-rw-r--r-- 1 root root   31 Dec  5 06:42 love.txt
-rw-r--r-- 1 root root    0 Dec  5 17:40 t1.txt
[root@localhost hadoop]# 
[root@localhost .git]# ll
total 96
drwxr-xr-x  2 root root 4096 Dec  4 19:41 branches
-rw-r--r--  1 root root    8 Dec  5 18:12 COMMIT_EDITMSG
-rw-r--r--  1 root root   92 Dec  4 19:41 config
-rw-r--r--  1 root root   73 Dec  4 19:41 description
-rw-r--r--  1 root root   23 Dec  4 19:41 HEAD
drwxr-xr-x  2 root root 4096 Dec  4 19:41 hooks
-rw-r--r--  1 root root  361 Dec  5 18:12 index
drwxr-xr-x  2 root root 4096 Dec  4 19:41 info
drwxr-xr-x  3 root root 4096 Dec  5 03:47 logs
drwxr-xr-x 50 root root 4096 Dec  5 18:12 objects
-rw-r--r--  1 root root   41 Dec  5 18:11 ORIG_HEAD
drwxr-xr-x  4 root root 4096 Dec  5 18:12 refs
[root@localhost .git]# cat HEAD
ref: refs/heads/master
[root@localhost .git]# cat refs/heads/master 
54cf628bf72462bc37804fcc5df3850eacf9cf7e
[root@localhost .git]# git cat-file -t 54cf6
commit
[root@localhost .git]#


重点需要关注的就是HEAD,它链接到refs/heads/master,而master中存放的就是HASH码,其实就是一个COMMIT对象!


那么这个MASTER到底是什么东西呢?

[root@localhost .git]# git cat-file -p HEAD
tree 1aabc54505caf74bbe716649a974484cdc954438
parent b714e9882e287957061ce6a06bca26092a0eea48
author zhangfengzhe  1449367933 -0800
committer zhangfengzhe  1449367933 -0800
bug fix
[root@localhost .git]# git cat-file -p master
tree 1aabc54505caf74bbe716649a974484cdc954438
parent b714e9882e287957061ce6a06bca26092a0eea48
author zhangfengzhe  1449367933 -0800
committer zhangfengzhe  1449367933 -0800
bug fix
[root@localhost .git]#

通过上面的,我们是否可以得出HEAD eq master ?


实际上在GIT里面,master就是一个分支,即branch,也就是一个文件,里面存放着HASH!


在GIT中,HEAD是可以发生指向变化的,稍后会介绍。

[root@localhost .git]# git cat-file -p HEAD~
tree bffc7fb973539b5560cafbea421b65c7de5630ce
parent 9ea1deaa2bafdf6061ff1dcffdf8ede25b3c0e73
author zhangfengzhe  1449367556 -0800
committer zhangfengzhe  1449367556 -0800
commit
[root@localhost .git]# git cat-file -p master~
tree bffc7fb973539b5560cafbea421b65c7de5630ce
parent 9ea1deaa2bafdf6061ff1dcffdf8ede25b3c0e73
author zhangfengzhe  1449367556 -0800
committer zhangfengzhe  1449367556 -0800
commit
[root@localhost .git]# 
[root@localhost .git]# git rev-parse HEAD
54cf628bf72462bc37804fcc5df3850eacf9cf7e
[root@localhost .git]# git rev-parse master
54cf628bf72462bc37804fcc5df3850eacf9cf7e
[root@localhost .git]#


git rev-parse可以用于查看指向的HASH


那么问题来了,怎么定位到master~3的根目录呢?

[root@localhost .git]# git cat-file -p master^{tree}
100644 blob d3aaea0fd831e7efc5751357d77afd3dde514b3fHelloWorld.java
100644 blob 01f9a2aac3e315c5caa00db4019f1d934171dba0delete.me
100644 blob 4ca70693ed50750b52b2a5d1c289adc554a251aflove.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391t1.txt
[root@localhost .git]# git cat-file -p master:love.txt
i love you
i hit you
i eat you
[root@localhost .git]#


其实,上面的例子,就是所谓的tree-ish表达式!

tree-ish表达式,可以方便我们快速定位到任何一个对象。



创建/删除/合并分支

git与其他的版本控制系统,差别还是蛮大的,分支对于git而言,只是一个文件,里面存放HASH码而已。

[root@localhost hadoop]# git branch
* master
[root@localhost hadoop]#


git branch 列出所有的branch


注意到master前面有一个*号,说明的是当前正在使用的branch


[root@localhost hadoop]# git branch mybranch1
[root@localhost hadoop]# git branch
* master
  mybranch1
[root@localhost hadoop]#


如果我们不想在master分支上,想切换到mybranch1上,该如何操作呢?

[root@localhost hadoop]# git checkout mybranch1
Switched to branch 'mybranch1'
[root@localhost hadoop]# git branch
  master
* mybranch1
[root@localhost hadoop]#


如果我们想先创建一个分支,然后切换到这个分支的话,可以分步使用git branch + git checkout 来完成,当然也可以快捷完成:git checkout -b即可。

[root@localhost hadoop]# git checkout -b mybranch2
Switched to a new branch 'mybranch2'
[root@localhost hadoop]# git branch
  master
  mybranch1
* mybranch2
[root@localhost hadoop]#



切换分支,是什么鬼?

[root@localhost hadoop]# git branch
  master
* mybranch1
[root@localhost hadoop]# cat .git/HEAD 
ref: refs/heads/mybranch1
[root@localhost hadoop]# git checkout master
Mlove.txt
Switched to branch 'master'
[root@localhost hadoop]# cat .git/HEAD 
ref: refs/heads/master
[root@localhost hadoop]#

切换分支,只是改变了HEAD文件指向而已!


其实这更加准确的说明了,HEAD指向的是当前的branch!


创建分支到底意味着什么?

[root@localhost hadoop]# cd .git/refs/heads/
[root@localhost heads]# ll
total 16
-rw-r--r-- 1 root root 41 Dec 12 00:10 master
-rw-r--r-- 1 root root 41 Dec 12 00:24 mybranch1
[root@localhost heads]# cat *
51d60776dc4cbe9c07f65cee378874f232e198d8
51d60776dc4cbe9c07f65cee378874f232e198d8
[root@localhost heads]#

我们清楚的看到了,mybranch1和master一样,以文件的形式存在,放的是HASH CODE。


此时此刻,其实mybranch1 和 master 一样都指向当前的COMMIT对象!  如下图所示:


深入Git_第3张图片


HEAD的指向就是通过git checkout在不同的分支上来回切换。


下面,我们来做几个小例子:


[root@localhost hadoop]# git branch
  master
* mybranch1
[root@localhost hadoop]# vi love.txt
[root@localhost hadoop]# cat love.txt 
i love you
i hit you
i eat you
changed ?
add
[root@localhost hadoop]# git cat-file -p HEAD:love.txt
i love you
i hit you
i eat you
changed ?


分支改变了,但没有提交,看看对其他分支的影响:

[root@localhost hadoop]# git checkout master
Mlove.txt
Switched to branch 'master'
[root@localhost hadoop]# git cat-file -p HEAD:love.txt
i love you
i hit you
i eat you
changed ?


分支提交后,这个分支就会变化,但是不会影响其他分支的,其他分支看不到这种变化!因为HEAD指针指向的问题。

[root@localhost hadoop]# git checkout mybranch1
Mlove.txt
Switched to branch 'mybranch1'
[root@localhost hadoop]# git branch
  master
* mybranch1
[root@localhost hadoop]# git add love.txt
[root@localhost hadoop]# git commit -m 'test branch' love.txt 
[mybranch1 8cf1339] test branch
 1 file changed, 1 insertion(+)
[root@localhost hadoop]# git branch
  master
* mybranch1
[root@localhost hadoop]# git cat-file -p HEAD:love.txt
i love you
i hit you
i eat you
changed ?
add
[root@localhost hadoop]# git checkout master
Switched to branch 'master'
[root@localhost hadoop]# git branch
* master
  mybranch1
[root@localhost hadoop]# git cat-file -p HEAD:love.txt
i love you
i hit you
i eat you
changed ?
[root@localhost hadoop]#

如果此时此刻,我们对mybranch1分支进行删除,会发生什么呢?

[root@localhost hadoop]# git branch
* master
  mybranch1
[root@localhost hadoop]# git branch -d mybranch1
error: The branch 'mybranch1' is not fully merged.
If you are sure you want to delete it, run 'git branch -D mybranch1'.
[root@localhost hadoop]#

画个图,来说明:



如果我们把mybranch1分支删除掉,那么commit-e对象将会找不到了,因为没有对象可以指向它,会成为“孤儿”,GIT不会允许出现这样的情况,此时我们要做的就是合并!

[root@localhost hadoop]# git branch
* master
  mybranch1
[root@localhost hadoop]# git merge mybranch1
Updating 7d1ea72..8cf1339
Fast-forward
 love.txt | 1 +
 1 file changed, 1 insertion(+)
[root@localhost hadoop]# git cat-file -p HEAD:love.txt
i love you
i hit you
i eat you
changed ?
add
[root@localhost hadoop]#


其实,合并后,只是更新了master的指向,master会指向commit-e而已。


注意git merge执行后的提示“Fast-forward”,这是个什么意思?


其实说的就是,这种合并是一个比较简单的合并方式,因为仅仅只是改变了master的指向就达到了合并的目的。还有一种较为复杂的情况3-WAY MERGE:


如果master分支有新的COMMIT,而mybranch1有2个新的COMMIT,那么怎么合并呢?


深入Git_第4张图片


此时此刻,就不可以将master指向commit-g那么简单了。


那么实际上,git会对于master以及mybranch1分支的共有部分commit-d、commit-e、commit-g进行比较处理,生成一个新的commit对象完成merge操作。但这对于我们都是透明的,我们其实无需关心,还是直接使用git merge即可!