嵌入式开发场景下的代码管理方案

特别鸣谢:感谢ChatGPT和New Bing,真是写作之利器。

版本控制,也称为源码控制、代码管理,是跟踪和管理软件代码的工作实践。随着信息化、数字化技术的发展,源代码逐渐成为企业的核心数据资产,在企业中有着非常重要的地位。所以如何管理好代码这个数据资产,是每一个企业都需要考虑和解决的问题。而作为数字化时代的先行者,大部分互联网和科技型企业已经完成或者部分完成了这项工作,并借助敏捷开发、DevOps等方法论和工具实现了代码的规范、可靠管理,以及高效高质量的产品交付。

在产业数字化转型,企业DevOps转型的大趋势下,近些年有不少传统嵌入式开发的企业开始参与进来。嵌入式与互联网、科技公司有着不同的技术栈、开发模式和交付方式,对他们来说,并不能发扬拿来主义精神,将“前辈”的经验直接复用。需要结合实际情况走出一条不一样的路,这里首当其冲要解决的就是代码的可靠管理、高效协同以及高质量交付问题。

1. 嵌入式开发场景的代码管理特点与诉求

1.1 特点

根据我过往工作经历以及接触到的部分客户来看,嵌入式开发场景的主要特点如下:

  • 团队规模:团队规模不大,多数在100人左右。

  • 技术路线:主要以C/C++为主。

  • 开发模式:主要以瀑布流为主。

  • 需求管理:需求相对简单,相较于应用系统的复杂业务逻辑,嵌入式更多是在跟芯片、元器件打交道。

  • 代码大小:较小,多数在KB或者MB级别。

  • 协同方式:主要以单人开发为主,无需协同。

  • 交付频率:较慢,大部分按照月度进行交付。

  • 交付方式:固件为主(单片机、下位机等),少部分是可执行程序(上位机)。

  • 测试方式:真机烧录手动测试为主,极少团队使用仿真模拟测试。

  • 系统工具:主要围绕需求管理和代码管理。

    • 需求管理:常见有Word、Excel等电子文本方式,或使用专业PLM、ALM工具,又或是无管理工具。
    • 代码管理:以SVN为主,或无管理工具,仅依靠U盘、网盘、文件服务器进行归档和传递。

在这样的背景下,从事嵌入式开发的企业其实也是不紧不慢,安稳度过了一段时期。但在数字化转型的浪潮下,很多企业管理者开始居安思危,希望借助一些先进的开发和管理模式,帮助企业和研发团队转型升级。

1.2 诉求

如上文所述,嵌入式开发场景的特点决定了他们的工作模式,也带来了一些弊端。简而言之就是缺少管理、缺乏规范、不成体系。根据我接触到的客户情况来看,从事嵌入式开发的管理者和研发人员对于管理企业源代码的主要诉求如下:

  • 统一管理:源代码是企业核心资产,有必要进行集中、统一管理。

    • 对于无代码管理工具的团队,需要使用代码管理工具,避免企业源代码丢失、泄露。
    • 对于企业存在多套代码管理工具的情况,需要尽可能统一代码管理工具,避免管理不受控或增加管理成本。
  • 权限管控:需要实现最基本的无权限、只读、读写、管理等权限分配和管控。

  • 版本控制:需对代码进行版本控制,实现最基本的查看历史记录、查看提交人、进行版本回退等功能。

    • 对于开发人员,版本控制提供了一颗后悔药,避免代码丢失或误删除、误修改后无法找回的问题。
    • 对于管理人员,版本控制提供了一套可追溯、可回退机制,避免人为恶意删除、修改代码等情况的出现,确保企业资产安全可靠。
  • 代码规范:需要对开发人员的代码规范进行约束,避免开发人员上传一些无关要紧的大文件以及不规范的代码提交信息,从而导致性能问题并影响协同效率。

  • 代码复用:需要有代码复用的能力,避免企业内重复造轮子,避免相同功能的代码在多个项目中多次实现。

  • 分支策略:需要一套分支策略和工作流,实现单人、多人对于一个项目的持续开发、修复、发布,并且能较好的区分和管理不同的环境和版本,如开发环境、生产环境以及为某客户定制版本。

  • 审核机制:需提供一套审核机制,实现代码的评审和确认,未经审核的代码不允许进入代码管理系统,从而提高代码质量,提升开发团队的综合能力。

  • 安全审计:需记录系统用户在代码管理系统上的行为和操作,便于对一些风险行为进行分析、追溯和告警,降低代码泄露的风险。

