认识GIT的存储类型

Git存储类型主要有4种,blob,tree,commit,tag,他们以压缩的形式存储在.git/objects目录当中。其中blob和tree构成其完整的文件存储系统,类似于操作系统的文件系统。commit和tag则维护提交和标记等信息。

在Git的存储模型当中有一个原则,只要类型和内容相同,就会被认定为同一个文件,无论在工作区当中文件有多少份拷贝,在git库里面只会存有一份,并以其类型,大小和内容串接后的内容结果的散列值作为其文件名。其格式为:

#TYPE# #SPACE# \0 #SIZE#
#CONTENT#

其读写规则如下面的Ruby程序所示:

require 'zlib'
require 'digest'

def write_object(content, type)
    size = content.length
    header = "#{type} #{size}\0"
    store = header + content
    digest = Digest::SHA1.hexdigest(store)
    zib = Zlib::Deflate.deflate(store)
    open "/tmp/#{digest}", 'wb' do|io|
        io.write zib
    end
end

def read_object(path)
    open path, 'rb' do|io|
        content = Zlib::Inflate.inflate(io.read)
        index = content.index("\0")
        type, size = content[0...index].split(/\s+/)
        puts "type: #{type}, size: #{size}"
        puts "content: #{content[(index + 1)..-1].unpack('a*')}\n\n" 
    end
end

blob

git blob 是 git 最基础的存储类型,类似于文件系统中的文件,可以表示存储任意的文件,比如文本文件,源代码,图片等,对应文件系统中的标准文件。

如下图所示,blob文件只记录类型(blob),长度,二进制内容。并不关心其真实的存储位置和具体的文件名,如果有多个文件内容完全一致,但是路径或文件名不一致,在GIT的库中对应的都是同一个blob文件。

blob

tree

tree 类似于文件系统中的目录,他能指向或包含:

  • git blob 对象记录
  • 其他 git tree 对象记录

tree

同blob一样,tree同样只记录它下面包含的直接子目录和文件,不关心自己的名称,它自己的名称记录在自己的parent tree当中。如下图所示的文件结构,tree1,tree2,tree3指的都是同一颗tree。

├── tree0
│   └── tree3
│       └── test.txt
├── tree1
│   └── test.txt
└── tree2
    └── test.txt

commit

git commit对象存储的是每一次提交的内容,它包含:

  • 谁创建了这个提交,一般包含用户名和Email及提交信息等内容。

      author James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800
      committer James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800
    
  • 指向根git tree对象的一个指针.

      tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
    
  • 指向父git commit对象的一个指针,可以有多个,一般为一个,merge代码的时候会出现两个,也可以没有(只限首次提交的情况)。

      parent 69d53d698802439dab1d82cc0e646987b0fab347
      parent 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152
    
  • 本次提交的注释或备注。

commit

tag

git tag对象比较特殊,相当于别名的作用,我们可以使用它对commit,tree,blob对象创建别名。一般情况下,如果我们不写tag的描述,则不会在.git/objects目录中新增tag object对象,只会在.git/refs/tags下新增一个ref的记录。只有在tag时指定了-m并填写了描述信息时,它才会真正生成一个git tag对象。它一般包如下信息:

  • 所指向对象的HASH值。
  • 所指向对象的类型。
  • tag名称。
  • tagger(tag创建者及时间)。
  • 描述信息。

tag

理解暂存区

大家都清楚,Git本地库有3个组成部分,分别是工作目录(Working Directory),暂存区(Stage或者Index),Git仓库,其中,工作目录和Git仓库都比较好理解,和其他的版本控制工具非常类似,要理解暂存区就要稍微花点心思了。

Git的设计确实非常巧妙,所有暂存区信息的维护只跟一个文件有关,那就是 .git/index,和工作区和Git仓库不同,.git/index只维护 blob 对象的信息,并不关心 tree 或者 commit。我们可以使用命令 git ls-files —stage 查看暂存区的状态。

100644 83baae61804e65cc73a7201a7252750c76066a30 0	bak/test.txt
100644 fa49b077972391ad58037050f2a75f74e3671e92 0	new.txt
100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a 0	test.txt

暂存区 vs. git库

