作者归档:myCode

基于 git flow + gitlab 协作开发:01 抛出问题

很久以来,我一直在寻找一个适合小型团队独立项目的 git 协同工作流。主要原因是实际工作中很难在繁忙的迭代中兼顾真正的协同和代码质量管理。造成的现象就是在一个以月维度发布版本的产品中出现各种各样的分支、hotfix。到底哪个是主线,哪个分支修复了哪些问题、不同的分支是否与主线同步更新都是未知数。如果不叫一个从开始就参与到项目中的人给你做介绍,很难去做维护。

我参与的团队中使用的是内部 gitlab 服务器做代码托管。我调研了 git flow / github flow / gitlab flow,每种工作流都有各种优势。gitlab 官方大名鼎鼎的 gitlab flow 工作流,可以支撑大部分真正进入敏捷开发的团队和项目组。但在一些观念尚未真正敏捷而敏捷先行的项目组中,gitlab flow 工作流很难“入乡随俗”。而 github flow 的简单粗暴缺又无法满足现有团队的需求。最终我的目光转移到了 git flow。

下面我们将以开发过程中实际遇到的问题为媒介,来介绍从工程管理到代码审查直至 CI/CD 在我们的团队中是如何运作的。但本文不会涉及到 git flow 的细节,更多请您参考:

混乱的分支管理

在我历史参与过的项目中,我看到过不同形式的分支命名,诸如 Dev_* / dev_* / Rel_* / Release_* / hotfix* 等等不同格式的分支命名规范。每个分支的 Git 路线图看起来错综复杂、犬牙交错!上一个版本与本次开发版本中命名格式不同、分支中出现人名、分支中出现公司名、分支中出现英文介词,这些都见过。而最终这些分支终结于哪里?好像没有一个人能说的特别清楚。

而 git flow 工作流和其工具链(我更喜欢叫它工具链,因为它帮我们完成的是一套命令和合集)则帮我们把这些都预先圈定好,通过固定的指令将分支命名保存为统一的格式。如果你使用默认的初始化参数,那么格式将是下面的样子:

E:\Documents\Repositories\solutions\git-flow (master -> origin)
$ git flow init -d
Using default branch names.
No branches exist yet. Base branches must be created now.
Branch name for production releases: [master]
Branch name for "next release" development: [develop]