所谓工欲善其事必先利其器,嵌入式开发团队或企业要解决的问题远不止如此,但很多团队选择先从工具侧入手,基于一个好的工具再慢慢探索和完善其实践方式和管理流程。

2. 嵌入式开发场景的代码管理工具与方式

纵观版本控制系统,即代码管理系统的发展史,大致分为4个时期,它们对应的主流工具如下:

  • 1980年:RCS
  • 1990年:CVS、VSS、Perforce
  • 2000年:SVN
  • 2005年:Git

毫无疑问,Git是当下用于源代码管理的主流工具,它的发起人正是Linux之父Linus Torvalds。早在2002年以前,世界各地的Linux开发者通过邮件的方式把代码diff发送给Linus本人,再由他人工评审和合并,效率非常之低下,社区开发者也表达了不满。而Linus非常反对CVS、SVN等集中式的版本控制系统,它们性能较差且必须联网才能使用。在2002年至2005年这段时间,BitMover公司授权Linux社区免费使用它们的商业化版本控制系统BitKeeper,然而在2005年,Linux社区的开发人员却试图破解BitKeeper,导致BitMover公司收回使用权。故事的最后,Linus本人花了两周时间用C写了一个分布式的版本控制系统,就是Git。所以Git自诞生之日起,就是为了更好的管理Linux的核心代码。它跟Linux一样也是开源的,并且在社区开发人员的持续贡献下发展了快二十年。

而SVN作为上一代的版本控制系统,开始逐渐退出历史舞台,今年1月,GitHub也宣布自2024年1月8日起,停止对 Subversion (SVN)的支持。

对于没有使用任何代码管理工具的企业,没有历史负担,会直接选择Git。而大多数的从事嵌入式开发的团队,都在使用上一代的源代码管理工具SVN,这时候他们要考虑的就是Why和How的问题了。

2.1 SVN与Git

要考虑为什么或者要不要从SVN切换到Git,最直接的方式就是对它们进行一个对比。

