掌握Git精髓,解决版本控制、工作流问题,实现高效开发

  • 关键词:Git For Teams – 读书笔记

总览

Git方法论

1.优秀的技术源自优秀的团队

2.尽早为项目设定预期

3.带有清晰的目标工作

4.记录有助于日后工作的文档

5.用实战技能武装自己

6.学会从错误中恢复

7.和同队成员系统工作

8.通过同行评审,分享出色完成工作时的荣耀和责任

9.探索项目历史,寻求问题解决之道

10.通过开放的协作促进社区的成长

11.想要编写优良的代码,团队必须拥有自己的仓库

Part1 制定工作流

1.团队作战

  • 团队成员
  • 思维策略
    • 创造性思维:预见未来、另辟蹊径、头脑风暴、灵光一闪、勇于质疑、保持专注
    • 和理解性思维:评估现状、阐明现状、善于组织、敏锐感知、产生共鸣、善于表达
    • “敢作敢当”思维:分清主次、善于总结、验证结论、身体力行、价值驱动、相信直觉
  • 团队会议
    • 时间表:辨别问题本质(10min)、头脑风暴,寻找解决方案(25min)、整理想法(5min)、挑选至多三个想法进行验证(5min)
    • 项目启动
    • 追踪进展
      • 冲刺计划会议(随时准备接手新工作并对做完的工作负责的员工和倾向于别人为他们安排好工作的员工)
      • 承诺会议(针对他们工作中作出的“承诺”进行汇报)
      • 深入研讨会议(比如谷歌日历)
      • 冲刺演示会议(有利于发现新想法、分辨文档记录或后续修复bug)
      • 冲刺回顾会议(讨论工作流程)
    • 培养同理心
      • 搜集故事
      • 带有目的地倾听
      • 旧事重提
    • 回顾

2.Git中的团队协作

专业术语 说明
本地仓库副本 项目中的更改历史的独立副本
克隆副本(或派生forking) 开发者创建一个他们可以更改的仓库副本(可选私有或公有)
签出(check out) 为了从我当前的分支切换到另一个
提交(submit) 将我的更改持久存储到仓库
储藏(stash) 暂存更改,允许你随后拉取你刚刚储藏的工作并重新应用
抓取(pull) 从远端仓库抓取更改,然后自动并入我的仓库
拉取(fetch) 拉取新的更改并将它们合并(merge)进入本地分支(branch)的跟踪(tracked)副本
推送(push) 将我的分支推送到远端仓库

命令与控制

分布式版本控制系统避免了集中式系统的权限控制方式。Git没有提供内置的规则来帮助你控制代码的访问权限,它只是一个内容跟踪工具。

1.项目治理:了解代码的归属,Git允许你追踪仓库中每一份代码的作者,甚至可以为添加到仓库的每一份代码加上签名

  • 版权和贡献值协议
  • 分发许可
    • MIT许可证:允许人们在注明原作者的前提下自由使用代码,并且你不需要为衍生的软件负责
    • Apache许可证:明确将原作者的专利授权给用户,并要求用户提供变更说明,描述你的作品在之前的版本上做了哪些修改
    • GNU General Public License(GPL)分为V2和V3:一个共享友好的版权协议,要求作品或衍生品的分发者将源码以相同协议共享
    • 创作共用许可证:允许你选择重新分发时是否能够修改以及能否用于商业目的
  • 领导力模型
    • 仁慈的独裁者(BDFL):领导者或许不会活跃于每次代码评审中,但最终保留了驳回或撤销某个决定的权力
    • 共识驱动、主管批准:当社区对解决方案感到满意时,他们将这个标记为RTBC(ready to be committed),通过社区审核与测试
    • 技术评审委员会或项目管理委员会:治理模型,该模型是基于一个Apache项目的项目管理委员会模型
  • 行为守则

2.访问模型

三种流行的访问模型:

单一仓库共同维护模型:团队中的每个人都是维护者,有权限向项目仓库上更改

并列贡献者仓库模型:提交贡献的开发者创建一个项目的远程副本,等待项目维护者接受他们的更改

分散贡献者仓库模型:代码通过文本格式的补丁包共享

分支策略

在版本控制中,分支用于隔离一块代码上产生的不同想法。

1.理解分支

开发者创造了关于命名分支和使用分支的约定来避免冲突。这些约定帮助开发者决定在什么情况下允许工作出现偏离(创建新的分支),以及什么时候合并(合并两个或多个分支中的提交对象)。