放到暂存区的文件都已经添加到.git/objects中,执行commit时,就是把暂存区中的变化提交到git库中。对比暂存区和库中的文件,可以很容易区分哪些文件有了变化。暂存区中有,而库中没有,则表示下次commit需要新增文件。暂存区中没有,而库中有,则表示下次commit需要删除某些文件。如果暂存区中有,库中也有,但是其HASH不一样,则表示下次commit需要修改文件。暂存区中有,库中也有,名称和HASH值都相同,则表示当前提交中没有任何更新。

暂存区 vs. 工作目录

一般我们执行 git add —all 命令,可以批量把工作目录的所有更新都添加到暂存区当中,执行 git status 可以很容易查看暂存区中和工作目录中的文件变化。同和git库比较一样,无论是文件内容变化了,还是文件名或者文件路径变化了,都会被视作更新。

注意,.git/index中只track blob对象,它其实不关心tree对象的,这也正是暂存区设计精巧之处,因为只要是blob对象变化了,它的parent tree一定会相应变化,所以tree的变化只要在commit时自动更新就好了。

如果删除.git/index , 等价于下次commit删除整个库中的文件。

实例演示

基本操作

首先,创建一个空目录并进到该目录,执行 git init 初始化git本地库。执行完后,你会发现,当前目录下多出了一个.git的隐藏文件夹,如下所示:

.git
├── HEAD
├── branches
├── config
├── description
├── hooks
│   ├── ...
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

git的所有命令操作都是围绕着.git目录下的内容展开,我们的每一步操作,都可能会引起该目录中内容的变化,下面我们来边操作边观察该目录的变化情况。

下面,我们先新增一个文件,并把它存储到暂存区。

echo "version 1" > test.txt
git add --all

可以发现,这时.git/objects下多了一个文件。

.git
├── ...
├── objects
│   ├── 83
│   │   └── baae61804e65cc73a7201a7252750c76066a30
│   ├── info
│   └── pack
└── ...

通过命令查看,该文件果然是刚刚添加的test.txt。

git cat-file -t 83baae6		# blob	
git cat-file -s 83baae6		# 10
git cat-file -p 83baae6		# version 1

使用命令 git commit -m 'first commit' 创建第一个提交。

这时,我们发现 .git/objects 目录中又新增了2个文件。

.git
├── ...
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── master
├── objects
│   ├── 10
│   │   └── 4778189118bbf75587062dc6fb61bbc9685a47
│   ├── 83
│   │   └── baae61804e65cc73a7201a7252750c76066a30
│   ├── d8
│   │   └── 329fc1cc938780ffdd9f94e0d364e0ea74f579
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── master
    └── tags

通过命令查看,可以发现,一个文件是tree对象,另一个是commit对象。

commit 对象的散列值会因人而异,即便是同一个人,不同的时间提交的commit对象也会不一样。

.git/logs/HEAD 会记录下你每一次的更新操作,比如提交动作或者切换分支的动作,如果只在单分支下操作,.git/logs/HEAD 中的内容和 .git/logs/refs/heads/master一般都会同步更新,完全一致。而 .git/refs/heads/master 则指向于当前分支下最新的提交。

cat .git/logs/HEAD
# 0000000000000000000000000000000000000000 104778189118bbf75587062dc6fb61bbc9685a47 James Zhan <zhiqiangzhan@gmail.com> 1425023419 +0800	commit (initial): first commit

cat .git/logs/refs/heads/master  # Same as `cat .git/logs/HEAD`

git cat-file -t 10477818  	# commit
git cat-file -s 10477818 		# 183
git cat-file -p 10477818
# tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
# author James Zhan <zhiqiangzhan@gmail.com> 1425023419 +0800
# committer James Zhan <zhiqiangzhan@gmail.com> 1425023419 +0800
# 
# first commit

git cat-file -t d8329fc1c 	# tree
git cat-file -s d8329fc1c 	# 36
git cat-file -p d8329fc1c
# 100644 blob 83baae61804e65cc73a7201a7252750c76066a30	test.txt

cat .git/refs/heads/master 	# 104778189118bbf75587062dc6fb61bbc9685a47

修改test.txt,并且再新增一个new.txt。

echo "version 2" > test.txt                                                                                                                   
echo "new file" > new.txt                                                                                                                   
git add --all

