文章目录
使用分支你可以把你的工作从开发主线上分离开来,以免影响开发主线
分支简介
我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。
git add README test.rb LICENSE
git commit -m 'The initial commit of my project'
当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在 Git 仓库中这些校验和保存为树对象。 随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。
现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 它会在每次的提交操作中自动向前移动。
创建分支
Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分支, 你需要使用 git branch 命令:
git branch testing
这会在当前所在的提交对象上创建一个指针。 那么,Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD 的特殊指针,指向当前所在的本地分支(译注:将 HEAD 想象为当前分支的别名)。 在本例中,你仍然在 master 分支上。 因为 git branch 命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。
现在我们来看下分支情况git log --oneline --decorate
或者使用
git branch
切换分支
切换分支命令
要切换到一个已存在的分支,你需要使用 git checkout
命令。 我们现在切换到新创建的 testing 分支去:
git checkout testing
这样 HEAD 就指向 testing 分支了。
切换分支的好处
那么,这样的实现方式会给我们带来什么好处呢? 现在不妨再提交一次:
vi file.txt
git add .
git commit -m "testing 分支第一次提交,共提交四次“
现在的分支图如下
如图所示,你的 testing 分支向前移动了,但是 master 分支却没有,它仍然指向运行 git checkout 时所指的对象。 这就有意思了,现在我们切换回 master 分支看看:
git checkout master
这条命令做了两件事。 一是使 HEAD 指回 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。 也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发
分支切换会改变你工作目录中的文件,在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果你当前分支的工作目录有修改过的内容未提交,git将禁止切换分支.
我们不妨再稍微做些修改并提交:
vi file.txt
git add .
git commit -m "master 第四次提交"
现在,这个项目的提交历史已经产生了分叉。 因为刚才你创建了一个新分支,并切换过去进行了一些工作,随后又切换回 master 分支进行了另外一些工作。 上述两次改动针对的是不同分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。
简单查看当前分支历史
它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况
git log --oneline --decorate --graph --all
小案例
让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。
-
开发某个项目。
-
为实现某个新的需求,创建一个分支。
-
在这个分支上开展工作。
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:
-
切换到你的主分支。
-
为这个紧急任务新建一个分支,并在其中修复它。
-
在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到主分支。
-
切换回你最初工作的分支上,继续工作。
项目中的主分支情况
首先,我们假设你正在你的项目上工作,并且已经有一些提交。
新建分支,处理工作
现在,你已经决定要解决你的公司使用的问题追踪系统中的 #53 问题。 想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b 参数的 git checkout
命令:
git checkout -b iss53
#该命令为以下两条命令的简写
#git branch iss53
#git checkout iss53
不断提交工作内容
你继续在 #53 问题上工作,并且做了一些提交。 在此过程中,iss53 分支在不断的向前推进,因为你已经检出到该分支(也就是说,你的 HEAD 指针指向了 iss53 分支)
处理紧急任务
现在你接到那个电话,有个紧急问题等待你来解决。有了 Git 的帮助,你不必把这个紧急问题和 iss53 的修改混在一起,你也不需要花大力气来还原关于 53# 问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 你所要做的仅仅是切换回 master 分支。
但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,保存进度(stashing) 和 修补提交(commit amending))。
step1 切回主分支
现在,我们假设你已经把你的修改全部提交了,这时你可以切换回 master 分支了:
git checkout master
这个时候,你的工作目录和你在开始 #53 问题之前一模一样
step2 建立紧急任务分支
现在让我们建立一个针对该紧急问题的分支(hotfix branch)来解决问题
git checkout -b hotfix
vim file.txt
git commit -a -m "处理紧急任务"
step3 合并到主分支
你可以运行你的测试,确保你的修改是正确的,然后将其合并回你的 master 分支。 你可以使用 git merge
命令来达到上述目的:
git checkout master
git merge hotfix
在合并的时候,你应该注意到了"快进(fast-forward)"这个词。 由于当前 master 分支所指向的提交是你当前提交(有关 hotfix 的提交)的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
step4 删除紧急任务分支
现在紧急任务已经处理完毕,你想要回到你之前的工作中,但是,你应该先删除horfix分支,因为现在你已经不需要它了(master 分支已经指向了同一个位置)。你可以使用git branch -d
来删除它
git branch -d hotfix
step5 回到被打断前
现在你可以切换回你正在工作的分支继续你的工作,也就是针对 #53 问题的那个分支(iss53 分支)
你在 hotfix 分支上所做的工作并没有包含到 iss53 分支中。 如果你需要拉取 hotfix 所做的修改,你可以使用 git merge master 命令将 master 分支合并入 iss53 分支,或者你也可以等到 iss53 分支完成其使命,再将其合并回 master 分支。
接下来我们使用第二种方法试一下。
分支的合并
假设你已经修正了 #53 问题,并且打算将你的工作合并入 master 分支。 为此,你需要合并 iss53 分支到 master 分支,这和之前你合并 hotfix 分支所做的工作差不多。 你只需要检出到你想合并入的分支,然后运行 git merge
命令:
git checkout master
git merge iss53
咦~好巧,有冲突,冲突的解决稍后再说,现在来看看这次发生了什么。
这次合并和你之前合并 hotfix 分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的工作祖先(C2),做一个简单的三方合并。
和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
既然你的修改已经合并进来了,你已经不再需要 iss53 分支了。 现在你可以在任务追踪系统中关闭此项任务,并删除这个分支。
冲突的解决
向上面的情况,两个分支都修改了同一个文件,在合并是就会产生冲突,此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件: 任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段,看起来像下面这个样子
如图,当前head所指向的版本,和你iss53所指向的版本存在冲突,其中<<<<<<<HEAD
到 =========
为master分支所修改的内容,因为HEAD现在指向master分支。=========
到 >>>>>>>iss53
为iss53分支所指向的内容。为了解决冲突,你可以选择留下其中一部分,或者手动将两部分内容合并。例如,你可以改成这样
如果你想使用图形化工具来解决冲突,你可以运行 git mergetool,该命令会为你启动一个合适的可视化合并工具,并带领你一步一步解决这些冲突。
修改完毕后,你可以使用
git add
命令来将冲突文件标记为以解决冲突,使用git commit
命令来提交本次合并
管理分支
现在已经创建、合并、删除了一些分支,让我们看看一些常用的分支管理工具。
git branch
命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表:
分支前的*表示你当前正在使用的分区
- 查看每个分支最后一次提交的内容
git branch -v
- 查看已经合并的分支
git branch --merged
- 查看未合并的分支
git branch --no-merged
- 删除一个分支
如果删除的分支还未合并到主分支,git会提示你删除失败,如果你真的不想要该分支了,你可以使用-D选项强制删除它们git branch -d testing