一般来说,一种约定包含了两类分支:

长期活跃的公开分支:扮演了代码中介的角色,并入大量开发者的贡献。

短暂的私有分支:隔离一个新想法的开发过程。这些新想法可以是一个bug修复、新增功能或实验性的重构。

当你和他人共享代码时,你可以继续在你的分支副本上添加提交对象;但是,既然分支已经被共享,可能其他人也在他们的分支副本上添加了提交对象。当你下次想要同步这两个分支时,Git作为纯粹的内容跟踪者,会转而借助你的经验来将这两份提交对象合并到一个共享的历史。自动化流程中的这个中断称为合并冲突(merge confict)。你的任务是解决冲突并为有问题的工作选择最合适的共享历史。

2.挑选约定

形成文档的约定让新参与者更加容易上手,而其他团队成员现在只需花费更少的时间来帮助新人。

挑选合适的分支策略,需要成员们共同讨论希望如何发布你们的工作成果。

3.几种约定

要么选择“优先合并”,要么查对完成的工作并一次性发布,要么介于两者之间。

主线分支开发

在这个策略中只有少量分支。开发者总是将他们的工作提交到一个中央分支,这个分支随时可部署。换句话说,项目的主分支应该只包含经过测试的工作,并且绝不应该被破坏。

主线分支开发:将所有提交保存在一个分支

使用分支的主线开发:分支隔离了多人贡献的工作

  • 使用鼓励持续集成工作的分支策略优点:
    • 整个项目中不会出现很多分支
    • 并入代码库的提交相对较小
    • 紧急修复会变得更少,因为主分支中的代码都是达到部署标准的。
  • 使用这个策略的缺点:
    • 这个策略的前提是主分支中只包含随时可部署的代码
    • 部署这个概念更适合指自动装载到用户设备的代码
    • 开发者验证产品代码的方法之一是将功能隐藏在一个开关后

功能分支部署

为了克服单分支策略的限制,可以引入两种类型的分支:功能分支和集成分支。严格来说,它们是依据每个分支的职能来区分的约定。

功能分支的部署策略中,所有新的工作都在一个功能分支上完成,这个分支小到恰好能够容纳一个完整的想法。

这些分支通过一个集成分支与其他开发者完成的工作保持同步。等到软件发布前,构建管理员可以挑选将哪些功能集成到这个版本,并为部署创建一个新的集成分支。

功能分支部署:功能分支之间通过集成分支保持同步

Github流程:功能分支在审查后进行部署,然后被并入到master分支中

  • 使用功能分支部署策略优点:
    • 类似于主线开发,我们力图让代码快速持续部署
    • 区别于主线开发,我们提供一个可选的构建步骤。当使用构建步骤时会出现一个选项,用于选择应该将哪些功能并入到master分支中进行部署
  • 使用功能分支部署策略缺点:
    • 如果代码存放在功能分支上,而不是立刻发布到master分支上,开发者需要进行额外的维护,保证在将代码并入部署分支前,功能都保持最新
    • 分支的语义化命名对于熟悉系统的开发者有帮助,但如果进行中的功能很多,新参与者会对这些行话感到困惑
    • 开发者需要定期清理那些已经并入master分支的遗留分支。这不算麻烦,但与在单一的master分支上工作,需要这个额外的步骤

状态分支

和之前提到的策略不同,状态分支策略引入分支位置或快照的概念。

一般使用的部署图通常过于简化,告诉我们代码从一个环境迁移到另一个环境,但事情并没有这么简单。

部署的谎言:代码其实并没有从本地服务器移动到产品服务器。。。

本地环境–>开发环境–>发布前的准备–>生产环境

真实的部署过程:使用了集中式的代码托管系统。。。

image

  • 在语义化版本命名中,如何增加版本号

在语义化版本命名中,发布的版本号总是应该遵循这样的格式:MAJOR.MINOR.PATCH。

MAJOR,主版本。应该在进行了无法向后兼容的API修改时增加。

MINOR,次版本。应该在增加新功能但是不会打破已有功能时增加。

PATCH,修订版本。应该在进行了向后兼容的bug修复时增加。

  • Git项目使用的分支命名约定是状态分支策略的一个有意思的变种(maint,master,next,pu)

maint:这个分支包含了Git最近一次稳定发布的代码以及小数点版本的额外提交(维护)

