前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >git分支

git分支

作者头像
mwangblog
发布2018-07-04 14:43:54
1.3K0
发布2018-07-04 14:43:54
举报
文章被收录于专栏:mwangblogmwangblog

什么是git分支?

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

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

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

三个对象及其关系

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

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

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

指向提交对象的heads

创建分支

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

代码语言:javascript
复制
$ git branch
* master
$ git branch -v
* master 57b75e6 Add GitHub description.

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

用图简略表示如下:

仅有master分支

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

代码语言:javascript
复制
$ git branch dev
$ git branch
  dev
* master

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

创建一条dev分支

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

代码语言:javascript
复制
$ 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 <branchname>命令,在切换前请确保你的工作目录是干净的:

代码语言:javascript
复制
$ git checkout dev
Switched to branch 'dev'

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

代码语言:javascript
复制
$ git branch
* dev
  master
$ git log --oneline --decorate -357b75e6 (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的指向。目前分支情况图示如下:

切换到dev分支

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

代码语言:javascript
复制
$ git branch <branchname>
$ git checkout <branchname>

“快进”合并

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

代码语言:javascript
复制
$ 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

查看一下各个分支所指:

代码语言:javascript
复制
$ 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,其他分支并没有更改,图式如下:

dev新提交

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

切换回master

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

代码语言:javascript
复制
$ 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(快进),此时分支情况如下:

合并dev分支

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

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

代码语言:javascript
复制
$ git merge --no-ff dev

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

代码语言:javascript
复制
$ git branch -d dev
Deleted branch dev (was fd2e1cb).

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

代码语言:javascript
复制
$ git branch -D <branchname>

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

代码语言:javascript
复制
$ git branch --merged       # 查看已经被合并的分支$ git branch --no-merged    # 查看还没有被合并的分支

目前分支情况如下:

删除dev分支

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

快进合并

分支合并

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

代码语言:javascript
复制
$ git checkout -b testing
Switched to a new branch 'testing'

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

代码语言:javascript
复制
$ 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

代码语言:javascript
复制
$ 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有一个新的提交。怎样在命令行查看呢?

代码语言:javascript
复制
$ 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分支。图示如下:

合并提交1

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

代码语言:javascript
复制
$ 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.这个新提交拥有两个父提交。

合并提交2

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

代码语言:javascript
复制
$ git branch -d testing 
Deleted branch testing (was 40a00ae).

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

分之合并

冲突解决

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

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

代码语言:javascript
复制
$ 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修改如下并且提交:

代码语言:javascript
复制
$ 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分支:

代码语言:javascript
复制
$ 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来查看:

代码语言:javascript
复制
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

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

    both modified:   README.md

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

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

代码语言:javascript
复制
$ 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为我们标记的冲突的部分:

代码语言:javascript
复制
<<<<<<< HEAD
I LOVE GIT.
=======
Git is a free and open source distributed version control system.>>>>>>> iss1

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

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

代码语言:javascript
复制
I LOVE GIT.

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

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

代码语言:javascript
复制
$ 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

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

现在提交即可:

代码语言:javascript
复制
$ 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分支:

代码语言:javascript
复制
$ git branch -d iss1
Deleted branch iss1 (was d6801d6).

储藏与清理

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

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

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

代码语言:javascript
复制
$ 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命令可以查看储藏的列表:

代码语言:javascript
复制
$ git stash liststash@{0}: WIP on master: 11f0f7a merge iss1

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

代码语言:javascript
复制
$ 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 <file>..." to update what will be committed)
  (use "git checkout -- <file>..." 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 <stashname>删除相应的储藏,如果<stashname>为空,则会删除最新的储藏:

代码语言:javascript
复制
$ git stash drop
Dropped refs/stash@{0} (939ab1d7c4f88fe2dd9b3420d0cf919a668eff23)
$ git stash list
$ 

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 mwangblog 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是git分支?
  • 创建分支
  • 切换分支
  • “快进”合并
  • 分支合并
  • 冲突解决
  • 储藏与清理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档