总结一下:

  • 架构:SVN是集中式架构,Git是分布式架构,下图是对它们架构的一个描述。但从架构模式上来看,也不太容易能理解两者的区别,那么可以参考灵活性这项的内容,并且举一个形象的例子,更有助于理解它们显著的差异。

    • SVN像直播,用户必须联网才能观看(提交代码)。
    • Git像网盘,用户可以下载到本地进行观看(本地提交),并且可以进行编辑、传播,有需要时再同步回网盘上(远端同步)。

    所以Git能满足远程办公、异地办公等临时脱离代码服务器的开发场景,某种意义上更符合未来协同办公的发展趋势。同时为开发人员在本地进行版本控制提供了可能性,这使得开发人员在有后悔药可以吃的前提下能够在本地进行非稳定功能的开发,等功能相对稳定后再同步到远端,这为多人协同开发打下了基础,避免不稳定的代码影响到他人。

    如果你在使用SVN,为了解决上面的问题,就不得不多在本地建几个文件夹存放临时的代码副本,这一点也不优雅。

  • 安全:由于架构模式的区别,两者在安全可靠方面也有不同的表现。

    • SVN是单副本,除非服务器有备份,否则服务器挂了代码数据就都会丢失,可靠性较低。
    • Git是多副本,每个开发人员本地都有一套拷贝,并且包含所有历史记录,虽然可能不是最新版本,但发生极端情况时,更容易恢复,数据的可靠性更高。

    当然,Git的副本数越多,可靠性也就越高,同时存在数据泄露的风险就越高,数据的安全性又面临挑战,这是另外一个话题。好在国内企业对于数据隐私、数据保护非常重视,大部分传统企业已经通过DLP(Data loss prevention 数据泄露保护)技术实现了对企业内部系统数据的加密和保护,如IP-guard等工具,这个技术对于SVN和Git同样有效。

  • 权限:SVN和Git在权限模型上也有较大的差异。

    • SVN支持对代码库中的目录进行权限控制。
    • Git只提供对整个代码库的权限控制,无法对代码库中的目录进行权限控制。

    这是两个工具设计理念引起的差异,这也可能是SVN用户唯一不愿意迁移到Git的理由了。

    由于SVN支持按目录授权,早期也没有太多模块化设计的实践,所以使用SVN用户习惯性的把整个项目的所有组件和相关依赖塞进一个仓库,走的是大仓模式,再根据需要对目录或整个仓库授权。这又导致SVN的仓库普遍臃肿、容量大,加剧了性能问题。

    而Git的理念是模块化的,倡导解耦,走的是分仓模式。如果代码彼此之间业务和功能逻辑相对独立,并且能够独立编译,那么就应该放到不同的代码库进行管理和授权。如果代码彼此之间有强依赖,无法独立编译,那么可以放到一个仓库里,但就没必要进行按目录授权了,因为不具备某些目录权限的开发人员无法通过编译来验证自己开发的功能是否正确,这样就失去了协同开发的意义。当然,有些以Git为底层的代码管理系统支持对代码库的目录进行写入控制,在一定程度上弥补了这个权限控制的缺失。

  • 性能:同样也是由于架构模式的区别,两者在性能上也相差甚远,用C++开发人员的话来讲。

    • SVN的分支是值类型,创建一个分支就相当于在服务端拷贝一份代码到另一个目录,效率低。
    • Git的分支是指针类型,创建一个分支只是创建代码提交记录的指针,效率高。

    正因如此,SVN的用户基本上不会有多个分支,因为分支越多,性能越差。分支少决定了SVN的用户无法实现多人协同开发,一旦多个开发人员在同一个分支下进行开发,那么产生冲突的概率就会增加,并且相互影响,后提交的人需要解决冲突,强制覆盖后又会影响先提交的人,最后变成竞赛游戏。而在嵌入式开发场景中,一个固件对应多个版本,或者针对不同芯片、不同用户又有定制版本的情况时有发生,又不得不利用分支来进行管理,分支数量一上去,分支间的代码同步和性能问题又开始显现,可谓是让SVN的用户骑虎难下。

    Git与之相反,依靠灵活快捷的分支管理方式,配合多种分支策略,可以实现多人协同开发、按环境和版本区分代码、并尽可能确保通用的代码在不同环境、版本间保持同步。分支策略本身也决定了研发流程的规范建设,这也是企业关心的核心内容。

  • 体验:

    • SVN的命令较少,易于学习,操作简单。常用TortoiseSVN这款图形化客户端工具进行操作。
    • Git的命令较多,较难学习,操作相对复杂。熟悉命令的开发人员习惯使用命令行进行操作,而对于初学者建议使用SourceTree等图形化客户端或主流IDE的Git插件来操作。

    从学习和使用层面来看,Git的成本相对较高,但也有一些降低门槛的方法和工具。相较于架构、模式所带来的变化,操作体验上的变化显得微不足道了。从另一方面来说,Git也算是开发人员的基本技能,毕竟全球94%的开发人员都在使用Git或者会使用Git。

    此外,因为SVN支持按文件进行下载,再加上操作简单,非技术人员比如产品经理、项目经理也容易上手使用,所以非常多的SVN用户使用SVN进行文档管理,比如Word、Excel等。Git恰恰相反,不支持按文件下载(可以通过部分克隆实现,但对于非技术人员成本略高),必须把整个仓库下载下来,操作也比较复杂,所以当这些用户迁移到Git后,旧文档的管理方式又变成了一个问题。对于这个问题,文档就应该放在文件服务器(FTP、SMB、NFS、NAS等)或者文档协同系统(Wiki、Confluence、腾讯文档、飞书等)上进行管理。放在SVN上,在以前的时代虽然可行,但集中式的架构需要在线才能访问,且大文件使得SVN本就不足的性能雪上加霜,此外现在人们对于文档管理的需求除了管理、存储、版本控制之外,还看重协同,所以如果决定从SVN向Git进行迁移,文档的管理模式也需要进行升级。

