3.5 使用 Git 顺利地推进并行开发

    这里我们来看一下通过使用版本管理系统,在团队中顺利地开展并行开发的方法。

    如上所述,Git 提供了能够使多人并行开发的合并模式,因此能方便地实现多人同时编辑同一文件。但是,当一个人不得不并行地进行多个不同的开发时,就会有一些问题。

    第 2 章的问题 2 和问题 9 中涉及的、在新功能的开发过程中需要修改已发布版本中的 bug 的情况下,就可以使用版本管理系统的分支功能。

    3.5.1 分支的用法

    ●…… 什么是分支

    分支(branch),英语的意思为树枝。是一种从主干分离出来的,作为和主开发内容不同的开发内容进行分别管理的机制。分支是几乎所有的版本管理系统都具备的标准功能。

    和其他的版本管理系统相比,Git 的分支功能的特点是速度快且易于使用。

    ●…… 什么是发布分支(release branch)

    以第 2 章的情形为例,我们来看一下分支的使用方法。但在此之前,我们先来谈一下发布分支的用法。

    发布分支是向正式环境进行发布时实际使用的分支。有将 master 用作发布分支的,也有从 master 创建别的分支作为发布分支的。

    具体来说就是要根据各团队和项目的实际情况选择合适的方法,没有一定正确的答案。关于使用 Git 的工作流程的组建方式,我们将稍后介绍。本次事例中暂且以 master 作为发布分支,下面的讲解都是以此为前提的。

    ●…… 克隆和创建分支

    首先将(所认为的)中央代码库克隆到本地机器上。

    $ git clone git@github.com:CoolInc/someniceapp.git

    接着为发布后的新功能开发创建分支。分支的名称就是 new-cool-function。

    $ git branch new-cool-function

    分支创建之后进行 checkout 操作。这里的 checkout 和以 Subversion 为代表的中央集权型版本管理系统中的 checkout 的意思是不同的,这里使用 checkout命令来进行分支切换。

    $ git checkout new-cool-function

    这样一来就可以在 master 以外的分支上进行新功能的开发,不会对已经发布的代码产生任何影响。

    ●…… 提交和提交记录

    假设已经修改了几处代码,现在我们用 git commit 命令来试着进行提交。

    $ git commit -m "实现了非常炫的功能" $ git commit -m "修改了一些小错误" $ git commit -m "想到了一些需要改进的地方,于是进行了修改"

    上述提交记录的问题在于内容过于抽象 42 ,但我们这里暂且先继续下去。

    42 阅读到这里的读者一定知道这种写法的提交记录是肯定不行的。这里我们是以第 2 章的案例进行说明,第 2 章的案例中也正是因为没有正确地填写提交记录,才导致提交记录如同上述提交记录一样让人无法理解。实际操作中应该将修改的内容写入提交记录。关于这部分内容我们在第 4 章会进行具体的讲解。另外,在提交记录中记下相关联的 bug 票号也是非常重要的。

    再用 git log 命令来确认一下提交记录。

    $ git log —pretty=oneline 05afd8b298d2439ddf7d5ae720b4967613fb11cb 实现了非常炫的功能 4006971346b0cae1596914023981eff4a8b5410c 修改了一些小错误 2487df32b6096cd349d8543304a299e41fdb037a 想到了一些需要改进的地方,于是进行了修改

    还可以用 git branch 命令来确认分支的改变。

    $ git branch master * new-cool-function

    至此,新建分支的状态如图 3.2 所示。

    3.5 使用 Git 顺利地推进并行开发 - 图1

    图 3.2 新建的分支

    ●…… 分支的切换

    在第 2 章的案例中,有报告称已经发布的正式环境中发生了故障。如果按照第 2 章的死亡行军项目的做法,又是给目录重命名又是复制文件的话,那就实在太麻烦了。其实用 git checkout 命令就能简单处理。

    $ git checkout master

    只需执行上述命令就能切换回 master 分支,即本次事例中的发布分支。如果不确定是否已经回到 master 分支,可以通过 git log 命令确认下提交记录。

    $ git log —pretty=oneline 27b06870957e44d5b02606af668c9a120df4b7e0 最初的发布版本

    保险起见,我们再用 git branch 命令来确认下。

    $ git branch * master new-cool-function

    确实已经回到了 master 分支。这里假设我们正处于第 2 章所述的境况下,让我们试着对故障进行处理。不直接修改 master,而是新建故障处理用的分支,然后再进行提交。Git 的分支创建非常快速且易于合并,因此应该灵活应用其分支功能。

    $ git checkout -b issue345 $ git branch * issue345 master new-cool-function

    可以使用 git checkout -b XXX 命令来创建分支并同时进行 checkout 操作。分支的名字可以任意取,这次假设已经有了故障报告的 bug 票(ticket),以票号为名字创建分支。缺陷(ticket)管理系统(ITS/BTS)的相关内容将在第 4 章进行讲解。

    ●…… 修正 bug 后的提交

    在 issue345 分支上对 bug 进行了修正,并进行了 3 次提交,如下所示。

    $ git commit -m "邮件的bug修正" $ git commit -m "申请时的死锁问题的修正" $ git commit -m "删去没用的代码"

    在 issue345 上提交了 3 个修正后,分支的状态如图 3.3 所示。

    3.5 使用 Git 顺利地推进并行开发 - 图2

    图 3.3 新建的故障处理用的分支

    ●…… 合并到 master

    修正告一段落后,让我们把修改的内容合并到 master。只需切换到 master 并执行以下命令即可。

    $ git checkout master $ git merge issue345 Updating e380b5e..8f3b4e5 Fast forward src/main/scala/Application.scala | 1 - src/main/scala/MailSender.scala | 13 + 2 files changed, 13 insertions(+), 1 deletions(-)

    最终的状态如图 3.4 所示。

    3.5 使用 Git 顺利地推进并行开发 - 图3

    图 3.4 合并到 master

    在完全不影响新功能开发的基础上,完成了对故障的处理 43 。

    43 实际在案例分析中数据库的变更管理也是非常棘手的一个问题,需要予以解决。但这里暂且不进行说明,数据库变更管理的相关内容将在后面进行讲解。

    ●…… 向 master 进行 Push

    接着,把本地机器上合并到 master 分支中的修正用 git push 命令 Push 到中央代码库。

    $ git push origin master:master

    这样 Push 就完成 了。接着让 QA 部门进行验收测 试,用 git checkout 命令就能回到原来的新功能开发。

    $ git checkout new-cool-function

    之后,在 new-cool-function 分支上进行新功能添加的开发,开发完成后合并到 master 即可 44 。

    44 因为本次的例子非常简单,所以能够轻易地合并,但也有发生冲突无法合并的时候。在这种情况下,首先要合理地解决冲突,然后再重新合并。这和 Subversion 等其他的版本管理系统的思考方式是一样的。

    ●…… 分支使用方法总结

    像这样通过合理使用分支功能,并行地进行多个版本的开发也变得容易了 45 。

    45 关于分支功能请参考 Pro Git 中文版“Git 分支”一章(http://git.oschina.net/progit/3-Git-分支.html )或者 nulab 提供的“猴子都能懂的 Git 入门 ”(http://backlogtool.com/git-guide/cn/ )等,上述资料对 Git 的分支功能做了浅显易懂的总结。

    Subversion 这样的中央集权型版本管理系统无法像 Git 这样简单地创建分支。Git 的话还可以在本地机器的封闭环境下随意地创建分支、进行合并,因此能够以简单且高速的方法实现并行开发。

    3.5.2 标签的使用方法

    ●…… 什么是标签

    在刚才的例子中,你是否觉得如果能把发布时的系统快照保存下来会很方便呢?标签(tag)就是用于实现这个功能的。实现的细节上可能有所差异,但几乎所有的版本管理系统都提供了标签功能。

    ●…… 新建标签

    还是刚才的例子,首先可以为发布的时间点打上标签。下面就以“v0.1" 为标签名,执行 git tag 命令。标签名在内容上要简洁易懂,一般多以版本号作为标签名。

    $ git tag -a v0.1 -m "最早的发布版本"

    这样以后也能找出这个时间点的系统快照了。

    第 2 章的案例分析中,发布之后已经过了一段时间,这样的情况下仍然可以追溯回去打上标签。首先在 master 分支上用 git log 命令寻找对应的提交。

    $ git log —pretty=oneline 27b06870957e44d5b02606af668c9a120df4b7e0 最早的发布版本 ae9884e9f1e7551026c447556c63db58d49d6774 邮件的bug修正 ad2d9a217af750b2fb0a0636f0a26ef761ba2bfc 申请时的死锁问题的修正 bdabe98d3b9102ce33ed7b4c6033ebc9cbb44fe6 删去没用的代码

    找到对应的提交之后,用 git tag 命令为那个提交打上标签。这样就 OK 了。

    $ git tag -a v0.1 27b06870957e44d5b02606af668c9a120df4b7e0

    ●…… 标签的确认

    下面来确认下刚才创建的标签。

    $ git tag v0.1

    确认了标签之后,再来确认下标签对应的内容是否正确。

    $ git show v0.1 tag v0.1 Tagger: ikeike443 <ikeike443@gmail.com> Date: Thu May 16 15:32:59 2013 +0900   commit 27b06870957e44d5b02606af668c9a120df4b7e0 Author: ikeike443 <ikeike443@gmail.com> Date: Thu May 9 19:29:49 2013 +0900   最早的发布版本 …

    可以用 git show 命令来确认标签的内容。从结果来看内容似乎是正确的。在本地环境上打好标签后,用 git push 命令 Push 到中央代码库。

    $ git push origin v0.1:v0.1

    ●…… 标签的取得

    如果要在其他环境上取得刚才创建的标签,应该怎么做呢?

    首先,在其他环境上用 git clone 命令进行克隆。 git clone ,顾名思义,就是克隆代码库的命令。代码库中包含的分支、标签都会被复制到本地环境中。

    $ git clone git@github.com:CoolInc/someniceapp.git

    克隆执行完后创建分支,并将标签 checkout 到该分支上 46 。

    46 这里也可以把标签名和分支名都设置成“v0.1”,但这里没有这么做。相关原因请参考专栏。

    $ git checkout -b 0.1 v0.1

    这样就能在其他环境上取得标签了。如果不想新建分支的话,还可以用下面的方法 47 。

    47 这样操作会变成 Detached HEAD 的状态。相关内容请参考专栏。

    $ git checkout v0.1
    专栏 避免使用相同的标签名和分支名
    本文的例子中虽然避免使用相同的分支名和标签名,但使用相同的名称也是可以的。
    $ git checkout -b v0.1 v0.1





    新建名为“v0.1”的分支,在其基础上 checkout 名为“v0.1”的标签。但是因为同名的缘故,“v0.1" 指的是分支还是标签就不清楚了。
    这里,因为执行的时候还没有名为“v0.1”的分支,所以能够顺利执行。如果已经存在名为“v0.1”分支,则是无法执行的。例如,如果之后再新建名为“test”的分支,并在其上 checkout“v0.1”标签,就会出现以下消息。
    $ git checkout -b test v0.1
    warning: refname 'v0.1' is ambiguous.
    fatal: Ambiguous object name: 'v0.1'.





    相反,在从 master 切换到“v0.1”分支时也会出现同样的问题。
    $ git checkout v0.1
    warning: refname 'v0.1' is ambiguous.
    fatal: Ambiguous object name: 'v0.1'.





    在这种情况下,需要像下面这样明确指出这个是标签还是分支。
    //v0.1checkout名为“v0.1”的标签
    $ git checkout -b test refs/tags/v0.1
     
    //v0.1checkout名为“v0.1”的分支
    $ git checkout refs/heads/v0.1





    Git 中,标签和分支实际上分属于不同的命名空间,因此通常在使用时不需要特别指明是标签还是分支,可以省略。所以说,乍一看标签和分支还是可以同名的。
    但这样很容易造成混乱。实际使用中要避免标签和分支同名,这样混乱也会相应减少。万一出现同名的情况下,可以通过指定命名空间来明确是标签还是分支。请记住这一点。

    ●…… 标签使用方法总结

    像这样,通过用标签管理系统某个时间点的快照,就能合理地进行版本管理,在发生故障后查找原因时,以及在紧急情况下的系统回滚时,也能发挥作用。

    本次的例子中,在最初发布时打了一次标签。之后处理故障,将修改合并到了 master,因此待 QA 部门的验收测试结束,可以再次发布时,应该再次打上标签。

    $ git tag -a v0.1.1 -m "进行了issue 345的故障处理"

    最终,合理使用分支和标签功能之后,版本管理系统的状态如图 3.5 所示。

    3.5 使用 Git 顺利地推进并行开发 - 图4

    图 3.5 活用标签

    至此,我们一起看了使用分支和标签功能的基本的团队开发的方法。下一节我们将研究使用分支功能的团队开发工作流程的各种形式。

    专栏 什么是 Detached HEAD
    执行本文中的命令,会显示下面这样的消息。
    $ git checkout v0.1
    Note: checking out 'v0.1'.
     
    You are in 'detached HEAD' state. You can look around, make experimental
    changes and commit them, and you can discard any commits you make in this
    state without impacting any branches by performing another checkout.
     
    If you want to create a new branch to retain commits you create, you may
    do so (now or later) by using -b with the checkout command again. Example:
     
    git checkout -b new_branch_name
     
    HEAD is now at 8a4e89c… xxxx





    这就是 Detached HEAD 状态。不必创建分支就能实验性地修改并提交,或者放弃修改。执行 git branch 命令,显示如下。
    $ git branch
    * (detached from v0.1)
    master





    如果最终想保留在这个状态下进行的各类实验性质的提交的话,只需重新创建分支,并切换到该分支上,就能将提交保存下来。
    $ git checkout -b new_branch_name





    Detached HEAD 状态虽然是很方便的功能,但在部署过程中获取标签的时候,如果还保持 Detached HEAD 状态,管理上就很难理解。所以要像本文中的示例一样,一开始就新建分支,并将其 checkout。但具体做法还是要结合项目自身的情况进行探讨。