master:这个分支包含应该进入下一次发布的提交

next:这个分支用于测试一些主题在进入master分支后的稳定性

pu:这个建议的更新分支包含尚未准备好合并的提交

产生灵感——社区评审(pu分支->next分支)——官方发布(master分支->maint分支)

  • 使用状态分支策略的优点:
    • 分支名称与手头工作的上下文密切相关
    • 无需猜测就能明白每个分支的目的,在合并自己的工作时更容易选择正确的分支
  • 使用状态分支策略的缺点:
    • 在没有指导的情况下,并非总是能很容易找到从哪开始新建一个分支
    • 由于分支名与团队的上下文密切相关,在项目中保持一致可能会更困难,使得新人上手时更加困惑

计划部署

如果你没有一个完全自动化的测试集,却必须计划一个部署,那么计划部署分支是适合你的策略。

其中涉及到:

分支命名约定-功能冻结(没有新的功能要做了,只允许bug修复)

开发继续,但不会并入预发分支

代码并入一个新的分支master并打上标签,从而软件得以发布

一个补丁完成后进入master分支,现在我们的发布标签是1.0.1

image

  • 使用计划部署策略的优点:
    • 计划部署不要求你预先准备好全面的测试设施
    • 软件的构建过程通常伴随着开发、质量保证、生产这些阶段
    • 通过严格遵守约定,开发者总能明确自己应该从哪个分支开始工作
    • 这个模型同样适用于版本化管理的软件
  • 使用计划部署的缺点:
    • 对于刚接触到软件部署并且没有经历过整个开发流程的开发者,存在较多的认知负担
    • 如果开发者从错误的分支开始工作,要讲所有工作恢复同步是件困难的事情
    • 它不如持续集成那么流行

更新分支

在分布式版本控制系统中,整合外部工作的方法与你选择的分支策略无关。更新分支时,你可以选择其中一种策略:合并(merge)或变基(rebase)。

深入理解两种策略的差异之前,先来了解一下多个仓库之间的连接是如何维持的:

仓库之间的连接通过一个远程的引用建立。这个应用允许开发者将远程仓库中所有提交对象复制到自己的本地仓库。远程连接通常用于至少共用部分历史的仓库。

比如,你想要将你的工作添加到同事的分支上。你连接到他们的远程仓库,拉取分支并尝试添加你的工作。但你不能这么做。如果那是一个本地分支,你可以将分支顶端添加一些新的对象。但是,因为你希望更新的是一个远程分支,所以你无法将一个新的提交对象放置到你的仓库中那个分支的顶端,因为这个操作只能由远程仓库的所有者完成。与此相反,你必须先创建一个新的跟踪分支来储存你的更改。

注:一些跟踪分支是自动的,clone命令将会默认创建一个名为master的跟踪分支,和与它同名的远程分支保持一致。

已知你现在有:

1.一个可以添加新提交的分支的本地副本

2.一个不能添加提交的分支的引用副本

3.一个仍然存在于远程仓库的原分支

当你和你的同事分别在自己的仓库中进行更改时,这些分支将会不可避免地不再保持同步。

fetch命令将会更新分支的引用副本,下载所有新的提交。这个分支的可变跟踪副本,有多种更新方式(有选择就会有分歧)。

通过远程引用更新跟踪分支的过程,通常借助pull命令实现。

pull是两个无关步骤的组合:fetch和merge,或者fetch和rebase。

pull命令默认使用merge策略来更新本地分支,但是,通过添加–rebase参数,开发者可以选择通过rebase策略来更新自己的本地分支。

  • 两种变基的方式(变基在更新一系列提交时有两种方式)
    • 第一种,在相关分支上整合新的工作(更新一个分支)时用于代替合并
    • 第二种,通过增删修改分支上的提交历史中的特定提交来更改现有分支上的历史,达到使历史更加精简的目的

变基给人带来的印象是复杂和繁琐的。但从图形化的角度来说,变基是可读性最佳的策略。

一般来说,我们将变基解释为在一个已有的时间线上重放已有的提交。

变基两个分支时,只改变其中一个分支的历史,而另一个分支看上去没有变化

image

rebase命令用于更新分支,而merge命令用于引入全新的工作。

工作流

导视:基本工作流-集成分支-发布计划-发布后的补丁

初始工作流

使用git的工作方式可能会很快变得复杂,要记住所有事情就越困难。在你的团队中要建立约定来维护一致性,以帮助你快速理解你的代码历史。