综上,从SVN迁移到Git,优势自然是顺应技术发展的趋势,相较于SVN这个已经没有任何官方支持的上一代产品,有非常多的以Git为底层的成熟工具和商业化产品,比如GitLab、GitHub、Gerrit等,可以为企业DevOps转型提供更好的支持。此外Git可以从根本上解决SVN的性能问题,实现更好的研发协同,便于企业建立规范的研发流程。但问题是除了工具本身和使用体验上的差异,Git在分仓模式、分支策略、授权模式上还有不同,甚至直接影响研发流程的改变,这里面有哪些潜在的风险,又该如何去解决,为此我会在后续章节来展开,详细讨论这些问题。

2.2 分仓、权限与依赖问题

在上文中我已经简单介绍了SVN和Git在分仓模式和权限管理上的一些区别,简单来说:

  • SVN支持目录授权,常用于大仓模式。
  • Git仅支持仓库授权,常用于分仓模式,也支持大仓模式。

这本是一个简单的选择题。要么留在SVN,保留现有的目录授权模式;要么使用Git分仓,权限也落在不同的仓库上;要么使用Git大仓,权限就控制在整个仓库上。直到另一个需求打破了这个平衡:代码复用。

做过C/C++且又做过Java、Python、Node等语言开发的技术人员常常会感叹C/C++缺少好的包管理工具,羡慕Java有Maven、Python有Pip、Node有Npm,而C/C++ 一直深陷DLL地狱、代码版本与交付版本不一致、重复造轮子等各式各样依赖问题的泥潭不可自拔。

首先C/C++由于其语言特性,构建时常常是系统底层相关,所以打包时需要考虑操作系统、体系架构、编译器版本、构建类型等一系列因素。且需要关注一些包自身的属性:是纯头文件库、静态库还是动态库,以及包的构建参数(比如优化级别、是否开启exception和rtti的编译选项等)、还有指定裁剪性(特性宏)等配置。另外,由于C/C++的标准库(glibc和libstdc++)存在版本兼容性问题,以及C++存在ABI兼容性问题,这会让包的版本管理超越语义化版本(SemVer)所能解决的问题范围,这导致包的创建者需要在发包的时候为包的兼容性做更多的考虑。最后,由于C/C++语言在语法上缺乏包级别的模块化机制,会让包的符号冲突以及依赖解决变得困难。如果还不够,再加上交叉编译的场景,绝对会让一个通用C/C++包管理器的复杂度超过其它任何语言。

包管理器的目的就是对包进行版本控制,可以解决项目间的依赖问题,也能从根本上解决代码复用的问题。但由于C/C++包管理器的复杂度,导致C/C++开发人员在过去很长一段时间内没有包管理工具可用。在这样的背景下,大家对于C/C++的代码复用发展出了很多模式。

  • 基于代码文件目录划分

    项目划分好模块,定义好自己的目录,协商出一个Common目录作为公共的头文件目录,然后对不同的开发人员分配不同的目录权限就可以分工协作了。看起来是不是非常眼熟,这就是SVN的使用方式。然而这个Common目录可能又需要被另一个项目B所引用,那么项目B整个代码也加进这个代码库。最后所有的模块都耦合到一起,代码库膨胀的很快,导致严重的性能问题。

  • 基于代码复用工具划分

    由于SVN性能问题,很多开发者开始尝试迁移到Git,把代码分不到不同的代码库中。但由于没有包管理工具,嵌入式的项目需要先将项目关联的代码库拉到一起,然后再构建。而Git本身不提供仓库之间的关联关系,这就对分库之后的聚合管理提出了需求。后来出现了git submodulegit subtreegit repo等工具,就是为了解决分仓后,如何把项目再聚合出来,从而实现项目管理和代码复用。但这些都是在代码文件级别的复用,增加了管理的复杂度。

  • 基于CMake

    一些构建工具的发展,为C/C++的代码复用引入了更好的方式。例如CMake从3.0版本开始被称之为“Modern CMake”,是因为它引入了target的概念,以及基于target建立起了构建的依赖可见性和传播控制机制。这些都更好的支持了代码在构建上的模块化,借助CMake的ExternalProject和find_package特性,使得我们可以从指定的http或者git分支下载、构建、安装和引用代码库。但是这种复用方式,对于间接依赖的管理仍旧是不足的,没有办法做到全链条的依赖解析、依赖追溯、冲突判决,以及基于变更进行最小范围的重构建和发布管理。

  • 基于包管理工具

    解铃还须系铃人,问题发展到最后还是回到问题本身,C/C++没有好的包管理工具,那就做一个。Conan是一款出色的开源C/C++包管理器。它吸收了很多现代化包管理器的设计思想,探索解决通用C/C++包管理器的各种挑战。它需要使用Python进行配置,目前在国内的普及度还不算高,相关的文档教程也不是很齐全,相对来说有一定的门槛,但Conan可以说是目前C/C++唯一可用的包管理工具,也可能是真正的破局者。