How to name your supporting branch prefixes?
Feature branches? [feature/]
Bugfix branches? [bugfix/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
Hooks and filters directory? [E:/Documents/Repositories/solutions/git-flow/.git/hooks]
  • master 分支,始终最后稳定版本内容的分支,由 release 分支合并养成
  • develop 分支,所有新功能开发的基础、开发阶段冒烟修复问题等
  • feature/* 分支,一切功能开发的子分支,基于 develop,完成后合并到 develop
  • bugfix/* 分支,用于修复缺陷的分支名前缀。
  • release/* 分支,保存每一个版本的迭代信息,由 develop 分支生成
  • hotfix/* 分支,用于线上版本紧急修复的分支
  • support/* 分支,一般用于特殊功能支持的分支(不合并到主分支)

通过 git flow 工具链创建的分支,如 git flow hotfix start 1.0.1 会以固定格式进行命名,这样可以清晰的告诉我们哪些分支起源于哪里、用于做什么、终结于哪里。gitlab 中查看到的分支结构也会变得非常清爽。下图是 gitflow 的工作流的示例图:

Vincent Driessen 在 A successful Git branching model 一文中以另一种形式展示了 gitflow 工作流的详情图:

Hotfix 分支内容容易被遗忘

产品上线后,避免不了会出现一些测试过程中没有发现的边缘问题在客户场景出现,这就需要我们发布 hotfix 来帮助客户解决问题。但往往有些场景因为手动操作开启新的 hotfix 分支后很容易忘记将修改合并到发布分支和开发分支,版本发布比较多以后,会发现有一些 hotfix 分支在项目总仓库中,再加上命名的不规范,最终会不确定这些分支到底有没有合并到主干和开发分支。而 hotfix 有两种不同的场景,分别如下:

  • 线上最新版本出现问题,需要 hotfix
  • 线上非最新版本出现问题,需要 hotfix

两种情况下 hotfix 的流程是不一样的,线上最新版本出现问题,一般修复完成后需要将 hotfix 修改的内容合并到主干分支并且合并到当前开发人员正在开发的分支,确保下一次发布新版本的时候这个修复已经合并过去了。在合并完成后还需要打 tag 来表示本次修复的输出产物。git flow 工具链可以将这一系列操作自动化。当在最新版本中做对应的 hotfix 后,你看到的分支路线图类似于下图:

这些路线图结构清晰,一眼即可看懂。而线上非最新版本出现问题,在修复过程中要根据情况决定是否需要合并的到主干分支和当前开发人员正在开发的分支上。因为有些问题可能在后续版本已经修复而客户因各种原因无法升级到最新版本,所以需要在指定版本中做定制开发,这个可能并不能叫做一个常规的 hotfix,在 gitflow 工作流中,有些人更愿意称呼为 support branch。像下图一样:

其中 1.0.2 和 1.0.3 是针对旧版的一个支持修复,可能这个修复已经在主分支不适用了,但是我们依然是先创建了一个 support 分支,并在这个分支中最具体的 hotfix。这样可能会出现多个分叉,如果你的产品也有很多客户在适用就版本,那可能避免不了会出现这种场景。

参差不齐的 Git level

我们不能要求每一个从业人员对 Git 的熟悉程度都达到炉火纯青,如果项目组有 1~2 个人并且都 Git 各类场景都非常熟悉,我相信不需要引入什么工作流。但避免不了一个项目中有各类角色参与进来。他们可能在是精通某个领域的技术人才,也可能是架构设计的专家。但他们并不一定对团队协作开发非常熟悉,即使在参与项目前三番五次的传递过,而在没有真正的协作前,永远也不会知道这里面有多少陷阱。git flow 工具链将各类复杂场景简单化,只需要通过一些简单的命令就可以让参与项目的人员一起融入到协作中,如:

// 开始和完成一个功能
git flow feature start "name of your feature"
git flow feature finish

// 开始和完成一个版本的发布
git flow release start "version of your release"
git flow release finish

// 开始和完成一个 hotfix
git flow hotfix start "version of your hotfix"
git flow hotfix finish

但切记,git flow 不是什么教条、更不是什么灵丹妙药,理解他的工作流思维,而不是生搬硬套才能让团队协作更加流畅。这些仅是协作中的一部分,更大的一部分是协作过程中的代码审查,如何在 git flow 模式下工作配合 gitlab 做好代码审查,我将在下一篇文章中详细介绍。

Qt Quick 5.15.0 Connections 新语法

过去的 Connection 连接某个对象的信号时是这样写的:

Connections {
    target: authManager
    onError: {
        busyContainer.visible = false
        buttonJoin.enabled = Qt.binding(function () {
            return textMeetingId.length >= 11 && textNickname.length > 0
        })
        message.error(result.msg)
    }
}

新的语法中可以这样写:

Connections {
    target: authManager
    function onError(resCode, result) {
        busyContainer.visible = false
        buttonJoin.enabled = Qt.binding(function () {
            return textMeetingId.length >= 11 && textNickname.length > 0
        })
        message.error(result.msg)
    }
}

对于日常开发中,新的写法有助于我们定义槽函数的形参个数和实际作用。代码看起来更容易读懂。另外 Qt 官方文档中也介绍了一些其他好处,见:Qt官方文档

如果你使用 Qt 15.5.0 版本打开了一个旧的工程并使用的是旧的写法,那么会收到一个警告:

qrc:/qml/AnonJoinPage.qml:54:5: QML Connections: Implicitly defined onFoo properties in Connections are deprecated. Use this syntax instead: function onFoo(<arguments>) { ... }

既然是个警告,则不影响代码继续执行。但这样的警告不得不让你重写这部分代码来适配新的语法。

使用 CMake + Git Submodule 方式管理项目三方库

在项目开发过程中,避免不了要使用一些开源的三方库,我参加过的一些团队有不同的管理三方库的方式。不同的方式都各有优缺点,我们先列举一下碰到过的管理方式,说一些他们的优缺点,最后再来讨论我们今天介绍的管理方式弥补了哪些缺点。

统一管理三方库

一些团队是直接将三方库编译好放到 CI 机器上,在本地开发或者 CI 出项目的 Release 包时去做依赖拷贝。这样做有以下几点好处:

  1. 三方库不需要每次都重新编译,需要时拷贝,包括符号调试文件也可以一并维护。
  2. 大家公用一份三方库,确保三方库的出处都是一样的。

但是这样管理也有一些不足。

  1. 如果不同的项目需要依赖不同版本、不同分支的三方库,则统一的管理无法满足需求
  2. 三方库的版本管理迭代会保留诸多历史文件,一旦历史版本被清理则很难追溯历史缺陷的符号调试文件

上传三方库文件到 Git 中

编译好直接上传到项目的仓库中,这种方式有一些好处,比如:

  1. 工程下载下来不需要多余配置,启动 IDE 直接编译不废话。
  2. 自己想依赖什么版本就什么版本,不受限于其他项目

当然缺点显而易见:

  1. 静态库文件编译后非常大,上传到 Git 仓库中仓库会异常庞大,特别是三方库二进制文件的更新。
  2. 这种形式很容易出现源码和三方库文件不匹配的情况,这个项目编译好的复制到其他项目直接用,管理混乱。
  3. 跨平台项目要上传不同平台的三方库二进制文件到工程中

使用 CMake + Git Submodule 形式管理

通过以上的描述,我们已经很容易能看清楚不同的管理方式的优劣了。本文推荐的方法可以弥补一些上面方式的不足,但可能也因不同团队而异。推荐的方式大概分以下步骤。

  1. 将你需要依赖的三方库设置为 Git Submodule,每个项目可以依赖不同版本的三方库,不需要上传二进制文件。
  2. 脚本利用 CMake 自动编译三方库工程,不同平台可以使用 CMake 自动编译,不需要单独维护个别版本。
  3. CI 出 Release 时自动编译三方库并保留匹配版本的调试符号文件,和本地源码原始匹配不易混乱。

我们以一个 libyuv 三方库举例。首先将项目添加为 Git Submodule

git submodule add https://chromium.googlesource.com/libyuv/libyuv third_party/libyuv

添加完成后,我们写一个自动编译三方库的脚本,Windows 平台下使用批处理、macOS 下使用 Shell 脚本。

# Windows: Generate visual studio solution
cmake -H. -Boutput -G"Visual Studio 15 2017"
# Build release library
cmake --build output --config Release --target yuv
# Build debug library
cmake --build output --config Debug --target yuv
# macOS: Generate makefile
cmake -H. -DCMAKE_MACOSX_RPATH=OFF -DCMAKE_BUILD_TYPE=Release -Boutput -G"Unix Makefiles"
# Static library
cmake --build output --target yuv

这样在不同平台我们就可以自动编译三方库二进制文件了,本地开发时只需要将 Submodules 更新下来,头文件和库文件路径都修改为三方库文件的路径。CI 出 Release 包时也是先将 Submodules 更新下来编译后再编译项目主体。

Qt Quick Windows 下实现无边框窗口阴影效果

Qt Quick 中实现一个无边框窗口阴影效果的中心思想是将 Window 容器背景设置为透明的,在 Windows 容器中添加一个填满窗口的容器(如 Page、Rectangle 等),然后将这个容器设置一些边距,再给这个容器附加一个阴影效果,实现的效果如下:

继续阅读

Qt QML qtquickcontrols2.conf 使用系统未安装的自定义字体

现在很多视觉设计最终给出的字体可能并不是所有系统都安装过的,所以项目中可能需要单独把字体文件打包进去在程序运行的时候加载。Qt QML 有多种方式加载一个自定义字体,本文将介绍所有我知道的方式,大家根据自己的需要选择使用任意一种即可。

继续阅读

2020 新的开始

2019 年是“进展缓慢”的一年,也是我人生中具有重要意义的一年,更是在经历了诸多波折后回到人生正常状态的一年。虽然还有很多遗憾(包括家庭、感情、工作),但我还是要感谢这一年所有遇到过的人和经历过的事情,这些都无形中让我慢慢走向一个三十而立的人。

我将原有的个人博客域名指向了这个站点,这也意味着未来只会维护这一个博客,这里会有我成长所有相关的内容。按照惯例,总结过去一年的得失,对未来做一个展望。人总是要前进,在前进的道路上才能发现生活的意义。

继续阅读

递归修改当前目录 .cpp、.h、.md 文件为 UTF8 或 UTF8-BOM 格式

有些代码目录下的文件格式不同,会导致一些编译错误或者中文出现错误等等问题,下面脚本就是解决这个问题而生的。使用 Autoit3 编译脚本后放到你要转换的目录中,运行脚本会转换所有 .cpp、.h、.md 文件为 UTF8 格式,如果你希望修改成 UTF8-BOM 格式,可以将 $FO_UTF8_NOBOM 修改为 $FO_UTF8

继续阅读