记录工作过程

最简单的保持一致的方法是遵循一组规则,或是一份清单。每次在一个新的地方工作时,应该记录下你的工作流。

模板工作流 说明
项目经理 名字
开发站点 URL
开发站点上部署的分支 分支名
线上站点 53URL
线上站点上部署的分支 分支名
开始一个新的开发工单时,从哪个分支创建新的分支 分支名
开始一个新的补丁工单时,从哪个分支创建新的分支 分支名
更新工作时,使用 Git命令
评审后合并时 Git命令

注:有很多漂亮的图软件能够帮你勾勒你的想法(Balsamiq-Pencil-OmniGraffle-Dia-Inkscape)

记录编码的决定

使用bug跟踪工具来捕获所有需求。从本质上讲,不同的跟踪模块允许你记录、跟踪即将完成的工作、需要完成的任务,以及与任何在质量保证测试中发现的后续问题相关的讨论。

重点是:易于阅读且方便搜索的系统中,至少记下决定做这些功能的原因。

工单进展

建议每周一个冲刺,在每个冲刺的最后,组织一次内部展示。

  • 尚未开始(产品积压工作)
  • 准备开工(准备开发、准备代码评审、准备测试、准备客户批准和准备部署)
  • 进行中(制定中、开发中、测试中)
  • 已完成
例子 说明
准备齐全 这类工单准备开始做,且应该在本周完成
进行中 这类工单正在活跃地进行中
合并请求 代码已经完成,等待评审后并入主分支
需要测试 代码已通过评审,并被推送到开发分支。准备在质量保证服务器上接受团队成员的测试
已完成 工单已完成。这个状态也包括已关闭但未完成的工单(重复的任务、不再需要的功能)

如果开发者遇到了解决不了的问题,他应该将这个工单重新分配给最有可能“解决”这个问题的人。和别人交换工单的习惯是文化的一部分。

基本工作流

在不增加复杂度的情况下,整合了功能分支的工作流。你会发现这个工作流对于拥有测试设施的开发团队,也就是致力于快速部署代码的团队来说表现良好。

  • 关键特征:
    • 治理模型:贡献者共同维护
    • 集成合并:由原开发者执行
    • 整合分支:develop
