阅读 142

git系列---分支

注: 该系列文章整理自《Pro Git》,非原创。

git系列:

git系列--基础使用

git系列--分支

一 Git分支简介

几乎所有的版本控制系统都以某种形式支持分支。使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。

与许多其它版本控制系统不同,Git处理分支的方式可谓是难以置信的轻量,且鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。

首先回顾下Git保存数据的方式:Git保存的不是文件的变化或差异,而是一系列不同时刻的文件快照。Git进行提交操作的时候,会保存一个提交对象,该对象会包含一个指向内容快照的指针,还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针(首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象),类似于链数据结构。

Git的分支,在本质上仅仅只是指向提交对象的可变指针!它会在每次的提交操作中自动向前移动。

注:Git的默认分支是“master”,并没有什么特殊意义,只是git init命名默认创建的,一般懒得去改名字

1.1 分支创建

Git分支的创建,可以理解为就是简单的创建了一个基于当期提交对象的可以移动的新指针。

比如,新建一个testing分支,用git branch

$ git branch testing
复制代码

这会在当前所在的提交对象上创建一个新的指针

那么问题来了,git怎么知道当前自己在哪一个分支指针上呢?它用一个名为“HEAD”的特殊指针来区分——它指向当前所在的本地分支(可将其想象成当前分支的一个别名)。切换到其他分支,可以看成就是HEAD指向另一个分支指针

1.2 切换分支

新建分支,并不会自动切换到新分支,需要使用git checkout切换。

$ git checkout testing
复制代码

这样,就切到我们刚才新建的testing分支上,即HEAD指向了testing分支

Git的分支功能十分强大有用。比如,你需要在testing上开发一个测试功能,但不想影响到主线master,或master先进行额外的更新发布。此时,你就可以分别在两个分支上操作,待时机成熟,再将你的工作合并起来:

首先切换到testing分支,并进行了一次提交,此时,testing分支就向前移动领先master(此时master是不移动的)

然后我们切换回到主线master上

$ git checkout master
复制代码

这条命令做了两件事。

  • 一是使 HEAD 指回 master 分支,
  • 二是将工作目录恢复成 master 分支所指向的快照内容。 也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发。

注意: 在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。

现在,若在master上,又进行了一个更新提交,则项目上就会产生分叉。你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。

由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。任何规模的项目都能在瞬间创建新分支。同时,由于每次提交都会记录父对象,所以寻找恰当的合并基础(即共同祖先)也是同样的简单和高效。这些高效的特性使得 Git 鼓励开发人员频繁地创建和使用分支。

二 分支的新建和合并

在实际开发过程中,常常会突然遇到一个紧急问题需要修复。这时,可以先切换到主分支,再为这个紧急的任务新建一个分支来解决问题。待问题解决后,再将其合并到主分支中。

2.1 新建分支和分支合并

假设项目已有一些提交

现在你正在解决公司的问题#53,因此,你新建了一个issue53分支。(想新建一个分支,并同时切换道这个分支上,可以使用带-b参数的git checkout命令:

$ git checkout -b iss53
Switched to a new branch "iss53"
复制代码

它是下面两条命令的简写

$ git branch iss53
$ git checkout iss53
复制代码

继续在issue53上工作并提交了一些新提交。

现在突然接到紧急任务,需要快速修复主分支的问题。有了Git的帮助,你不必把这个紧急问题和iss53的修改混在一起,你也不需要花大力气来还原关于 53# 问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 你所要做的仅仅是切换回 master 分支,并新建hotfix分支,再修复合并。

$ git checkout master
$ git checkout -b hotfix
复制代码

你可以在hotfix分支上修复问题,并运行你的测试,确保你的修改是正确的,然后将其合并回你的 master 分支来部署到线上。 你可以使用 git merge 命令来达到上述目的:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)
复制代码

现在这个紧急问题已修复并合并到主分支中了,因此可以删除该分支。 你可以使用带 -d 选项的 git branch 命令来删除分支:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).
复制代码

