git分支

什么是git分支?

什么是git分支?首先让我们回顾一下提交对象,一个提交对象(commit objects)包括:

  • 一系列文件在某个时间的快照。
  • 一系列指向父提交对象的索引。
  • 一个SHA-1名字,这个名字40个字符长,是独一无二的。
  • 作者的姓名和邮箱,以及提交时对提交的描述。

事实上,“一系列文件在某个时间的快照”并不是直接存在于提交对象。在git中,blob对象保存着文件的快照,树对象保存着目录结构和blob对象的索引,而提交对象保存指向树对象的指针。下图是一个这三者关系的示意图:

git分支_第1张图片
三个对象及其关系

那么git中的分支是什么呢?

git中的分支就像是你的文件的一份副本,你可以在需要的时候拷贝一份出来,这样你就得到了一个“分支”,你可以在上面修改,修改完成之后再合并回去。在一些版本控制软件中实际情况确实是这样,然而在git中并非如此

在git中,对分支的操作大部分只是在修改指向提交对象的heads。我们知道,heads是一个指向提交对象的指针,分支操作中的大部分操作只需要修改heads的指向,即向heads文件中写入41个字符即可(40个SHA-1字符串和1个换行符)。与其他一些版本控制软件采用的复制文件策略相比较,git分支操作与文件大小无关,操作迅速快捷。

git分支_第2张图片
指向提交对象的heads

创建分支

现在先来看看我们在哪个分支,使用git branch命令查看当前分支,命令选项-v显示分支指向提交对象的校验和及其描述:

$ git branch
* master
$ git branch -v
* master 57b75e6 Add GitHub description.

从结果中看到,现在只有一个分支,叫做master*表示当前所在的分支,即HEAD的指向。

用图简略表示如下:

git分支_第3张图片
仅有master分支

现在创建一条dev分支,使用git branch 命令:

$ git branch dev
$ git branch
  dev
* master

现在有了两条分支:masterdev,目前我们在master分支。图示如下:

git分支_第4张图片
创建一条dev分支

可见,事实上只是创建了一个指向图中提交对象C3的指针,使用git log --decorate可以查看heads的指向:

$ git log --oneline --decorate -3
57b75e6 (HEAD -> master, origin/master, dev) Add GitHub description.
beac1f4 make README.md more friendly.
14bd627 add two wrong line to README.md

master、远程originmasterdev指向57b75e6提交对象,HEAD指向master

切换分支

现在切换到dev分支,使用git checkout 命令,在切换前请确保你的工作目录是干净的:

$ git checkout dev
Switched to branch 'dev'

这样就切换到了dev分支,查看一下:

$ git branch
* dev
  master
$ git log --oneline --decorate -3
57b75e6 (HEAD -> dev, origin/master, master) Add GitHub description.
beac1f4 make README.md more friendly.
14bd627 add two wrong line to README.md

可以看到我们确实在dev分支,HEAD确实指向了dev分支。在切换分支时,git会将分支所指向的提交对象的文件快照检出到工作目录,并且更改HEAD的指向。目前分支情况图示如下:

git分支_第5张图片
切换到dev分支

git checkout -b 可以创建分支并且切换到它,相当于执行下面两条命令:

$ git branch 
$ git checkout 

“快进”合并

现在在dev分支,我们创建一个dev.md文件并且提交:

$ touch dev.md
$ git add dev.md
$ git commit -m "add dev.md"
[dev fd2e1cb] add dev.md
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 dev.md

查看一下各个分支所指:

$ git log --oneline --decorate -3
fd2e1cb (HEAD -> dev) add dev.md
57b75e6 (origin/master, master) Add GitHub description.
beac1f4 make README.md more friendly.

dev前进了一个提交对象,HEAD指向dev,其他分支并没有更改,图式如下:

git分支_第6张图片
dev新提交

现在切换到master,使用$ git checkout master命令,HEAD会指向master,工作目录中的文件将会被替换:

git分支_第7张图片
切换回master

合并分支使用git merge 命令,这个命令将分支合并到当前分支,现在我们在master分支,执行下面的命令将dev分支合并到master分支:

$ git merge dev
Updating 57b75e6..fd2e1cb
Fast-forward
 dev.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 dev.md