工作流如下所示 说明
当你开始一个工单时,在工单跟踪工具中更新状态,比如将它标记为正在进行 这会通知你的团队你正在进行的工作,并告诉你为了完成这个工单你需要创建的分支数量
从develop分支上创建一个命名包含工单编号和大致的工作描述的新分支 如果你正在进行的工单拥有子任务,这个工单在你的工单系统中可能称为元工单或Epic工单。如果你的工作只是未来工作的一部分,你应该使用相关最小的工单号。你的工单系统可能会将这些工单称为用户故事、issue或bug工单
在你开始工作后,确保每次可能将更改并入master分支之前,工单分支能够保持最新状态 每个提交说明都以包含工单号的方括号开头:[#1234]
运行相关的测试,保证你的代码中的拼写错误和常见错误都能被发现 这一步可能包括一个拼写检查、一个语法检查(Lint静态语法检查)。如果你在测试驱动的环境中工作,那么一定会有额外的测试需要运行
当你完成工作的时候 上传最后一个提交,包含关键词resolves,后跟工单号:Resolves #1234
可以选择将你的工单分支推送到代码托管仓库 使用提交说明中的关键字,这一推送会将你的工单跟踪工具带入下一步
用你的工单跟踪工具为这个工单添加一条评论 说明你使用某个方法的原因,并证明工作已经完成
确保工单分支保持最新,然后将你的工作合并到develop分支 假设没有合并冲突,将更新后的分支推送到中央仓库
假设新添加的工作没有带来任何新的问题(回归) 这个工单就可以关闭了
最后,删除你本地的工单分支和这个工单分支的远程副本  

使用同行评审的可信开发者

  • 关键特征:
    • 治理模型:贡献者共同维护
    • 集成合并:由审查者执行
    • 整合分支:develop

与最基本的团队工作流相比,增加了一个同行评审环节。每个工单都由另一个团队成员从代码的角度进行评审。

1.当你开始一个工单时,在工单跟踪工具中更新状态,表示这个工单已经在进行中了。

2.从develop分支的本地副本上创建一个新的分支。

3.进行你的工单,使用变基来确保你的分支是最新的([#1234],Resovles #1234)。

4.运行相关测试,去掉语法错误和常见错误。

5.将你的代码推送到代码托管仓库。这是备份。

6.当你完成这个工单时,确保工单分支与develop保持同步,然后将你的工作上传至代码托管系统。工单跟踪工具中将工单标记为需要测试。

如果需要手动评审,而且没有完善的自动化测试,评审者需要完成以下步骤:

1.根据原始的工单描述对工作进行一次评审。

2.合并工单分支和develop分支,假设没有合并冲突,将更新后的分支推送到中央仓库。

3.假设没有出现回归,评审者会关闭工单,通知开发者他的工作已经并入主分支。

当团队大到拥有评审流程时,建立一台共享的开发服务器就很有用了,团队可以实施定期的工作展示。为了减少团队成员检查develop分支的最新版本以及构建软件的开销,你可以选择搭建一个Jenkins(http://jenkins-ci.org/)实例来使这个流程自动化。

需要质量保证团队的不可信开发者

我们假设有一个不可信的开发者,不允许将任何人的工作并入主分支。

  • 关键特征:
    • 治理模型:具有并置仓库的贡献者
    • 集成合并:由评审者执行
    • 整合分支:develop

开发者在代码托管系统中派生一份项目来开始工作,然后在本地克隆这份仓库的派生副本。这一步只执行一次。

你可能会希望你的合约雇员(不可信的开发者)在派生仓库中工作,而不是给他们主项目的写入权限。

根据计划发布软件

使用发布计划来讲新版本软件呈现给用户。

发布稳定版本

1.从一个基本的提交开始,创建一个名为master的新分支。

2.使用易于记忆的命名约定给这个提交打上一个版本号标签(v1.0)。

3.将更新后的仓库推送到集中式代码托管系统。如果没有使用自动化构建过程,就使用新的代码更新相关的服务器。

在第一个发布完成后,你现在可以将工作切分为稳定、面向公众的工作和正在进行的开发。

正在进行的开发

考虑两个不同的环境:监控线上代码的健康状况;同时继续开发,添加新的功能或改善已有的功能。

设置(周一)的工作内容如下:

  • 所有develop分支上的工作并入测试分支qa。周一前完成及美元通过同行评审的工作只需要留在原来的工单分支即可
  • 将测试服务器更新至qa分支的最新版本
  • 为上周完成的所有用户故事创建一个QA清单。使用标准化的工单格式很容易编写这个清单。

测试(周一和周二)的工作内容如下:

  • 运行自动化测试,确保关键业务的交互没有出现回归(网站访问者和会员仍然可以使用预期的功能)
  • 团队成员负责测试清单中的所有项目,并将工单的状态更新为pass或者fail
  • 任何发现的bug都应该打开新的工单,并在发布日前提出,要么通过新的修复解决,要么从qa分支上移除相关的提交

发布日(周三)的工作内容如下:

  • 将qa分支并入master分支,打上发布版本的标签
  • 通过签出master分支上的提交,也就是项目最新的发布,更新线上网站。使用显式标签确保你可以轻松退回到先前的状态

将新功能和修复的公告发布到开发博客。

发布后的补丁

有时部署的代码存在错误。如果一个bug需要在下次软件发布前得到快速修复,这时就需要进行一个计划外的修复。这些部署通常称为补丁。

在补丁中,工作不是从develop分支开始,而是从master分支开始,确保更改只引入了部署代码中发现的问题的修复。

1.创建一个补丁的分支,签出master分支,而不是develop分支,确保没有意外地将额外的功能加入这次修复。

2.生成标签名的列表,定位到最新打上标签的发布。

3.从master最新打上标签的发布上,创建一个新的分支,使用hotfix--格式的分支名(hotfix-1234-fixing_thress_seat_issue)。

4.完成与开发工单相同的评审步骤。

5.将通过测试的补丁分支并入master分支。

6.将master分支上的最新提交打上最新发布的版本号(v1.0.1)。

7.将经过测试的补丁分支并入开发分支,保证在下次软件正式发布时这些修改不会丢失。

非软件项目中的写作

我想要新写一章,并保持这些提交干净整洁,但有时候一章正写到一半,进来了一份编辑修改,我希望及时处理。

设置如下的分支结构:master、drafts、以及每章单独的一个分支。

drafts分支让我能够整合我正在进行的所有工作。它通过合并已完成的章节来保持同步,或是在编辑进行修改后变基master分支。