添加到暂存区后,会发现 .git/objects 下又多了2个文件。

.git
├── ...
├── objects
│   ├── 10
│   │   └── 4778189118bbf75587062dc6fb61bbc9685a47
│   ├── 1f
│   │   └── 7a7a472abf3dd9643fd615f6da379c4acb3e3a
│   ├── 83
│   │   └── baae61804e65cc73a7201a7252750c76066a30
│   ├── d8
│   │   └── 329fc1cc938780ffdd9f94e0d364e0ea74f579
│   ├── fa
│   │   └── 49b077972391ad58037050f2a75f74e3671e92
│   ├── info
│   └── pack
└── ...

通过命令查看可以看到最新添加的2个文件内容,正是新增的一个文件new.txt和修改过后的test.txt中的文件内容。

注意:内容为 “version 1” 的blob对象还在。尽管我们操作是在同一文件中进行,只要是内容不同,git都会认为是不同的文件,如果你碰巧文件内容和其他文件内容相同,则git不会创建新的文件,比如:new.txt中恰好也是 “version 1”,则不会有新的文件增加,感兴趣的同学可以试一试。

git cat-file -t 1f7a7a47 		# blob
git cat-file -s 1f7a7a47		# 10
git cat-file -p 1f7a7a47		# version 2

git cat-file -t fa49b077		# blob
git cat-file -s fa49b077		# 9
git cat-file -p fa49b077		# new file

使用命令 git commit -m 'second commit' 提交该变更,我们会发现,.git/objects 目录中又新增了2个文件,跟第一次的提交一样,一个是commit对象,另一个是tree对象。

.git
├── ...
├── objects
│   ├── 01
│   │   └── 55eb4229851634a0f03eb265b69f5a2d56f341
│   ├── 10
│   │   └── 4778189118bbf75587062dc6fb61bbc9685a47
│   ├── 1f
│   │   └── 7a7a472abf3dd9643fd615f6da379c4acb3e3a
│   ├── 73
│   │   └── a7f40207cd99c99e737456e1c94ad2d8632126
│   ├── 83
│   │   └── baae61804e65cc73a7201a7252750c76066a30
│   ├── d8
│   │   └── 329fc1cc938780ffdd9f94e0d364e0ea74f579
│   ├── fa
│   │   └── 49b077972391ad58037050f2a75f74e3671e92
│   ├── info
│   └── pack
└── ...
git cat-file -t 0155eb42 		# tree
git cat-file -s 0155eb42		# 71
git cat-file -p 0155eb42		
# 100644 blob fa49b077972391ad58037050f2a75f74e3671e92	new.txt
# 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a	test.txt

git cat-file -t 73a7f402		# commit
git cat-file -s 73a7f402		# 232
git cat-file -p 73a7f402
# tree 0155eb4229851634a0f03eb265b69f5a2d56f341
# parent 104778189118bbf75587062dc6fb61bbc9685a47
# author James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800
# committer James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800
# 
# second commit

如果我们想找回某个文件的历史版本,可以使用 reset 命令找回。

恢复test.txt到历史版本

git log test.txt                                                                                                                              
git reset 10477818 test.txt
git checkout -- test.txt

我们把找回后的test.txt移动到 bak 目录,并把根目录下的test.txt恢复最新版本

mkdir bak
mv test.txt bak/

git reset HEAD test.txt
git checkout -- test.txt
git add --all

把变更提交到暂存区后,我们惊异地发现,.git/objects 目录下竟没有任何变化。这是因为,尽管有更新,但是我们更新的文件内容在 .git/objects 目录下都已经存在了,故无需重新创建。然而,尽管blob对象没有变化,但是tree对象的变化还是很大的,但是由于我们还没有提交这个变更,故tree对象还没有创建。

新的tree对象的创建一般发生在commit提交之时。

.git
├── ...
├── objects
│   ├── 01
│   │   └── 55eb4229851634a0f03eb265b69f5a2d56f341
│   ├── 10
│   │   └── 4778189118bbf75587062dc6fb61bbc9685a47
│   ├── 1f
│   │   └── 7a7a472abf3dd9643fd615f6da379c4acb3e3a
│   ├── 73
│   │   └── a7f40207cd99c99e737456e1c94ad2d8632126
│   ├── 83
│   │   └── baae61804e65cc73a7201a7252750c76066a30
│   ├── d8
│   │   └── 329fc1cc938780ffdd9f94e0d364e0ea74f579
│   ├── fa
│   │   └── 49b077972391ad58037050f2a75f74e3671e92
│   ├── info
│   └── pack
└── ...