这就是为什么一些软件企业从SVN迁移到Git没有那么大阻力而从事嵌入式开发的企业则不同的原因。归根到底是C/C++缺乏好的依赖管理手段,而企业、管理者、开发人员一直都面临代码复用这个问题,并希望通过从SVN迁移到Git来解决这个问题。但显然这个问题仅依靠SVN或者Git自身是无法解决的。

2.3 基于Git进行多仓管理

既然Git现在是代码管理的主流方案,并且依靠Git自身无法解决分仓后的多仓管理问题和代码复用问题,那就需要借助一些其他的工具和方法,其实都是上文中提到过的。虽然这些工具和方法本身不够完善,但对于处于不同阶段不同场景的企业和用户,可以有个参考,毕竟软件世界没有银弹。

2.3.1 Git submodule

Git submodule可以让一个Git仓库作为另一个仓库的子目录,从而实现在一个代码库中引用其他的代码库进行复用。

Git submodule的特点如下:

  • 在主库中通过 git submodule add <子库git地址>命令实现引用子库代码。

  • 在主库中通过.gitmodule文件来记录主库和子库的引用关系。

  • 主库只是引用了子库的SHA,并没有直接拷贝子库代码,所以子库的代码变更内容在主库上不可见,只是在本地拉取时将对应的子库拉取到本地。所以在Git服务器上看不到完整的项目代码,这也意味着无法实现对于整个项目的代码评审。

  • Clone主库不会自动Clone子库,除非在Clone主库时:

    • 执行git submodule update
    • 执行git clone --recursive
    • 添加Git全局配置git config --global submodule.recurse true
  • 子库commit/push 也需要在主库 pull/commit/push,容易遗忘导致代码未同步或者主库关联了旧的子库。

    • 可在主库执行git submodule foreach 'git pull origin master'更新所有子库。
    • 由于该问题导致主库、子库在解决冲突、切换分支时变得非常复杂,且容易出错。

个人不建议在实际项目中使用Git submodule,除非能有效解决以上问题,或者可在少量的项目中进行试用再进行决策。

2.3.2 Git subtree

Git subtree与Git submodule功能类似,但目前Git subtree在开发人员中的呼声高于Git submodule。

Git subtree的特点如下:

  • 在主库中通过 git subtree add --prefix=<主库子目录> <子库git地址> <子库分支>命令实现引用子库代码。

  • 主库拷贝了子库的代码,所以在Git服务器上可以看到完整的项目代码,也可以实现整个项目的代码评审。所以Git submodule is Link, Git subtree is Copy. 也意味着Git subtree的性能略差,会增加主库的大小。

  • 无法通过默认的Git命令将主库的代码变更同步到子库,需要适应新的工作流。

    1
    2
    3
    4
    5
    6
    # 添加子库
    git subtree add --prefix=<主库子目录> <子库git地址> <子库分支>
    # 从子库中拉取
    git subtree pull --prefix=<主库子目录> <子库git地址> <子库分支>
    # 推送到子库
    git subtree push --prefix=<主库子目录> <子库git地址> <子库分支>
  • 可通过一些工具和方法简化工作流。

    • 设置Git别名以简化操作。

      1
      2
      3
      4
      # 在 .gitconfig 文件中添加
      [alias] gits = subtree
      # 然后执行命令
      gits add --prefix=<主库子目录> <子库git地址> <子库分支>
    • 设置子库为主库的远端别名以简化操作。

      1
      2
      3
      4
      5
      # 添加子库为主库的remote别名
      git remote add -f subA <subA>.git
      # 然后执行命令
      git subtree add --prefix=A subA master
      git subtree pull --prefix=A subA master
    • 使用第三方工具git-subrepo.

