【Beta阶段】团队源代码管理

0. 快速上手与理解

  • 如果你的团队来了一个新队员,有一台全新的机器,你们是否有一个文档,只要设置了相应的权限,她就可以根据文档,从头开始搭建环境,并成功地把最新、最稳定版本的软件编译出来,并运行必要的单元测试?(在这过程中,不需要和老队员做任何交流)

其实我们非常想说可以,但是最后在仔细考虑后发现确实是不可以的。我们的文档注重的方面更多地在理解用户需求与后端的设计上,但是关于配置环境,运行单元测试这方面的文档,虽然后端工程师在启程时写过一篇,如下:

但是后期并没有对它进行必要的完善与维护,并且没有将它push到github上,所以我们即使想说是,但是确实是不可以的。我们没有重视对于环境与运行方面的文档。

 

1. 你的团队的源代码控制在哪里?用的是什么系统?如何处理文件的锁定问题?

 

场景: 程序员果冻正在对几个文件进行修改,实现一个大的功能, 这时候,程序员小飞也要改其中一个文件,快速修复一个问题。怎么办?

一个代码文件被签出 (check out) 之后,另一个团队成员可以签出这个文件,并修改,然后签入么?

有几种设计,各有什么优缺点?

例如,签出文件后,此文件就加锁,别人无法签出;  或者, 所有人都可以自由签出文件

 

答:我们团队的源代码控制在Github上,使用的Git系统与Github本身集成的一些其他功能。

由于Git的特点,在处理文件的合并这方面有着易懂且优秀的合并技巧。

以下是场景分析:

程序员果冻现在正在对几个文件进行修改,程序员小飞要修改其中一个文件,修复一个大bug。果冻因为还没有修改完,还没有push。但是很快,小飞修改完成了,commit,push成功!小飞乐滋滋地想着自己即将到来的奖金,去吃饭了。办公室就剩下果冻一个人在紧张赶工….也是轻松且愉悦地commit,push。

啊?被拒绝了!

Pushing to git@github.com:buaase/Phylab-Web.git

To git@github.com:buaase/Phylab-Web.git

! [rejected] master -> master (non-fast-forward)

果冻心里很是郁闷,难道就因为我长得没小飞帅,就要拒绝我的提交吗?

嘿嘿,其实不是的,其实这是因为果冻在进行push之前,小飞已经推送了内容到远程数据库,果冻的push就被拒绝。因为直接push会覆盖小飞所修改的内容。

所以作为项目经理的我,在跟我们团队的队员强调Git的使用时反复强调一点,在所有的push之前一定要进行pull,将服务器端他人推送的内容先合并到本地。

比如我们在项目中演示如下:

关于文件的锁定这一点上,git本身的合并机制支持了多个人同时开发不同的feature,不论是通过同一个分支,或者是不同的分支。如果在同样的时间内,一个文件只能被一个人修改的话,虽然是不会产生冲突,但是在某些项目里,这样类串行化的作业方式效率较低。当然在实际使用git的过程中应该尽量避免冲突,能够自动merge就自动合并。

 

2. 如何看到这个文件和之前版本的差异? 如何看到代码修改和工作项 (work item),缺陷修复 (bug fix) 的关系。

 

   场景: 程序员果冻看到某个文件被修改了,他怎么看到这个文件在最近的修改究竟改了哪些地方? 

   场景: 程序员果冻看到某个文件在最新版本被改动了100 多行, 那么和这100多行对应的其他修改在什么文件中呢? 这个修改是为了解决哪些问题而作的呢? 那些问题有工作项 (work item,issue),或者bug 来跟踪么?

 

答: 在git管理中, 使用git diff即可看到文件和之前版本的差异。

  • git diff:是查看working tree(工作目录)与index file(暂存区)的差别的。

  • git diff --cached:是查看index file(暂存区)与commit(本地仓库)的差别的。

  • git diff HEAD:是查看working tree(工作目录)和commit(本地仓库)的差别的。

在git里,这三个概念是很重要的,其中 git add的一个功能是将修改从工作目录添加到index file,commit的工能就是从index file的修改添加到 commit。

比如下面这是一个截图:(使用git diff --cached)

当我们把a.txt文件提交并作出修改后,可以使用git diff来查看差异:

在Github中,也可以通过可视化的界面来看到每次修改的代码(包括增加,删除)文件与修改的具体位置,修改的具体内容。

比如下面这样的界面:

可以看到每次修改的文件路径、文件的内容、红色代表删除掉的内容,绿色代表添加的内容

修改为了解决的问题要通过commit日志的提交信息来实现,但是很遗憾,在我们的团队项目中,关闭Issue和commit日志信息都做得不太好。大家的commit日志除了黄雨萌和我的相对规范一些,队员们很多都用来当打油诗了…

一次commit可以和Issue相关联,以表明这个Issue是由本次commit修复的,只需要在git commit的时候使用时加入如下commit日志信息即可:

  • fix #xxx

  • fixes #xxx

  • fixed #xxx

  • close #xxx

  • closes #xxx

  • closed #xxx

 比如我使用了如下语句:

现在可以在github看到

可以看到最后那里,一次commit和issue的关闭是绑定的。

3. 如果某个文件在你签出之后已经被别人修改,并且签入了,那么你在签入你的修改的时候, 如何合并不同的修改(merge)? 你用了什么工具来帮助你?

 

答: 我们使用Git来帮助我们完成了这件事。

在一般情况下,git pull后git会自动合并Git修改的部分,自动的Merge。但是,也存在无法自动合并的情况。就像果冻和小飞这样,远程数据库和本地数据库的同一个地方都发生了修改的情况下,因为Git无法自动判断要选用哪一个修改,所以就会发生冲突。但是,Git会在发生冲突的地方打个标记!比如这样式的:

<<<<<<< HEAD

test in Local

=======

test in Remote

>>>>>>> 17c805…(Commit的Hash值)

  • ==分割线上方是本地数据库的内容

  • ==分割线下方是远程数据库的某次产生冲突的commit所修改的内容。

这时候我们需要通过一双慧眼来识别哪些都可以保留,哪些保留远程数据库的内容,哪些保留本地数据库的内容。在将文件冲突的内容合并后,删除掉<<<<< 和=====,>>>>>这样的东西,重新add,commit,push,即完成了一次手工合并。

不过因为大家都是新手入门,不太会合理地人工合并冲突,所以项目经理本身在分配任务时,遵循了一个原则:尽量不让两个人的任务在同一个文件上产生重叠

每个人修改的文件范围或者其他都是固定的,一般不会存在两个人同时修改同样的文件。当然,前端和后端在修改时大部分时候都会产生冲突,这时候我们就使用了另一套机制来帮我们实现这一点:

新建分支与分支合并。

Beta阶段,我们要用到一个论坛的框架与现有的一些逻辑,但是又要同时兼容之前的网站主页的风格与设计,为了实现这一点,我们的前端负责修改页面样式,后端负责对接后台逻辑与数据库的同步问题。那么问题来了,如果两个人在同一个分支下进行工作,那么每次都要有合并与修改记录,并且如果一个人修改的部分因为某些bug不可用的时候,另一部分也无法确认其所修改的部分是否正确。所以我们建立了两个分支(Back-end和Front-end),Front-end上只有一个wecenter的模版供前端进行样式的修改,而后端使用默认的wecenter的模版进行数据库的对接与其他工作,两份工作由此而开始独立进行,互不干扰。

我们团队项目里的Front-end分支和Back-end分支的情况如下:

等到前端做到可行的程度后,前后端修改的部分开始合并,合并两个分支来将前端修改的部分合并到Back-end分支上,这样合并的工作只在一天进行,且由于冲突的部分较少,大部分都是自动合并,效率很高。

 

4. 你有20个文件都是关于同一个功能的修改,你要如何保证这些文件都同时签入成功(修改的原子性),或者同时签入不成功?

     

场景: 程序员果冻要签入 20 个文件,他一个一个地签入, 在签入完5 个 .h 文件之后, 他发现一些 .cpp 文件和最新的版本有冲突,他正在花时间琢磨如何合并... 这时候, 程序员小飞从客户端同步了所有最新代码, 开始编译, 但是编译不成功 - 因为有不同步的 .h 文件和 .cpp 文件!  这时候, 别的程序员也来抱怨同样的问题,果冻应该怎么办?

 

答:果冻目前面临的问题恰恰可以由我们第三点所述的branch的好处解决。

Branch的出现,可以让任何一位开发者基于其他人的代码或环境都完整可用(即stable版)的环境下进行自己的部分的独立开发 。

最后的合并工作可以放在一天之内,将所有的Branch上的feature合并到一个dev分支上来。但是这样面临的风险也是有的,多个分支同时合并时如果出现了比较大的冲突,合并起来必须小心翼翼。

同时,在解决一个Issue的时候,也可以新建一个Issue分支,比如像如下所示:

在解决了Issue后,可以使用分支合并的技术将两个或多个分支合并。

5. 你的PC 上有关于三个功能的修改,但是都没有完成,有很多文件处于半完工的状态,这时你要紧急修改一个新的 bug,如何把本地修改放一边,保证在干净的环境中修改这个 bug, 并成功地签入你的修改 --- changelist management

   

 

答:在Git里,不能完整地保证commit后整个环境处于可编译或可运行状态下的commit是不好的提交。

所以在文件半完工的状态下,我们不可以使用commit来将文件修改的内容留下来。

Git为我们提供了一种类似于操作系统里的保存现场的指令,那就是stash。 它可以把当前工作现场"储藏"起来,等以后恢复现场后继续工作,使用方法类似下面:

$ git stash

Saved working directory and index state WIP on master: 5655bdc Merge branch 'mas

ter' of https://github.com/buaase/Phylab-Web

HEAD is now at 5655bdc Merge branch 'master' of https://github.com/buaase/Phylab

-Web

 

这时候就会发现,add了以后的东西都被"雪藏"起来,现在的工作区非常干净,我们这时候可以在一个干净的环境中修复紧急的bug并提交,签入,在push后,再使用