使用 git commit -m 'third commit' 提交当前的变更。

.git
├── ...
├── objects
│   ├── 01
│   │   └── 55eb4229851634a0f03eb265b69f5a2d56f341
│   ├── 10
│   │   └── 4778189118bbf75587062dc6fb61bbc9685a47
│   ├── 1f
│   │   └── 7a7a472abf3dd9643fd615f6da379c4acb3e3a
│   ├── 3c
│   │   └── 4e9cd789d88d8d89c1073707c3585e41b0e614
│   ├── 73
│   │   └── a7f40207cd99c99e737456e1c94ad2d8632126
│   ├── 83
│   │   └── baae61804e65cc73a7201a7252750c76066a30
│   ├── c8
│   │   └── d216df37ffa30f963a0c757661526e35014978
│   ├── d8
│   │   └── 329fc1cc938780ffdd9f94e0d364e0ea74f579
│   ├── fa
│   │   └── 49b077972391ad58037050f2a75f74e3671e92
│   ├── info
│   └── pack
└── ...

同样,这一次提交后,新增了2个对象,分别是当前的commit对象,和最新的tree对象。

git cat-file -t 3c4e9cd7 		# tree
git cat-file -s 3c4e9cd7		# 101
git cat-file -p 3c4e9cd7		
# 040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579	bak
# 100644 blob fa49b077972391ad58037050f2a75f74e3671e92	new.txt
# 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a	test.txt

git cat-file -t c8d216df		# commit
git cat-file -s c8d216df		# 231
git cat-file -p c8d216df		
# tree 3c4e9cd789d88d8d89c1073707c3585e41b0e614
# parent 73a7f40207cd99c99e737456e1c94ad2d8632126
# author James Zhan <zhiqiangzhan@gmail.com> 1425024419 +0800
# committer James Zhan <zhiqiangzhan@gmail.com> 1425024419 +0800
# 
# third commit

当前提交的的状态

tree_list

历史提交的状态

commit_list

分支操作

基本操作

使用命令 git checkout -b dev 切换到dev分支

.git
├── ...
├── HEAD
├── ...
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           ├── dev
│           └── master
├── ...
└── refs
    ├── heads
    │   ├── dev
    │   └── master
    └── tags

可以看到 .git/objects 目录中多了 .git/logs/refs/heads/dev.git/refs/heads/dev 两个文件,前者记录了当前分支下的操作日志,后者维护了当前分支最新commit的ref,即当前最新的commit值。通过checkout切换分支,本质上是改变 .git/HEAD 中的值。

cat .git/HEAD 		# ref: refs/heads/dev

cat .git/logs/refs/heads/dev
# 0000000000000000000000000000000000000000 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425025659 +0800	branch: Created from HEAD

cat .git/logs/refs/heads/master
# 0000000000000000000000000000000000000000 104778189118bbf75587062dc6fb61bbc9685a47 James Zhan <zhiqiangzhan@gmail.com> 1425023419 +0800	commit (initial): first commit
# 104778189118bbf75587062dc6fb61bbc9685a47 73a7f40207cd99c99e737456e1c94ad2d8632126 James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800	commit: second commit
# 73a7f40207cd99c99e737456e1c94ad2d8632126 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425024419 +0800	commit: third commit

cat .git/refs/heads/dev			# c8d216df37ffa30f963a0c757661526e35014978
cat .git/refs/heads/master 	# c8d216df37ffa30f963a0c757661526e35014978

我们再使用命令 git checkout master 切换回master分支。

cat .git/HEAD 		# ref: refs/heads/master

cat .git/logs/refs/heads/dev
# 0000000000000000000000000000000000000000 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425025659 +0800	branch: Created from HEAD