个人建议可以在依赖场景不复杂的中小型项目中使用Git subtree,以避免性能问题,它的体验和工作方式相对比较友好。

2.3.3 Script/CMake

最简单的办法往往最有效,通过脚本来拉取或者相关子库的代码,将脚本放在主库中,按需执行,比如拉取相关子库代码:

1
2
git clone -b <子库A分支> <子库A git地址> <本地目录A>
git clone -b <子库B分支> <子库B git地址> <本地目录B>

或者通过CMake的FetchContent来实现,可以参考《C++ 工程依赖管理新方向:CMake & Git | KC的废墟堆》,基于FetchContent可以再封装一套脚本。

个人建议也是可以在依赖场景不复杂的中小型项目中使用,比较轻量和灵活,但有一定的技术门槛,甚至需要专人来做,这对做传统嵌入式开发的团队是个挑战。

2.3.4 Git-Repo

Git-Repo是Google开源的一款Git客户端工具,是为了搭配Google开源的代码管理工具Gerrit进行使用,而Gerrit是为了管理Android的开源项目AOSP而设计的。Google是为数不多的坚持大仓模式(Monorepo)的巨头公司,AOSP也是一个大型项目,一个项目包含了数百个代码库,彼此之间存在依赖关系。所以为了更好的管理这些仓库,Google形成了自己独特的AOSP工作流,Gerrit通过一个项目清单Manifest.xml来组织仓库关系,Git-Repo就可以通过Manifest.xml来实现代码的批量拉取和推送。

Git-Repo和Gerrit主要是解决了多仓管理的问题,除了对仓库进行批量操作,Gerrit还支持跨代码库进行评审,所以AOSP从流程到系统到工具都是相辅相成的。Git-Repo可以在一定程度上解决代码复用问题,不过Gerrit本身不是商业化产品,没有厂商技术支持,且Gerrit的用户体验较差,复杂度较高,所以当嵌入式项目使用AOSP专用的工具,又显得有点水土不服。

借鉴Git-Repo和Manifest.xml的思想,阿里使用Golang重写了一个Git-Repo-Go工具,可以在GitLab、GitHub、阿里云效CodeUp等以Git为底层的代码管理系统上获得Git-Repo批量操作代码库的体验。但是上文中也提到,Git-Repo和Gerrit是相辅相成的,Gerrit支持跨代码库进行代码评审,支持更丰富的权限管理模式,为多仓下的代码管理和评审提供了基础。而GitLab、GitHub等代码管理系统目前还是以分仓模式为主,原生不提供这种业务功能,所以导致Git-Repo-Go仅仅是实现了Git-Repo的部分功能,这时不免怀疑这么折腾为啥不直接用Gerrit。

2.3.6 Conan

终极方案,如果项目依赖相对复杂,需要在项目级别进行代码评审,且要考虑依赖解析、循环依赖、依赖追踪等问题,那么以上工具方案都不用考虑了,它们都无法从根本上解决问题,所以Conan这个工具必须死磕下来,不管是头文件、静态库还是动态库的管理,Conan都能在一定程度上满足,虽然它本身具备一定的复杂性,但目前没有更好的路可以走了。

当然如果愿意快速解决问题,知名制品库厂商JFrog提供了Conan Center以及相关解决方案,我就不多打广告了。如果愿意折腾,Conan本身开源,且可以通过SonaType的开源制品库Nexsus实现,这也变相提供了另一种“低成本”的方案。

3. 极狐GitLab嵌入式开发场景解决方案

围绕企业对于嵌入式开发场景的诉求,极狐GitLab提供了一整套的解决方案,可以较好的解决嵌入式开发场景下的种种问题,重点是以下几部分内容。

3.1 高可用部署与灾备

企业进行代码统一管理的前提是代码管理系统需要具备高可用、高性能以及容灾等特性,才能支撑企业安全可靠的管理代码数据资产。