现在切回之前工作的分支,即issue53,并继续工作

$ git checkout iss53
复制代码

在hotfix分支上做的修复工作并没有包含到issue53分支中。如果需要拉取所做的修复,可以使用git merge master将master合并入issue53分支中(因为hotfix已删除,所以合并master分支)。 或者,可以等到issue53分支完成任务后再合并回master分支中。

假设你已经修正了 #53 问题,并且打算将你的工作合并入 master 分支。 为此,你需要合并 iss53 分支到 master 分支,这和之前你合并 hotfix 分支所做的工作差不多。 你只需要检出到你想合并入的分支,然后运行 git merge 命令:

$ git checkout master
$ git merge iss53

复制代码

这和你之前合并 hotfix 分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的工作祖先(C2),做一个简单的三方合并。

和hotfix单纯的推进分支指针不同,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。

最后删除分支

$ git branch -d iss53
复制代码

三 分支管理

3.1 查看分支列表

git branch命令,不加任何参数的情况下,会当到当前所有分支的列表(本地分支):

$ git branch
  iss53
* master
  testing
复制代码

notemaster分支前的*字符,代表当前检出的那个分支(即,当前HEAD指针所指向的分支)

--merged--no-merged这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。 如果要查看哪些分支已经合并到当前分支,可以运行 git branch --merged:

$ git branch --merged
  issue53
* master
复制代码

因为之前已经合并了issue53分支,所以现在它在列表中。通常可以用git branch -d删除这些已经合并了的分支

若要查看未合并的分支,可以运行git branch --no-merged

$ git branch --no-merged
  testing
复制代码

注:对于这些还未合并的分支,如果它包含了还未合并的工作,尝试使用git branch -d来删除时会提示失败。(可以使用-D选项强制删除,不推荐)

四 远程分支

可以通过 git remote show (remote) 查看远程仓库的具体远程分支情况

4.1 远程分支介绍

上一篇讲过远程仓库,也就是你服务器的git仓库,如GitHub上的项目。远程分支可以理解为,是将远程仓库上的分支的拷贝到本地目录中,但对于这些拷贝下来的远程分支,你只能去查看,而不能主动去提交修改,只有当你与服务器同步时,拷贝下来的分支才会有新的提交,即分支指针才会有移动。

简单而言,就是你仅仅只有只读权限,可以读取服务器的最新代码状态,并将其合并到自己的本地分支中。或者自行推送最新的修改到服务器中(此时,拷贝的远程分支也会自动更新到最新状态)

远程分支以(remote)/(branch) 形式命名

举个例子,假设你有一个git.ourcompany.com的git远程服务器,且上面已经有工作基础了的,而不是个空项目,此时你可以通过 git clone 克隆下来,拉取它的数据。此时,git为帮你将这个远程服务器命名为origin,同时拉取它的所有数据,并创建一个origin/master分支指针指向它,并且还会创建一个master本地分支,指向相同的地方,使得有工作的基础

现在你在本地master分支上做了些提交,与此同时,其他人推送更新了git.ourcompany.com远程仓库。(只要你不与服务器同步,拷贝的远程分支origin/master指针就不会有移动)

要抓取服务器的最新代码,可以运行git fetch origin。抓取并更新本地拷贝下来的远程分支。

fetch命令只会抓取数据并更新到远程分支,但并不会自动帮你合并到你的工作的本地分支master中,需要你自行合并(pull命令则可以自动合并,但不推荐使用)。

4.2 推送

本地的分支并不会自动与远程仓库同步——你必须显式地推送想要分享的分支。当你想要分享公开一个分支时,你可以使用git push推送上去。

例如,希望和其他人在serverfix分支上工作,可以运行git push (remote) (branch)

$ git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
 * [new branch]      serverfix -> serverfix
复制代码