git告诉我们此次合并的方式是Fast-forward(快进),此时分支情况如下:

git分支_第8张图片
合并dev分支

现在dev分支已经被合并到master分支了。从上图可以看出git仅仅是简单的更新了masterHEAD的指向,这是由于合并前master指向dev的直接上游,这种合并方式叫做快进(Fast-forward)。

可以使用--no-ff选项避免使用“快进”合并,这样会形成一个新的合并提交,类似下节讲到的分之合并:

$ git merge --no-ff dev

现在dev分支已经充分得发挥了自己的作用,让我们删除它:

$ git branch -d dev
Deleted branch dev (was fd2e1cb).

如果一个分支没有完全合并到当前分支,那么git会阻止你删除它,如果确实要删除它,使用-D命令选项:

$ git branch -D 

如果想要知道那些分支被合并了或者没有合并,使用下面的命令:

$ git branch --merged       # 查看已经被合并的分支
$ git branch --no-merged    # 查看还没有被合并的分支

目前分支情况如下:

git分支_第9张图片
删除dev分支

本文所讲的例子整体过程图示如下:

git分支_第10张图片
快进合并

分支合并

现在创建一个testing分支并且切换到该分支:

$ git checkout -b testing
Switched to a new branch 'testing'

添加testing.md并提交,修改tesing.md并提交:

$ touch testing.md
$ git add testing.md
$ git commit -m "add testing.md"
[testing dd4555e] add testing.md
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testing.md
$ echo "A file added in testing branch." > testing.md 
$ git commit -a -m "add description of testing.md"
[testing 40a00ae] add description of testing.md
 1 file changed, 1 insertion(+)

回到master分支并且修改dev.md

$ git checkout master 
Switched to branch 'master'
$ echo "A dev file." > dev.md
$ ls
dev.md  README.md
$ git commit -a -m "add description of dev.md"
[master 1b63c87] add description of dev.md
 1 file changed, 1 insertion(+)

现在两条分支在分叉后都有新的提交:testing有两个新的提交,master有一个新的提交。怎样在命令行查看呢?

$ git log --oneline --decorate --graph --all
* 1b63c87 (HEAD -> master) add description of dev.md
| * 40a00ae (testing) add description of testing.md
| * dd4555e add testing.md
|/  
* fd2e1cb add dev.md
* 57b75e6 (origin/master) Add GitHub description.

# 省略

可以看到,在fd2e1cb分支分叉,testing之后进行了两次提交,master进行了一次提交,目前我们在master分支。图示如下:

git分支_第11张图片
合并提交1

现在将testing分支合并到master分支:

$ git merge testing
Merge made by the 'recursive' strategy.
 testing.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 testing.md
$ git log --oneline --decorate --graph --all
*   8425ef2 (HEAD -> master) Merge branch 'testing'
|\  
| * 40a00ae (testing) add description of testing.md
| * dd4555e add testing.md
* | 1b63c87 add description of dev.md
|/  
* fd2e1cb add dev.md
* 57b75e6 (origin/master) Add GitHub description.

# 省略

现在git帮我们合并了mastertesting,并且生成了一个新的提交(你可能需要填写提交描述),这个新提交的SHA-1校验和前七位是8425ef2。

git能够帮我们自动合并,而不会产生冲突的原因是我们在不同的分支中修改了不同的文件,此时git会参考两个分支所指的快照(testing40a00aemaster1b63c87)和两个分支的共同祖先(fd2e1cb),自动合并。参考的三个快照分别相当于下图的C6、C7和C4.

新生成的提交叫做合并提交,相当于下图的C8.这个新提交拥有两个父提交。

git分支_第12张图片
合并提交2

好了,现在删掉testing分支吧:

$ git branch -d testing 
Deleted branch testing (was 40a00ae).

本文所讲的分支合并的整体过程图示如下:

git分支_第13张图片
分之合并

冲突解决

如果在不同分支中同一个文件的同一个地方做了修改,git就无法干净利落地合并它们。

创建一个新的分支iss1,在iss1分支中将README.md修改如下并且提交:

$ git checkout -b iss1
Switched to a new branch 'iss1'
$ vim README.md 
$ cat README.md 
# Hi, Git!

This is my first git project and i use it to learn git.