极狐GitLab专业版提供高可用部署方案,是松耦合分布式架构,各组件均为多副本部署,各组件均可实现横向扩展。极狐GitLab高可用架构通过横向扩展同时实现了高性能,针对从1k到50k的用户数场景提供了不同的参考架构,可以满足不同用户规模的企业。

极狐GitLab同时提供GEO主从架构部署方案,该方案为一主多从架构,主从节点配置不必完全一致,主节点提供读写服务,从节点提供只读服务,数据在主从节点之间实时同步,可实现:

  • 通过访问就近节点加速
  • 负载分担
  • 准实时备份
  • 灾难恢复

当主节点出现故障后可在几分钟内将从节点切换为主节点,恢复服务。GEO可在实现准高可用的同时极大降低基础设施资源成本、部署和运维成本。

3.2 组织管理

极狐GitLab通过群组、子群组嵌套关系实现对企业复杂组织关系的映射,嵌套最多支持20层,可以有效管理企业中的部门、组织、虚拟组织、项目。代码库隶属于群组或子群组,可在任意群组、子群组对仓库和人员权限进行管理。

对于需要总览整个群组的管理人员,可以将其在父群组上分配权限,该用户的权限将被继承到所有子群组和代码库中。对于需要查看部分内容的项目经理,可以将其在某个子群组上分配权限,该用户只可看到子群组下所有子子群组和代码库的数据。而对于只专注于具体功能模块的开发人员,只需要将其在具体的代码库上分配权限即可,这样就实现了在分仓模式下的授权管理。

3.3 分支策略

前文提到,分支管理是Git的优势,分支策略也是体现Git高效协同的重要价值,此外分支策略也直接决定了研发流程的标准化。从事嵌入式开发的企业进行DevOps转型,首先要考虑的就是分支策略怎么建立。以下是常见的几种分支模型,可供参考。

但从极狐GitLab自己实践的角度出发,我更倾向两种分支策略:

  • 上图是极狐GitLab自己的分支策略,本身是属于版本分支GitLab Flow,适合多人协同的大、中型项目。相较于Git Flow裁剪了一些不必要的分支,降低复杂度。
  • 下图是对于小型项目推荐的极简分支策略,本身是属于GitHub Flow,适合单人或者较少人员协同的小型项目。

需要注意的是,没有所谓的最佳分支策略,因为不同企业的研发流程是不一样的,即便是同一家企业的不同团队或者同一个团队在不同时期的研发流程也是不一样的,这时候就需要基于这些常见的分支策略总结和提炼一套属于自己的分支模型,并且对它持续进行检验和迭代。比如对于小型嵌入式项目,如果存在对不同的芯片或用户有定制版本,那么更建议基于GitHub Flow,加入Release分支来管理不同的交付版本。

3.4 分支保护

当分支策略制定完成,如何保证开发人员遵循这套流程,那一定是需要工具层面有约束手段。极狐GitLab提供分支保护功能,可以限制开发人员直接向主分支提交代码,必须通过向feature分支或dev分支提交代码,再通过Merge Requests的方式合并到主分支,可以降低代码冲突,提高协同效率,同时也为开发团队践行代码评审提供了工具侧的落地支撑。

此外极狐GitLab专业版实现了更加精细化的分支保护管理,可以对于指定的用户设置推送和合并权限,以此保证分支代码的可审核和可追溯性,而免费版只能较粗粒度的指定某一类用户角色,如Developer、Maintainer。

3.5 推送规则

代码规范也是DevOps转型非常关注的内容,以往粗放式的代码提交方式容易导致代码提交记录极度混乱、不可识别,比如大量1111test1之类的提交记录不仅让协同人员无法接手、难以理解,甚至提交人自己也无法基于这些记录进行拉取或者回滚操作。

极狐GitLab专业版提供的推送规则功能可以很好的解决这个问题,它可以实现:

  • 验证提交人是否是GitLab用户。

  • 自定义正则表达式,验证代码提交记录是否符合一定的规范。

  • 自定义正则表达式,验证分支名称是否符合一定的规范。

  • 自定义正则表达式,验证提交文件中是否有不符合规范的文件,比如.zip、.tar文件。

  • 验证提交的文件是否超过一定的大小。

