Git学习——Git分支篇(未完)
前言
完成了Git学习的基础篇,继续学习Git的分支特性,这是Git出众之处。
目录
-
- - - ***
分支简介
首先,Git保存数据的方式比较特殊,保存的是文件的快照,而不是文件的变化。
因此,在执行提交( commit )操作时,Git会保存一个提交对象( commit object)。该提交对象包含一个指针指向暂存的内容快照,同时包含作者的姓名、邮箱、提交时输入的信息和指向它父对象的指针。
首次提交产生的提交对象没有父对象,普通提交产生的提交对象又一个父对象,多个分支合并产生的提交又多个父亲对象。
暂存操作会为每一个文件计算校验和(使用SHA-1哈希算法),然后保存当前版本的文件快照( Git使用blob对象来保存)。
提交操作会计算每个子目录的校验和,然后保存树对象在仓库中。然后Git会创建一个提交对象,它包含除了以上信息外,还有指向这个树对象(项目根目录)的指针。
当做出修改后再提交,所产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
Git的分支,其本质是指向提交对象的可变指针。Git默认分支名为master,多次提交后,已经有一个指向最后提交对象的master分支,会在多次提交的操作中自动向前移动。
Git 的 master 分支并不特殊,它和其它分支一样,只是大多数人不会改变它。
分支创建
Git创建新分支实际上是创建了一个可以移动的指针,执行 git branch branch-name 完成。
$ git branch testing
这会在当前所在的提交对象上创建一个指针。此时拥有两个分支 master 和 testing 。
Git 是如何知道当前所在分支,这是由于它有一个名为 HEAD 的特殊指针,指向当前所在的本地分支。此时我们还是 master 分支上,因为 git branch 命令只创建了一个新分支,并不会自动切换到新分支中去。
简单的使用 git log 命令可以查看各个分支当前所指向的对象。只要加上 --decorate选项。
$ git log --oneline --decorate21eeb6c (HEAD -> master, testing) init the repo
可以看见当前的 master 和 testing 分支都指向校验和以 21eeb6c 开头的提交对象。
分支切换
执行 git checkout branch-name 可以切换到相应分支。
$ git checkout testingSwitched to branch 'testing'
此时的 HEAD 指向新分支 testing 。那么这么实现的作用是什么。假如此时在Git中修改并执行提交。
$ vim Hello.c$ git add Hello.c$ git commit -m "add Hello.c"
这里添加一个新文件举例,再使用 git log 查看。
$ git log --oneline --decorateb6e0678 (HEAD -> testing) add Hello.c21eeb6c (master) init the repo
可以看见的是,HEAD 指向了 testing并伴随其向前移动到了新的提交对象上,对象的校验和为b6e0678。值得注意的是,master 分支并没有向前移动,还是停留在上一次提交的状态。再次切换到 master 分支查看。
$ git checkout masterSwitched to branch 'master'
这里,Git 做了两件事,以是将 HEAD 指向 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。也就是说,如果现在做修改,将始于一个较旧的版本。本质上就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发。
分支切换会改变你工作目录中的文件在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。
此时我们在 master 分支中添加 Hello.java 并提交。执行以下命令,输出提交历史、各个分支的指向以及项目的分支交叉情况。
$ git log --oneline --decorate --graph --all* ee4b05d (HEAD -> master) another version of Hello| * b6e0678 (testing) add Hello.c|/* 21eeb6c init the repo
注意这里的master 和 testing 指向不同的对象。
分支的新建与合并
让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。 你将经历如下步骤:
- 开发某个网站。
- 为实现某个新的需求,创建一个分支。
- 在这个分支上开展工作。
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:
- 切换到你的线上分支(production branch)。
- 为这个紧急任务新建一个分支,并在其中修复它。
- 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分 支。
- 切换回你最初工作的分支上,继续工作。
新建分支
首先,假设正在项目上工作,并且有了两次提交,记为C0(第一次提交),C1(第二次提交),此时的HEAD和和master在第二次提交处。
$ git log --onelined093652 (HEAD -> master) add homepage64d793d add README
此时你决定要解决公司使用的问题追踪系统中的#53问题,于是想要新建一个分支并切换过去。可以执行如下命令。
$ git checkout -b iss53Switched to a new branch 'iss53'
这条命令实际上和以两条下命令等价。
$ git branch iss53$ git checkout iss53
此时的 master 和 iss53 都在第二次提交处,即C1。
假设你在iss53分支上工作并且做出了修改,经行了一次提交,这里记为C2。iss53的分支向前推进。
$ vim index.html$ git add issue53.html$ git commit -m "added a new footer [issue 53]"
$ git log --onelineb8deb27 (HEAD -> iss53) added a new footer [issue 53]d093652 (master) add homepage64d793d add README
现在,你接到电话,有个紧急问题需要解决,在Git中,不需要将这个问题和iss53放到一起,也不需要还原关于#53问题的修改,然后再添加这个紧急问题的修改,所需要做的是切换回 master 分支。
在这么做之前,要留意工作目录和暂存区里那些还没有被提交的修改,它可能会和即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是在切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题,这先不深究。切换回 master 。
$ git checkout masterSwitched to branch 'master'
此时的工作目录将和开始#53问题前一模一样,可以开始修复紧急问题,建立一个针对该紧急问题的分支( hotfix ),在该分支上工作直到问题解决。
假设问题很简单,只进行了一次提交完成了问题的解决。这次提交记为C3。
$ vim homepage.html$ git commit -a -m "fixed the problem"
分支合并
在确保问题解决后想要合并回 master 分支 来部署到线上。执行 git merge branch-name 命令,记得要切换到 master 分支上。
$ git checkout masterSwitched to branch 'master'$ git merge hotfixUpdating d093652..4a8a77eFast-forward homepage.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 homepage.html
值得注意的是"Fast-forward"这个词。由于当前的 master 分支所指向的提交是有关hotfix的提交的直接上游,所以Git只是简单的将指针向前移动,因为这种情况下的合并操作没有需要解决的分歧,这叫做"快进Fast-forward"。
完成了紧急任务,你又要回到之前的工作,不过记得删除hotfix这个分支,因为你以及不需要了,master 已经指向了同一位置,删除操作如下。
$ git branch -d hotfixDeleted branch hotfix (was 4a8a77e).
此时再切换到 iss53 分支继续之前的工作。假设你工作了一段时间经行了一次提交,记为C4。这里先梳理一下,master指向C3提交,iss53指向C4,C4的上游为C2,C2和C3的上游为C1。
$ git checkout iss53vim index.html......$ git commit -a -m "fix some issues"
你在 hotfix 分支上所做的工作并没有包含到 iss53 分支中。 如果你需要拉取hotfix 所做的修改,你可以使用 git merge master 命令将 master 分支合并入 iss53 分支,或者你也可以等到 iss53 分支完成其使命,再将其合并回 master 分支。
假设这里#53已经解决,打算合并到master分支上,执行和之前合并 hotfix 差不多的命令。
$ git checkout masterSwitched to branch 'master'$ git merge iss53Merge made by the 'recursive' strategy. index.html | 7 +++++++ issue53.html | 0 2 files changed, 7 insertions(+) create mode 100644 issue53.html
这里合并和 hotfix 的合并看起来不一样。因为 iss53 是从更早的时候分叉出来的(即 master 为C1时)。此时 master 为C3,并不是C4的直接祖先,Git需要做额外的工作,即将两个分支的末端快照(C3和C4)以及这两个分支的共同祖先(c1),做一个简单的三方合并。
和之前不同的是,这里将三方合并的结果做了一个新的快照并且自动创建一个新的提交指向了它。这个被称作一次合并提交,它的特别之处在于它有不止一个父提交。不妨记作C5。
$ git log --oneline --graph --all* f9d8ac1 (HEAD -> master) Merge branch 'iss53'|\| * 3c230d6 (iss53) fix some issues| * b8deb27 added a new footer [issue 53]* | 4a8a77e fixed the problem|/* d093652 add homepage* 64d793d add README
这里由下向上显示了合并的过程,由于hotfix已经删除,所以不再显示。 64d793d代表C0,d093652代表C1,4a8a77e代表C3,b8deb27代表C2,3c230d6代表C4,f9d8ac1代表C5。此时可以删除iss53分支。
$ git branch -d iss53
遇到冲突时的分支合并
图形化合并工具。
$ git mergetool
分支管理
执行不带参数选项的 git branch 可以查看所有的分支列表,*表示当前 HEAD 所在分支。
$ git branchiss53*mastertest
执行带 -v 选项可以显示每个分支的最后一次提交。这里三个分支最后一次提交相同。
$ git branch -v iss53 8543ed2 Update README.md* master 8543ed2 Update README.md test 8543ed2 Update README.md
执行带 --merged 和 --no-merged 选项可以查看与当前所在分支已经合并和未合并的分支,注意,刚新建没有提交的分支会显示在已经合并的分支中。
$ git branch --merged iss53* master
$ git branch --no-merged test
对于未合并的分支,无法使用 -d 选项删除,如果想强制删除,可以执行 -D 选项。
$ git branch -d testerror: The branch 'test' is not fully merged.If you are sure you want to delete it, run 'git branch -D test'.
$ git branch -D testDeleted branch test (was ff575cb).
分支开发工作流
利用已学内容模拟常见工作流程。
长期分支
长期分支的主要特点,首先,通常只会将稳定的代码、或者发布和即将发布的代码合并到 master 分支上;其次,有一些名为 develop 或 next 的平行分支,用来做后续开发和稳定性测试;还有些特性分支用来执行特殊的任务,如之前的 issu5 分支,为了解决特定的问题。
特性分支
特性分支是一种短期分支,用来实现单一的工作。