Git is a free and open source distributed version control system.
$ git commit -a -m "change README.md in iss1"
[iss1 d6801d6] change README.md in iss1
 1 file changed, 6 deletions(-)

切换到master分支,将README.md修改如下并且提交:

$ git checkout master
Switched to branch 'master'
$ vim README.md 
$ cat README.md 
# Hi, Git!

This is my first git project and i use it to learn git.

I LOVE GIT.
$ git commit -a -m "change README.md in master."
[master 63172f9] change README.md in master.
 1 file changed, 1 insertion(+), 7 deletions(-)
 $ git log --oneline --decorate --graph --all
* 63172f9 (HEAD -> master) change README.md in master.
| * d6801d6 (iss1) change README.md in iss1
|/  
*   8425ef2 Merge branch 'testing'

# 省略

现在将iss1分支合并到master分支:

$ git merge iss1
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

git告诉我们说自动合并失败,原因是在README.md文件中有冲突,并且提醒我们解决冲突后提交结果。

也就是说,git在遇到冲突时,并不会创建一个合并提交,而是暂停下来,等用户解决冲突之后,由用户提交。

含有冲突的文件被标记为“未合并”(unmerged)状态,随时可以使用git status来查看:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add ..." to mark resolution)

    both modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

现在让我们解决README.md中的冲突,首先来看一看git刚刚所做的工作:

$ cat README.md 
# Hi, Git!

This is my first git project and i use it to learn git.

<<<<<<< HEAD
I LOVE GIT.
=======
Git is a free and open source distributed version control system.
>>>>>>> iss1

其中的一部分是git为我们标记的冲突的部分:

<<<<<<< HEAD
I LOVE GIT.
=======
Git is a free and open source distributed version control system.
>>>>>>> iss1

=======的上半部分的是HEAD分支中的文件内容,在其下半部分的是iss1分支中文件的内容。

现在让我们将这部分修改如下:

I LOVE GIT.

这表示将丢弃iss1中的修改,当然你可以根据自己的喜好更改,你可以改成任意你需要的内容。

现在将文件添加到暂存区,并且查看状态:

$ git add README.md 
$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

nothing to commit, working directory clean

可见,一旦冲突文件被添加到暂存区,它的“未合并”状态就会被解除,即表示冲突已经解决。

现在提交即可:

$ git commit -m "merge iss1"
[master 11f0f7a] merge iss1
$ git log --oneline --decorate --graph --all
*   11f0f7a (HEAD -> master) merge iss1
|\  
| * d6801d6 (iss1) change README.md in iss1
* | 63172f9 change README.md in master.
|/  
*   8425ef2 Merge branch 'testing'

# 省略

最后删除iss1分支:

$ git branch -d iss1
Deleted branch iss1 (was d6801d6).

储藏与清理

git在切换分支时必须保证当前工作目录是干净的,如果现在做了一点更改,不至于提交一次新的更新,但是却必须更换到另一条分支上,怎么办呢?

git为我们提供了stash(储藏)工具。

现在在master分支上对README.md作一些更改,并且将它储藏起来:

$ git status -s
 M README.md
$ git stash 
Saved working directory and index state WIP on master: 11f0f7a merge iss1
HEAD is now at 11f0f7a merge iss1
$ git status -s
$ 

在运行git stash之后工作目录就变干净了,现在就可以切换到其他分支工作啦。

在其他分支工作完之后,又回到master,怎样继续工作呢?

使用git stash list命令可以查看储藏的列表:

$ git stash list
stash@{0}: WIP on master: 11f0f7a merge iss1

使用git stash apply 即可应用,如果为空,则会应用最新的储藏:

$ git stash apply
$ git stash list
stash@{0}: WIP on master: 11f0f7a merge iss1
$ git stash apply
On branch master
Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git status -s
 M README.md

我们的更改又回来了,使用git stash drop 删除相应的储藏,如果为空,则会删除最新的储藏:

$ git stash drop
Dropped refs/stash@{0} (939ab1d7c4f88fe2dd9b3420d0cf919a668eff23)
$ git stash list
$ 

可以使用git stash pop直接应用最新的储藏,同时删除该储藏。

在git中,可以进行多次储藏,也可以在不同的分支应用储藏。

你可能感兴趣的:(git分支)