推送规则验证不通过,则代码无法被推送到GitLab代码库中,从而确保研发人员严格按照规范进行代码提交。

规范代码提交信息可以配合Commitizen这款工具,它可以按照一些行业内通用的提交规范引导开发人员填写提交信息。也可以配合.gitignore文件过滤一些不需要上传的文件或文件类型。它们都是在客户端发挥检查作用,本身不具备约束性质,开发人员可用可不用,而推送规则是在服务端进行验证,确保最终合规。所以可以说Commitizen、.gitignore是源头检查,推送规则是尽头把关。两者可配合,但前者不可替代后者。

3.6 代码评审

由于嵌入式开发的周期相对较长,交付频率相对较慢,交付物多是固件,不具备互联网纯软件、高速迭代、灵活升级的特性,所以处理问题的成本也比较高,这也使得近些年从事嵌入式开发的企业对软件质量的要求逐渐提高,而提高软件质量最常用的方式就是进行代码评审。

极狐GitLab专业版提供了完善的代码评审机制,其中主要包括:

  • 支持强制代码评审,评审不通过不允许合并代码。
  • 支持自定义代码评审规则,针对不同分支设置不同评审人,以及最小核准人数。
  • 支持多重审批规则。
  • 支持当单元测试覆盖率降低时触发代码评审。
  • 支持阻止代码提交人、合并请求发起人进行评审。
  • 支持设置Code Ower(代码负责人)为审批人。

其中Code Owner可以为代码库的不同目录、不同文件、不同文件后缀设置代码评审人,比如:

1
2
3
fileA		@张三
*.go @李四
folderA/ @张三 @李四

这就实现了对Git代码库的目录、文件进行写入控制,也解答了上文中遗留的问题。

3.7 数据保护

上文也提到了数据保护,代码防泄漏的相关问题,具体落实到系统层面,极狐GitLab可以从事前、事中、事后三方个面提供支撑。其中最重要的是对GitLab的操作行为进行记录和分析,实现审计功能。

极狐GitLab专业版支持对创建仓库、修改密码、权限变更等系统事件进行审计,也支持对代码推拉事件进行审计,并可以事件流的方式传递给第三方日志系统,以便对数据进行分析和展示,也可以制定一些规则并触发告警或通知。

此外,在与第三方DLP工具对接的过程中,我们发现有些客户的DLP工具只能对Git客户端或IDE进行加密,而GitLab本身还可以通过在网页上直接打包下载的方式获取源码,这给客户的数据保护工作造成一些麻烦。所以极狐GitLab为这个本土化需求增加了一项功能,可以在网页上禁用源代码下载,以便更好的解决数据保护问题。

3.8 其他相关

除了以上在源代码管理方面的一些优势外,极狐GitLab还提供了一些其他的附加功能。

比如在工具方面,极狐GitLab提供了IDE插件,可以更方便的对GitLab中的项目、代码、流水线进行管理。提供了WebIDE,可以在线对代码进行查看、对比、编辑操作。

在项目管理方面,极狐GitLab自身提供轻量的偏敏捷的项目管理功能,且可以和主流的项目管理工具如Jira、禅道、PingCode以及和传统制造行业常用的PLM系统Windchill进行集成和打通,实现在需求管理系统中可以查看该需求任务关联了哪些GitLab的代码提交和合并请求,实现项目到开发的流程关联与追溯。

流水线是DevOps中的重要组成部分,也是研发效率提升的根本,极狐GitLab提供了开箱即用的CI/CD功能,相较于Jenkins,GitLab CI依靠其一体化、轻量化、声明式、开箱即用的特性,在开发者群体中的使用率越来越高,在国内企业中仅次于Jenkins排在第二位。也为从事嵌入式开发的企业做完源代码统一管理后,进行更进一步的DevOps转型提供支撑和帮助。

最后,随着物联网、人工智能等技术的不断发展,嵌入式系统的应用场景将会更加广泛,这将会催生更多的DevOps应用场景,也会进一步推动DevOps在嵌入式开发场景中的落地,期待那一天早点来到。


参考资料

嵌入式开发场景下的代码管理方案

https://wurang.net/embedded-scm/

作者

Wu Rang

发布于

2023-02-21

更新于

2023-05-05

许可协议

评论