cat .git/logs/refs/heads/master
# 0000000000000000000000000000000000000000 104778189118bbf75587062dc6fb61bbc9685a47 James Zhan <zhiqiangzhan@gmail.com> 1425023419 +0800	commit (initial): first commit
# 104778189118bbf75587062dc6fb61bbc9685a47 73a7f40207cd99c99e737456e1c94ad2d8632126 James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800	commit: second commit
# 73a7f40207cd99c99e737456e1c94ad2d8632126 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425024419 +0800	commit: third commit

cat .git/refs/heads/dev			# c8d216df37ffa30f963a0c757661526e35014978
cat .git/refs/heads/master 	# c8d216df37ffa30f963a0c757661526e35014978

可以看出,这次切换,.git 目录中的内容变化并不大,除了.git/HEAD中的ref指向了master。

echo "hello world" >> test.txt
git commit -am "add more contents to test.txt from master."
cat .git/HEAD 		# ref: refs/heads/master

cat .git/logs/refs/heads/dev
# 0000000000000000000000000000000000000000 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425025659 +0800	branch: Created from HEAD

cat .git/logs/refs/heads/master
# 0000000000000000000000000000000000000000 104778189118bbf75587062dc6fb61bbc9685a47 James Zhan <zhiqiangzhan@gmail.com> 1425023419 +0800	commit (initial): first commit
# 104778189118bbf75587062dc6fb61bbc9685a47 73a7f40207cd99c99e737456e1c94ad2d8632126 James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800	commit: second commit
# 73a7f40207cd99c99e737456e1c94ad2d8632126 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425024419 +0800	commit: third commit
# c8d216df37ffa30f963a0c757661526e35014978 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152 James Zhan <zhiqiangzhan@gmail.com> 1425026678 +0800	commit: add more contents to test.txt from master.

cat .git/refs/heads/dev			# c8d216df37ffa30f963a0c757661526e35014978
cat .git/refs/heads/master 	# 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152

在master分支下提交变更后,.git/logs/refs/heads/dev.git/refs/heads/dev 中的内容并不会有任何变化。

git checkout dev
echo "foo bar" >> new.txt
git commit -am "add more contents to new.txt from dev."

echo "hello world" >> new.txt
git commit -am "add yet another more contents to new.txt from dev."
cat .git/HEAD 		# ref: refs/heads/dev

cat .git/logs/refs/heads/dev
# 0000000000000000000000000000000000000000 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425030192 +0800	branch: Created from HEAD
# c8d216df37ffa30f963a0c757661526e35014978 8e084ff2ac87fe1169f3ed751712c96fd73d410e James Zhan <zhiqiangzhan@gmail.com> 1425030425 +0800	commit: add more contents to new.txt from dev.
# 8e084ff2ac87fe1169f3ed751712c96fd73d410e 69d53d698802439dab1d82cc0e646987b0fab347 James Zhan <zhiqiangzhan@gmail.com> 1425031354 +0800	commit: add yet another more contents to new.txt from dev

cat .git/logs/refs/heads/master
# 0000000000000000000000000000000000000000 104778189118bbf75587062dc6fb61bbc9685a47 James Zhan <zhiqiangzhan@gmail.com> 1425023419 +0800	commit (initial): first commit
# 104778189118bbf75587062dc6fb61bbc9685a47 73a7f40207cd99c99e737456e1c94ad2d8632126 James Zhan <zhiqiangzhan@gmail.com> 1425023714 +0800	commit: second commit
# 73a7f40207cd99c99e737456e1c94ad2d8632126 c8d216df37ffa30f963a0c757661526e35014978 James Zhan <zhiqiangzhan@gmail.com> 1425024419 +0800	commit: third commit
# c8d216df37ffa30f963a0c757661526e35014978 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152 James Zhan <zhiqiangzhan@gmail.com> 1425026678 +0800	commit: add more contents to test.txt from master.

cat .git/refs/heads/dev			# 69d53d698802439dab1d82cc0e646987b0fab347
cat .git/refs/heads/master 	# 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152

同样,在dev下做任何变更,也不会影响 .git/logs/refs/heads/master.git/refs/heads/master 中的内容。

分支合并

merge
git merge master

