git分支

什么是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显示分支指向提交对象的校验和及其描述:

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

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

用图简略表示如下:

仅有master分支

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

$ git branch dev
$ git branch
  dev
* master

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

创建一条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 <branchname>命令,在切换前请确保你的工作目录是干净的:

$ git checkout dev
Switched to branch 'dev'

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

$ 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>分支并且切换到它,相当于执行下面两条命令:

$ git branch <branchname>
$ git checkout <branchname>

“快进”合并

现在在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,其他分支并没有更改,图式如下:

dev新提交

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

切换回master

合并分支使用git merge <branchname>命令,这个命令将<branchname>分支合并到当前分支,现在我们在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(快进),此时分支情况如下:

合并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 <branchname>

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

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

目前分支情况如下:

删除dev分支

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

快进合并

分支合并

现在创建一个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分支。图示如下:

合并提交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.这个新提交拥有两个父提交。

合并提交2

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

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

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

分之合并

冲突解决

如果在不同分支中同一个文件的同一个地方做了修改,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 <file>..." 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 liststash@{0}: WIP on master: 11f0f7a merge iss1

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

$ 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>为空,则会删除最新的储藏:

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

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

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

原文发布于微信公众号 - mwangblog(mwangblog)

原文发表时间:2018-01-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jerry的SAP技术分享

如何实现Windows宿主系统和虚拟机ubuntu系统文件互相访问

我的宿主操作系统是Windows 10,使用Oracle的Virtual Box安装了Ubuntu。

13030
来自专栏Android自学

解决wordpress搬家后,主题、插件升级时出现“无法创建目录”的问题

21830
来自专栏葡萄城控件技术团队

Winform文件下载之WinINet

在C#中,除了webclient我们还可以使用一组WindowsAPI来完成下载任务。这就是Windows Internet,简称 WinINet。本文通过一个...

24480
来自专栏王亚昌的专栏

妥善处理解决网络I/O瓶颈

Linux 异步 I/O 是 Linux 内核中提供的一个相当新的增强。它是 2.6 版本内核的一个标准特性,但是我们在 2.4 版本内核的补丁中也可以找到它。...

45130
来自专栏深度学习与计算机视觉

Python 上下文管理器

TensorFlow的运行模型—session(会话),用来执行定义好的运算,会话拥有并管理TensorFlow程序运行时的所有资源,所以当运算结束后需要对资源...

21590
来自专栏公有云大数据平台弹性 MapReduce

Hbase Region Split compaction 过程分析以及调优

Hbase以高并发写入而闻名,而Compact和Split功能贯穿了hbase的整个写入过程,而只有掌握了Compact和Split内部逻辑以及控制参数才能根据...

2K00
来自专栏JavaEdge

GET和POST到底啥区别???

最普遍的答案 我一直就觉得GET和POST没有什么除了语义之外的区别,自打我开始学习Web编程开始就是这么理解的。 可能很多人都已经猜到了,他要的答案是:

12720
来自专栏Rgc

Flask即插视图与tornado比较

由于公司使用了Tornado框架和Flask框架,之前一直使用的都是Flask框架,已经对url下面紧跟着视图的写法很固执。刚开始接触Tornado框架,对于其...

45420
来自专栏蓝天

使用异步I/O大大提高应用程序的性能

aio_return 异步 I/O 和标准块 I/O 之间的另外一个区别是我们不能立即访问这个函数的返回状态,因为我们并没有阻塞在 read 调用上。在标...

11520
来自专栏Spark学习技巧

深入了解HBase架构

24120

扫码关注云+社区

领取腾讯云代金券