当执行Git命令时,Git做了什么
认识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文件。
tree
tree 类似于文件系统中的目录,他能指向或包含:
- git blob 对象记录
- 其他 git 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
-
本次提交的注释或备注。
tag
git tag对象比较特殊,相当于别名的作用,我们可以使用它对commit,tree,blob对象创建别名。一般情况下,如果我们不写tag的描述,则不会在.git/objects目录中新增tag object对象,只会在.git/refs/tags下新增一个ref的记录。只有在tag时指定了-m并填写了描述信息时,它才会真正生成一个git tag对象。它一般包如下信息:
- 所指向对象的HASH值。
- 所指向对象的类型。
- tag名称。
- tagger(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
当前提交的的状态
历史提交的状态
分支操作
基本操作
使用命令 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 环境,目前功能还不是很完善,感兴趣的同学可以来一起完善。