git log --pretty=oneline
# b55d52d81d9a659b0951a5a40d96c92128a64b4e Merge branch 'master' into dev
# 69d53d698802439dab1d82cc0e646987b0fab347 add yet another more contents to new.txt from dev.
# 8e084ff2ac87fe1169f3ed751712c96fd73d410e add more contents to new.txt from dev.
# 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152 add more contents to test.txt from master.
# c8d216df37ffa30f963a0c757661526e35014978 third commit
# 73a7f40207cd99c99e737456e1c94ad2d8632126 second commit
# 104778189118bbf75587062dc6fb61bbc9685a47 first commit

git cat-file -p b55d52d8
# tree 4e8c7e178ff1d8fa549a293ea6812cc1e48e1436
# parent 69d53d698802439dab1d82cc0e646987b0fab347
# parent 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152
# author James Zhan <zhiqiangzhan@gmail.com> 1425031476 +0800
# committer James Zhan <zhiqiangzhan@gmail.com> 1425031476 +0800
# 
# Merge branch 'master' into dev
 

注意,通过merge的合并后的commit,它会有2个parent,整个提交历史会形成一个菱形。

master 1__ 2__3__4_____
       \      \
dev      \____5__6__7
rebase
git reset --hard 69d53d69
git rebase master

git log --pretty=oneline
# d9e7958e702247201a34fa2935a5bd2cd3bdde04 add yet another more contents to new.txt from dev.
# dce3d9d4df66a5550416da2d96d9ba6bd4d88a93 add more contents to new.txt from dev.
# 2d1d4a124abb2c8283f1bb2d3a361c70cb4b3152 add more contents to test.txt from master.
# c8d216df37ffa30f963a0c757661526e35014978 third commit
# 73a7f40207cd99c99e737456e1c94ad2d8632126 second commit
# 104778189118bbf75587062dc6fb61bbc9685a47 first commit

git cat-file -p d9e7958
# tree 4e8c7e178ff1d8fa549a293ea6812cc1e48e1436
# parent dce3d9d4df66a5550416da2d96d9ba6bd4d88a93
# author James Zhan <zhiqiangzhan@gmail.com> 1425031354 +0800
# committer James Zhan <zhiqiangzhan@gmail.com> 1425031732 +0800
# 
# add yet another more contents to new.txt from dev.

rebase 同样可以合并2个分支,但是和merge不同,拿 git rebase master 举例,rebase操作以master为基准,对于和master不同的commit,它会单独拿出来并重做这些commit,这样整个提交历史依然会是一条直线。

1__2__3__4__7__8

原来的提交5,6并没有丢失,只不过换做7,8重做了一次,由于是重做,时间变了,即便消息内容一样,commit的散列值也变化了。

merge vs. rebase

合并分支时,merge和rebase究竟选哪一个一直都存有争论,在这里并不像展开这些争论,只在此列出它们各自的优点(对方的劣势),供大家参考。

merge 的优点
  • 完整的记录所有提交的先后时间顺序。
  • 不会修改提交的散列值。
  • 在团队协作过程中,冲突解决比较容易。
rebase 的优点
  • 提交历史记录线性,非常干净。
  • 合并后commit非常干净,不会存在像merge那样合并时commit中一大堆文件更新的情况。

如果团队没有要求,大家按照自己的习惯去使用就好,不用太纠结。另外,如果本地当前分支的更新已经提交到远端库,则千万不要使用rebase,因为rebase会修改commit的散列值,会给其他协作者造成难以解决的冲突。

最后

Git 有超过100多个子命令,每个子命令又有不同的参数,而且很多时候,不同参数下的子命令操作的语义相差甚远,要完全记下来基本上是不可能完成的任务。我的建议是,先把基本命令掌握好,然后把高级命令过一遍,只需要达到将来碰到相应问题的时候,可以快速的寻找解决方案即可。学习的过程中有不明白的地方可以随时Google和参考Stackoverflow。

尽管 git 命令行功能强大,但是对于阅读某个项目的小部分代码,尤其是要频繁回到历史版本时不是太方便,如果能在浏览器上直接查看,将会方便许多,还好我们已经有了 GitHub 这样的利器,可以帮我们快速定位代码,但是如果你不想把代码分享出去,又没有私有库可以用,不妨可以使用 webgit 来查看本地代码。webgit是一个ruby工具,依赖于 ruby 环境,目前功能还不是很完善,感兴趣的同学可以来一起完善。