GIT简单解析
commit对象,tree,blob
首先blob对象表示文件的快照内容,git并不保存具体的修改,而是保存所有经过修改的文件的快照内容,一次提交的文件的索引由一个tree保存,commit对象是每次提交的具体内容。
每个commit都有其父对象的指针(第一个除外),每次新建一个提交对象前git会先计算父对象的校验和,然后将文件目录转换为tree对象,之后的创建的提交对象除了提交内容外还包含此处的tree对象的指针,如此它就可以在将来需要的时候,访问父对象中未经修改的文件来重现此次快照的内容了。
实例分析
那么我们按以下步骤建立一个git仓库
1. 使用git init新建一个仓库
2. 在仓库下新建Readme.TXT
3. 使用git add . 和git commit -m “”提交修改
4. 向Readme中添加内容
5. 再次提交修改
6. 使用git log输出仓库信息
最后的结果是这样的:
此时我们的仓库中共有两个blob对象(都是Readme.txt,但内容不一样),分别包含这两个blob对象的指针的tree,以及两个包含具体提交信息和对应的tree的指针的commit对象。
内部的结构大概像这样:
first commit | ← | second commit |
↓ | ↓ | |
tree1 | tree2 | |
↓ | ↓ | |
blob1 | blob1 |
GIT分支与创建
master分支
git中的分支本质上是指向commit的指针(指我们所接触到的master分支或者自定义的分支,并不是commit对象所组成的分叉结构),比如默认的master分支,它默认指向你最后一次的提交对象,并在每一次提交后自动向前移动。
master | ||
↓ | ||
first commit | ← | second commit |
↓ | ↓ | |
tree1 | tree2 | |
↓ | ↓ | |
blob1 | blob1 |
创建分支
我们使用git branch来创建分支,例如
git branch MyBranch
它会在在当前commit对象上新建一个分支指针
master | ||
↓ | ||
first commit | ← | second commit |
↑ | ||
MyBranch |
HEAD指针
现在我们在一个仓库中拥有了多个分支,但我们目前任然在master分支上,这是因为git通过HEAD指针来访问当前所选的分支:
HEAD | ||
↓ | ||
master | ||
↓ | ||
first commit | ← | second commit |
↑ | ||
MyBranch |
之前我们使用git branch创建了分支,而HEAD指针并不会移动到新建的分支上,要切换分支需要执行git checkout命令,我们运行:
git checkout testing
master | ||
↓ | ||
first commit | ← | second commit |
↑ | ||
MyBranch | ||
↑ | ||
HEAD |
对不同分支进行提交
我们在仓库中创建新文件MyFile.txt,然后提交(现在HEAD指向MyBranch分支):
git add .
git commit -m “MyBranch commit”
现在仓库中的结构是这样的:
master | ||||
↓ | ||||
first commit | ← | second commit | ← | MyBranch commit |
↑ | ||||
MyBranch | ||||
↑ | ||||
HEAD |
让我们回到master分支:
git checkout master
此时git将仓库中的文件恢复到master所指向的second commit状态,可以看到MyFile.txt消失了,但不用担心,MyBranch commit仍然保存着MyFile.txt文件。
接下来我们常创建masterFile.txt,然后提交。
git add .
git commit -m “master commit”
此时我们仓库的结构出现了分叉:
HEAD | ||||
↓ | ||||
master | ||||
↓ | ||||
↙ | master commit | |||
first commit | ← | second commit | ||
↖ | MyBranch commit | |||
↑ | ||||
MyBranch |
此时我们可以轻松地在两条分支之间来回切换,而这一切只需要–branch 和 checkout两条命令即可完成。
分支合并
在相同线路上的两条分支的合并
先来说一个简单情况下的合并:
首先我们确保我们在master分支上:
git checkout master
git回复我们Already on ‘master’,那么创建一个新的分支master_issue,假设我们在这条分支中开始修复某些问题。
git checkout -b master_issue
添加-b表示若分支不存在则创建分支,此处相当于合并了branch 和 checkout两条命令。
我们对masterFile的内容进行修改并提交为”issue commit”。此时仓库的结构如下所示:(省略MyBranch部分)
HEAD | ||||||
↓ | ||||||
master | master_issue | |||||
↓ | ↓ | |||||
first commit | ← | second commit | ← | master commit | ← | issue commit |
我们将HEAD移动到master上,使用git merge来合并分支。
git checkout master
git merge master_issue
此时:(省略MyBranch部分)
HEAD | ||||||
↓(HEAD指向master_issue ) | ||||||
master/master_issue | ||||||
↓ | ||||||
first commit | ← | second commit | ← | master commit | ← | issue commit |
如果顺着一个分支可以走到另一分支上的话,git只会简单的移动分支指针,这种单线的合并不需要解决分歧,所以操作起来非常简单。
此时master和master_issue分支指向同一个commit对象,我们可以使用branch的-d选项来删除无用分支:
git branch -d master_issue
在不同线路上的两条分支的合并
我们先备份一个仓库,直接把仓库所在文件夹复制一份就好。
无矛盾的情况
在现在的仓库中一共有两条分支master和MyBranch,那么现在我们合并这两条分支:
git checkout master
git merge MyBranch
HEAD | ||||||||
↓ | ||||||||
master | ||||||||
↙ | master commit | ← | issue commit | ↖ | ↓ | |||
first commit | ← | second commit | new commit | |||||
↖ | ← | MyBranch commit | ← | ↙ | ||||
↑ | ||||||||
MyBranch |
git会在合并时用分支的共同祖先和分支(即second commit、issue commit与MyBranch commit)进行一次三方合并计算,并建立新的commit对象,此次合并出的commit会包含issue commit中的masterFile和MyBranch commit中的MyFile,且它有两个祖先。
通过git log可以看到master分支可以访问到之前所有的提交记录:
矛盾的情况
首先我们来制造一个矛盾的修改,之前在issue commit中我们修改了masterFile的内容,现在我们来到MyBranch commit中创建masterFile,并向其添加不同的内容。
git add .
git commit -m “add masterFile”
之后合并两条分支:
git merge master
我们得到了如下提示:
git并没有提交这次合并,除非我们解决冲突,我们使用git status来查阅合并冲突:
可以看到git提示我们在masterFile文件中发生了冲突,此时masterFile文件的内容如下:
<<<<<<< HEAD
11111111111111
=======
233333sad
>>>>>>> master
HEAD表示当前分支也就是MyBranch分支,’=======’下面是master分支的内容,我们可以直接对文件进行修改(可以是全新的内容)。
masterFile:
something
然后进行提交即可:
git add .
git commit -m “MyMerge”