git stash apply 或者 git stash pop

来将保存起来的内容取出来继续开开心心地开发啦。

比如下面我们来实际操作演示一下

 

可以看到我们对a.txt进行了修改,下面我们使用stash指令来让它”保存现场“

可以看到实际上 git stash后,我们对于a.txt的修改不见了?!

再使用 git stash pop,再来一观:

可以看到 git stash pop 后,对a.txt的修改又回来了。

6. 如何给你的源代码建立分支

    场景:你们需要做一个演示,所以在演示版本的分支中对各处的代码做了一个临时的修改,同时,主要的分支还保持原来的计划开发。你们怎么做到的?在演示之后,演示版本的有些修改应该合并到主分支中,有些则不用,你们是怎么做到的?

    场景:你们的软件发布了,有很多用户,一天,一个用户报告了一个问题,但是他们是用某个老版本,而且没有条件更新到最新版本。这时候,你如何在本地构建一个老版本的软件,并试图重现那个问题?

   

给源代码建立分支的过程我们下面使用一个gif图来演示一下:

上面这个gif图演示了我们使用cherry pick的一个完整流程(第一次录制gif见谅)

主要流程是我们建立了两个分支 dev1和devtest分支,并使用cherry-pick将dev1上的commit(日志信息为"test")合并到devtest分支上来,并且最后造成devtest分支也有该commit提交。

并且在这过程中通过命令行建立了远程分支。

当用户没有条件更新到新版本的时候,我们将新建一个分支,然后使用git reset hashcode(commit日志的唯一hash码,可以倒退回过去),然后进行测试以重现问题。

全过程如下所示:

我们可以看到,我们从fixed  #189的HEAD 回退到了 b5320c所代表的commit记录处。

7. 一个源文件,如何知道它的每一行都是什么时候签入的,为了什么目的签入的 (解决了哪个任务,或者哪个bug)?

   场景:一个重要的软件忽然出现崩溃的情况, 程序员果冻经过各种debug手段,发现问题是在某一个文件中有一行代码似乎显然出了问题,但是这个模块被很多其他模块调用,这行代码是什么时候,为了什么目的,经过谁签入的呢?如果贸然修改,会不会导致其他问题呢?怎么办?

答:针对一个源文件的每一行是在什么时候签入,为了什么签入,在github里有非常好的支持,如下图:

 在上图中我们可以看到,Handle10611.tex在两次提交中被修改了,分别是在65b4fe1和9a9913d两次commit中被修改过,并且两次都带有比较清晰的commit日志,知道它是为何被签入,修改又是为了什么。以及所有跟它有关的所有commit提交信息。

一个良好的团队应当维护一个良好的commit日志,并且有所规范,但是我们组在这一点上还非常欠缺...

8. 如何给一个系统的所有源文件都打上标签,这样别人可以同步所有有这个标签的文件版本?

   代码每天都在变,有时质量变好,有时变差,我们需要一个 Last Known Good (最后稳定的好版本) 版本,这样新员工就可以同步这个版本,我们如果需要发布,也是从这个版本开始。那么如何标记这个 Last Known Good 版本呢? 

 

使用git来打tag这件事,在Github中是可以很方便来做这件事:

每次发布到一定成果后,就需要发布一个realease版本,但是这样的话,是对commit本身打标签。在git里,标签分为两种类型:轻量标签和附注标签。轻量标签是指向提交对象的引用,附注标签则是仓库中的一个独立对象。

$ git tag v1.0.0

 

想查看tag的话,可以使用git tag来查看,如下:

如果想回到某个标签时某个文件的状态,那么只要使用git checkout tag(标签名) 即可,如下面这个gif所示:

 

可以看到我们的项目在发布时一共发布了1.1.0,1.1.1,1.0.0三版。

 

9. 你的项目的源代码和测试这些代码的单元测试,以及其他测试脚本都是放在一起的么? 修改源代码会确保相应的测试也更新么?你的团队是否能部署自动构建的任务?

    在签入之前,程序员能否自动在自己的机器上运行自动测试,以保证本地修改不会影响整个软件的质量?

    在程序员提交签入之后,服务器上是否有自动测试程序,完成编译,测试,如果成功,就签入,否则,就取消签入?

    团队是否配置了服务器,它自动同步所有文件,自动构建,自动运行相关的单元测试,碰到错误能自动发邮件给团队

 

答:配置了服务器,一开始使用的是Travis-CI来自动集成测试,但是由于网络因素,Travis-CI登录很慢,所以最后决意采用了drone.io来进行自动化的单元测试,每次测试都会自动按照预定的脚本运行单元测试,单元测试通过以后可以在Github的ReadMe里体现出来。

是这样的标志:

点开build passing的示例,我们可以看到在drone.io中我们部署的自动化测试:

  

每一次commit都会触发自动化测试,在部署成功后(只针对master分支),已经test了38次。

 在Setting中也可以看到,我们在build出错时,会自动通知我的邮箱(qianlxc@126.com)

posted @ 2015-12-22 12:43  软剑攻城队  阅读(2215)  评论(4编辑  收藏  举报