分支是git中最容易被误解的概念之一,虽然git分支并不难理解。使用分支时候是不有点不知所措,"老虎吃天,无处下爪"的感觉?还有那一系列的merge和rebase黑魔法操作,甚至是那些许的冲突都曾让你头痛不已?
本文中,虫虫将带你回到git王国,通过实例理解git分支的概念及分支相关的操作,在你阅读这篇文章后,希望能了解分支是怎么一回事,知道怎么灵活使用分支来解决工作中遇到的痛点,而不再是一头雾水,懵懂不知所措。
什么是分支?
要明白什么是分支,我们从经典的git图像开始,类似图片估计大家都见到过:
图上显示有三个分支Master、Dev和Chongchong。
其中Master是默认就存在的一个分支,我们叫它主分支。一般很多人会觉得这个Master系统保留的分支应该有其特殊之处,但是实际上Master和其他分支一样没有啥特殊之处,无非他是必须有的分支,这个名称也可以通过git远程仓库修改默认配置,改为阿猫阿狗都没有问题。这个分支也可以删,但是需要特殊设置下。
分支其实上就是一个指针
简而言之,在git中分支只是一个指向单个commit的指针。当然,这个指针只是用来表明这个commit和base分支的之间的偏差。从现在开始,本文中,当我们要指明指针的时候,我们将使用分支引用(branch reference),而我们要指明偏差时候用分支。
我们说分支引用除了指定某个基本偏差的commit外,还有一些其他特性,可以让我无缝高效实现一些git高级操作,比如merge和rebase。
在git中为什么分支操作非常高效而无其他资源负担的缘故就是因为他不过是个指针。这也是和svn等分支(独立目录)的根本区别之一。
git分支引用详解
git的版本历史通过一个的commit往前推进存储。而分支引用则是相反从后王前引用的,如下图所示:
从图上,我们可以看到每一个commit的引用,引用最先指向的是1。通过遍历每个分支,我们可以看出最后的通常commit是4(然后分支出了branch-1)。
分支引用的其他特性?
有什么东西在这里失踪了吗?确定分支是对单个提交的引用,但是当我们进行commit或者在分支内hard reset时候,相应的分支引用会发生什么变化呢?
下面我们实例展示一下,假设我们给branch-1做一个commit:
git checkout branch-1
echo "## 添加新的行" >> README.md
git add README.md
git commit -m "添加一个新行"
然我们用交互方式解释一下以上各个命令:
"跳到branch-1分支"
"向README.md文件添加一行,内容为markdown二号标题添加新的行"
"将这些变化放入暂存区域"
"目前branch-1分支,提交变化给branch-1,并将引用指向新的commit"
另一方面,关于hard reset的操作则更容易:
> git checkout branch-1
> git reset --hard HEAD~1
这更容易解释:
"我们在branch-1分支中,取消当前的提交,把引用指向前一个commit"。
Rebase操作
Rebase是我们在需要改变提交历史时候用的操作,主要在以下两种情况时候用:
1、改写commit信息,增加squash,删除或重新组织commit。当有意地将偏差点指向分支引用,则可以改变整个分支的历史。
2、更改分支开始点,偏离点。改变后,整个分支提交历史和偏差点都会改变,因为起点都变了。
下面我们以下图的一个repo图为实例,做操作演示
改写branch-1分支的基础
我们以最简单的场景开始,改变我们分支的基础。我们的目标是使branch-1从主分支的最新提交(master/commit 8)开始。
为此,我们只需要指定一个commit或分支引用,然后分支的所有commit将被重新应用在这个提交的基础上。
首先我们用git log命令看下当前的提交历史
git log --pretty --graph --oneline --all
切换到分支1
git checkout brach-1
以master为基础rebase
git rebase master
需要手动解决下冲突。
继续
git rebase -continue
rebase后的提交历史
git log --pretty --graph --oneline --all
可以对比,rebase后可以看到comit 5和7的哈希值变了。因为rebase后commit 5的祖先发生了变化,并且由于提交是不可变的,所以我们必须创建一个新提交,同样适用于以下提交。
改写分支历史
我们再实例展示下改变brach-1分支的提交历史。
假设我们要重命名commit 5的注释为"chongchong"。因为要更改特定的commit,我们必须使用交互模式。在交互模式中,我们为分支设置基础(commit 4)并让它保持不变,并在我们的文本编辑器中触发提示,以选择我们想要基础。
那么让我们来做一下互动式rebase:
git checkout brach-1
git rebase -i 5b64297
在这个例子中,我们将使用reword命令,其他各个命令建议自己测试练习。
r 5b64297 5
pick 369b38f 7
提交修改改的两行,然后用
git commit --amend
在编辑器中输入我们要修改的新commit注释,本例中为 "chongchong"
chongchong
git rebase -continue
完成rebase过程。
git log查看历史,结果如下:
我们通过上图可以看到,每一次改变都相当于新建一次commit,而且以此为基础继承于它之后commit都会改变(本例的commit 7哈希变化了)。
Merge操作
Merge与rebase操作的处理方式完全不同,实际上merge比rebase更简单。假设,我们想要想在分支branch-1添加master中的变化(6,8)变化,不用rebase提交历史变为7结束。这种情况下使用merge,而在我们的日常实践中也是merge更常用些。
merge有两种方法:快进和非快进。快进是将分支的指针移动到我们想要合并的分支。快进方法有时候会不能用,比如我们上面的列子中就不能将branch-1移动到master提交,因为这样会导致commit 5和7丢失。当然如果已经rebase分支到主分支顶部的,则可以用将快进方法将branch-1合并到主分支。
合并操作也很简单,我们继续前面的例子,实例展示:
git checkout master
git merge branch-1 -ff
快速合并之后的git图形
让我们回到我们初始时候git记录中
让我们使用快速merge 和并master合并branch-1中:
git merge master --ff-only
如预期的那样是无法快进合并。我们用一般方法合并:
git merge master
git log --pretty --graph --oneline -all
如上图所示,改操作创建了一个新的commit(68476ab),这个提交很特别,它有2个祖先(4和8),它被称为合并提交。
现在我们的git历史图示将如下所示:
Merge vs Rebase
当我们想要反映另一个分支的变化时,我们是使用rebase还是merge呢?这个问题没有统一的概念,按照个人喜好以及具体的工作场景:
如果你工作在公共分支中,你和基友们在同时协同工作,那么就使用merge,因为它以时间顺序保留了所有的commit,所有的变更都很清晰并将新变化最后叠加到最后一次提交的顶部。
如果你在你保持在你特有分支中工作,rebase会保持你的线性历史,然后, 当将你的分支合并的时候,可以保持你的master更加清晰,简练。
举报/反馈

虫虫搜奇

5884获赞 1.2万粉丝
世界真奇妙,虫虫带你去搜奇
科技领域创作者
关注
0
0
收藏
分享