你也可以运行 git push origin serverfix:serverfix,它会做同样的事——也就是说“推送本地的 serverfix 分支,将其作为远程仓库的 serverfix 分支” 可以通过这种格式来推送本地分支到一个命名不相同的远程分支。 如果并不想让远程仓库上的分支叫做 serverfix,可以运行 git push origin serverfix:awesomebranch 来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch 分支

推送之后,其他人使用git fetch抓取数据时,会在本地生成一个远程分支origin/serverfix,指向服务器的serverfix分支的引用

$ git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
 * [new branch]      serverfix    -> origin/serverfix
复制代码

特别注意:抓取新的服务器分支后,只会拷贝一份可读的远程分支,本地不会自动生成可工作的server分支(clone命令,生成master命令是系统自动生成的) 换一句话说,这种情况下,不会有一个新的 serverfix 分支——只有一个不可以修改的 origin/serverfix 指针。

可以运行git merge origin/serverfix,将这些工作合并到当前所处的本地工作分支。或者,如果想在自己本地也创建一个serverfix分支工作,可以将其建立在远程跟踪分支上($ git checkout -b serverfix origin/serverfix):

$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
复制代码

这会给你一个用于工作的本地分支,并且起点位于 origin/serverfix。

4.3 跟踪分支

从一个远程分支检出(checkout)一个本地分支会创建所谓的”跟踪分支“(它跟踪的分支叫做“上游分支”)。跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入git pull,Git能自动地识别去哪个服务器上抓取、合并到哪个分支。

当clone克隆一个仓库时,通常会自动创建一个跟踪origin/master的master分支。你也可以设置其他的跟踪分支,如前面的$ git checkout -b serverfix origin/serverfix就会创建一个跟踪origin/serverfix的本地serverfix分支。(该命令的快捷方式:$ git checkout --track origin/serverfix

现在,在本地分支 serverfix运行pull命令 会自动从 origin/serverfix 拉取。

设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支,你可以在任意时间使用 -u--set-upstream-to 选项运行 git branch 来显式地设置。

$ git branch -u origin/serverfix
复制代码

当推送一个新分支到服务器时,也可以使用带-upush命令,它不仅会推送本地分支到服务器,也会跟踪它。

$ git push -u origin serverfix

复制代码

如果你有注意的话,在GitHub上新建一个空项目时,就有相关的提示

添加一个远程仓库,并推送本地master分支到服务器上,同时跟踪这个远程分支。

4.4 拉取

git fetch 命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。 然而,有一个命令叫作 git pull 在大多数情况下它的含义是一个 git fetch 紧接着一个 git merge 命令。 如果有一个像之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 clone 或 checkout 命令为你创建的,git pull 都会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支。

由于 git pull 经常令人困惑所以通常单独显式地使用 fetch 与 merge 命令会更好一些。

4.5 删除远程分支

假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。 可以运行带有 --delete选项的 git push 命令来删除一个远程分支。

$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
 - [deleted]         serverfix
复制代码

五 分支的另一种整合:变基(rebase)

在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。两者的主要区别在于提交历史的不同。

5.1 变基(rebase)

变基rebase命令将分支上的所有修改移到另一个你指定的分支中,就好像“重新播放”一样,跟之前的合并merge的主要区别就是最终提交历史记录不同,但最终的代码结果是一样的。

假设现在开发任务有两个分叉

如果使用merge 命令。 它会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。

此时若使用变基rebase命令,则提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次,就好像“重新播放”一样。

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
复制代码

它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用

现在回到 master 分支,进行一次快进合并。

$ git checkout master
$ git merge experiment
复制代码

此时,C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照一模一样了。再次强调:两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁,一般使用变基的目的是为了确保在向远程分支推送时能保持提交历史的整洁,具体使用哪种方法,看个人实际使用。

注:不要对在你的仓库外有副本的分支执行变基。 如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

更多可查看Git 分支 - 变基

关注下面的标签,发现更多相似文章
评论