乐趣区

新一代高效Git协同模型AGitFlow详解

【以下为分享实录,有删节】

Git 工作流概述及 AGit-Flow 的优势

目前,Git 已成为源代码管理的标准和基础设施。“为什么 Git 能这么成功”?Git 的创建者 Linux 在 Git 十周年的一次采访中,道出了其中的奥秘:

The big thing about distributed source control is that it makes one of the main issues with SCM’s go away – the politics around“who can make changes.”

他认为 Git 能成功最关键的不是因为它更快、更安全,也不是因为 Git 是分布式的,而是解决了“到底谁能够贡献代码”这个问题。传统的集中式版本控制系统只能针对核心用户开放写授权,长期来看这对项目做大、做强是不利的。而 Git 改变了传统版本控制系统不能够让跟多开发者贡献代码这个顽疾,让“只读用户”也可以通过“代码评审”的方式参与到项目开发中。

当前业界有两种最常用 Git 工作流:GitHub 和 Gerrit。他们都具备仓库的授权模型简单,只读用户可参与代码贡献的特点。

如上图所示,我对这两种 GIT 工作流做了优劣势分析:** 代码评审模式不同:** GitHub 的代码评审称为“pull request”,每个特性改动生成一次代码评审。Gerrit 的代码评审称为“Change”,每个提交生都会生成一个“变更单”,这个变更单就是一次代码评审。

工作流类型不同:GitHub 的工作流属于分布式,当开发者需要参与项目的时候,虽然没有“写”的权限,但是可以通过“Fork”的方式创建一个个人仓库(派生仓库),他就可以在这个派生仓库中去创建代码分支,创建 pull request。GitHub 底层采用的是原生的 Git(即 CGit)。

Gerrit 的工作流是集中式,所有用户工作在统一管控的集中式仓库中。Gerrit 要求用户在本地克隆仓库中安装一个“commit-msg”钩子,以便在生成的提交中插入唯一的“Change-Id”,向服务器推送要使用特殊的 git push 命令。Gerrit 采用的是 JGit(Java 的 Git 实现)。

** 各自优势:** GitHub 简单易用,使用标准 Git 命令即可完成代码贡献;对派生仓拥有完全控制力,不受上游项目影响;可以创建跨项目的开源社区,全球开发者大协同,这也是 GIT 可以形成全球最大的开源社区的原因之一。

Gerrit 因为采用集中式的工作流,管理员可以对项目进行严格管控,可以严格控制谁可以访问仓库,谁可以对我的仓库做贡献。Gerrit 另外一个优势是可以实现多仓库项目管理。我们知道“Android”项目具有 1000 多个仓库,就是使用 Gerrit 进行管理的,很难想象如何使用 GitHub 来管理 Android 的 1000 多个仓库。

各自劣势:正如前面所说 GitHub 很难管理类似 Android 的多仓库项目。另外因为 GitHub 使用派生仓库的工作模式,会产生服务端数据冗余的问题。

Gerrit 需要集中管控,由管理员负责创建项目,而普通用户不能创建项目,这就使得一个 Gerrit 实例通常只管理一个项目或一个组织内的项目,难以在项目之间形成代码复用,也很难汇集跨项目的开发者组成开发者社区。

通过对 GitHub 和 Gerrit 等 Git 工作流的调研和学习,我们“取长补短”创建了阿里巴巴的 Git 工作流,即 AGit-Flow。

在阿里巴巴,我们喜欢 pull request、CGit,喜欢在命令行直接创建代码评审的集中式工作流,喜欢开放的开发者社区。我们不喜欢“commit-msg”钩子方式关联提交的代码评审,我们不喜欢一个一个分散的代码平台。

我们还开发了配套的客户端工具“git-repo”,既能在单仓库下工作,又支持类似 Android 的多仓库项目协同。

在阿里巴巴,我们如何使用 AGit-Flow

下面给大家演示一下,在阿里巴巴内部,我们是如何使用 AGit-Flow 工作的。

我们首先使用 Git 标准命令将仓库克隆到本地,然后在本地仓库内开发,创建提交。在工作区中执行 git pr 命令,推送本地提交到服务器,服务器端会自动创建一个新的代码评审,例如:pull request #123。团队的代码评审者就可以打开编号“123”的代码评审提交评审意见。开发者根据评审意见,在本地工作区继续开发、新增或修改提交。工作区中再次执行 git pr 命令,推送本地提交到服务器。服务器发现目标分支上已经存在来自同一用户、同一本地分支的 pull request,因此用户此次推送没有创建新的 pull request,而是更新已经存在的 pull request。

如果经过多次修改,代码依然不 OK。代码评审者也可以直接发起对评审代码的修改,帮助原开发者更新 pull request。代码评审者可以使用 git download 123 下载编号为 123 的 pull request 到本地仓库,代码修改完毕后,执行 git pr –change 123 命令,将本地修改推送到服务端。服务端接收到代码评审者的特殊 git push 命令,更新之前由开发者创建的 pull request。项目管理者通过点击 pull request 评审界面的合并按钮,将 pull request 合入 master 分支。master 分支被更新,同时关闭 pull request。

使用 AGit-Flow 工作流无需在服务器上创建新的分支,不需要给新加入的同学创建写入权限,可以对所有开发者只分配读取权限,然后通过创建代码评审再合并到主干的方式更新主干代码。这也是目前比较流行的主干研发模式。

目前大家可以通过云效代码管理平台(Codeup)来实现 AGit-Flow 工作流。

AGit-Flow 实现原理

AGit-Flow 是如何实现的呢?首先客户端使用特殊的 git push 命令向服务端发起代码推送请求,触发 AGit-Flow 工作流。为什么说这个 git push 命令特殊呢?因为它的目标分支是一个包含特殊的前缀“refs/for/” 的代码分支,分支后面又跟了一个“”。这个“”用于区分本地分支名,不同开发者提交的代码评审包含不同的“”,所以不会相互覆盖。

我们还可以通过“-o”来传递不同的参数,比如可以指定由谁来对我的代码进行评审,我的代码评审会关联哪个“issue”。这些操作都可以通过“git push”命令完成,后来我们发现这个 git push 命令比较复杂,于是我们封装了一个命令行工具 git-repo。目前 git-repo 已经对外开源,大家可以免费使用。

接下来这个“Push”命令就会打入到服务端,服务器端会启动一个进程“git-receive-pack”。(我们对服务器端的前端授权模块做了一些修改,使其能够识别这个特殊的 git push 命令,允许只读用户也能“Push”)如上图所示,“git-receive-pack”我做了星号标记,因为它是一个特殊的“git-receive-pack”。当它发现 push 命令的目标是一个特殊的引用后,它不会走 Git 原来内部的工作流,而是走“外部钩子”。通过“外部钩子”完成一些好玩的操作,比如创建代码评审。

在今年(2020 年)3 月份,我们已经把这个修改过的 git-core 贡献给了 Git 社区,目前正在评审中,后续 Git 新版本会包含这个新特性:proc-receive。此特性已经历经 15 次迭代,从最初的服务端扩展到集合了服务端扩展和客户端协议升级的完整解决方案。我们将这个技术开源,一方面繁荣了 Git 生态,让更多人能从阿里巴巴的技术中获益;另外一方面,阿里巴巴也得到了收益,我们的代码贡献得到了 Git 客户端的支持,Git 适配了我们新的玩法,让包含阿里巴巴在内的 Git 用户得到了更好的体验。

AGit-Flow 实现的技术细节

为了解释 AGit-Flow 实现的技术细节,我们先来了解一下 git push 命令原有的实现方式。![05.jpg](https://ucc.alicdn.com/pic/ developer-ecology/8552743fa0b24934bd9a93146ffedef4.jpg)

如上图左侧所示,git push 命令包含两部分信息,一个是被打成包的数据,叫“packfile”;另一个是推送的命令,叫“commands”。“packfile”和“commands”被推送到服务端后,“packfile”会走左侧的路径,首先进入“quarantine”中,进行“隔离”。当“commands”经过“pre-receive”钩子检查,认为用户的权限 OK、提交说明 OK、提交修改的文件 ok,“packfile”才会从隔离区释放出来,进入对象库(objects)。(如果“pre-receive”钩子脚本失败,则删除隔离区,并返回错误信息,终止推送命令的执行。)

接下来“commands”会传递给内置的“execute_commands()”函数,实现分支的创建、更新、删除等操作。然后通过“repor()”函数报告给客户端,最后执行“post-receive”钩子脚本,完成事件通知。

新钩子,新生态。AGit-Flow 对“git-receive-pack”的源码做了改动,新的流程如下图所示:

当客户端执行新的 git push 命令后,“packfile”的传播路径没有改变,但是我们更改了 ”git-receive-pack” 命令,增加了一个“过滤器”(图中漏斗部分)。过滤器将“commands”分成两组,一组是标准的 Git 命令(group1),一组是 AGit-Flow 特殊的命令(group2)。这两组命令经过“pre-receive”钩子检查后,左侧普通的命令(group1)会执行 Git 内置的 execute_commands 函数,生成新的引用,进行分支的创建、更新等。右侧这个特殊的命令会调用一个新的外部钩子“proc-receive”,然后创建一个特殊的代码评审引用,如“refs/pull/123/head”,并且可以用过特殊的 Git 命令将它下载到本地。

我们为 Git 贡献的这个新特性,由三部分组成。第一个部分就是“过滤器”,通过在服务端新增新配置变量 “receive.procReceiveRefs” 来实现。只要定义了这个特殊的配置变量,当客户端使用 git push 命令推送的时候,Git 就会根据配置变量去匹配,当匹配到相应的命令时,这个命令就会走特殊流程。这个配置变量属于多值变量,例如阿里巴巴代码平台的设置是:

  • git config –add receive.procReceiveRefs refs/for
  • git config –add receive.procReceiveRefs refs/drafts
  • git config –add receive.procReceiveRefs refs/for-review

这三条配置变量对应 git pr 的三种推送模式,会产生标准的 pull request、草稿模式的 pull request,或者一个代码评审者想推送指定的 pull request。

第二部分是 proc-receive 钩子。我们这个钩子应该说是 Git 中有史以来最复杂的一个钩子,它可以和服务端(git-receive-pack)进行双向通讯。首先服务端和钩子会做“版本协商”,因为我们认为这个协议后续会升级,为了保证向后兼容,所以我们首先要协商一个版本。服务端告诉钩子我是哪个版本,客户端告诉钩子我支持哪个版本,后面 Git 就可以用相应的版本协议与钩子进行通讯。服务端会用“pkt-line”编码命令,这个命令是三段式的,包含老的 ID,新的 ID 和需要更新的引用名称。服务端会把命令和参数发送给钩子。钩子会对这些命令和参数进行处理,这些处理由开发者以 API 调用的方式实现,然后将处理结果报告给钩子。钩子再把这个执行结果以特殊的方式报告给服务端。

第三部分是可定制的客户端状态报告。在上图右侧部分,大家可以看到有一条虚线由“proc-receive”钩子指向“execute-commands”函数,这个代表有些钩子无法处理的命令会返回给“execute-commands”函数进行处理。当“execute-commands”和“execute-commands”钩子处理完全部命令后,会统一进行“report()”(报告)。我们这个“report()”相比于 Gerrit 有一个优势,能够让客户端知道你没有改成“refs/for”引用,而是改成了另外一个引用。此外,我们也考虑了向下兼容问题,让老版本的 Git,在遇到新版本的 Git 服务器的时候一样可以收到报告信息。例如老版本的 Git 会认为你创建了“refs/for/master”;但是新版本的 Git 懂得扩展协议,可以识别出你创建了特殊的引用,比如知道你创建的不是“refs/for/master”而是“refs/pull/123”这样的引用。

阿里巴巴开源的客户端工具 git-repo 简介

git-repo 是阿里巴巴开源的一款集中式工作流命令行工具,对原生 Git 命令做了封装,简化了使用 AGit-Flow 等集中式工作流时稍嫌繁琐的 Git 命令。git-repo 是使用 Go 语言开发,运行时除 Git 外不依赖其他软件,所有具有拆包即用的优点。

git-repo 具有良好的兼容性,可以支持 AGit-Flow 兼容的代码平台以及 Gerrit。可以跨平台,目前支持 Linux、Mac、windows 系统,其中 Windows 是 Beta 版本。除了具备 Android repo 的多仓库管理能力外,还可以对单独的代码仓库进行操作。

如何下载安装 git-repo 呢?git-repo 目前已经开源到 Git hub 中,你可以访问 https://github.com/alibaba/git-repo-go/releases 页面下载合适的安装包。然后将解压缩后的 git-repo 文件移动到可执行目录中(如 Linux 下的 /usr/local/bin 目录),即完成安装。详细的使用方法此处不再赘述,大家可以访问 git-repo 主页了解。

总结:

AGit-Flow 是阿里巴巴研发的 Git 集中式协同协议,目前已经在阿里巴巴集团内部生根开花,在外部,通过云效代码管理平台提供支持。

AGit-Flow 的核心组件已经开源,将已经成为 Git 的核心组件。

为了方便开发者操作,我们开发了 git-repo 命令行工具。git-repo 使用 Go 语言开发,具有拆包即用、跨平台、兼容性好、可扩展等特点。git-repo 已经开源道 Git hub 社区大家可以进入产品主页免费下载使用。

以上内容整理自知忧的视频分享《AGit-Flow:新一代高效 Git 协同模型》,欢迎大家加入云效开发者交流群(钉钉群号:34532418)观看视频回放,下载演讲 PPT。


【关于云效】

云效,企业级一站式 DevOps 平台,源于阿里巴巴先进的研发理念和工程实践,致力于成为数字企业的研发效能引擎!云效提供从“需求 -> 开发 -> 测试 -> 发布 -> 运维 -> 运营”端到端的在线协同服务和研发工具,通过人工智能、云原生技术的应用助力开发者提升研发效能,持续交付有效价值。

【云效官网】https://www.aliyun.com/product/yunxiao?channel=zhibo【公测指南】https://developer.aliyun.com/article/756207【申请公测】https://devops.aliyun.com【学习路径】https://help.aliyun.com/document_detail/153739.html【开发者社区】https://developer.aliyun.com/group/yunxiao【精彩活动】云效公测开启「产品体验官」招募 https://www.aliyun.com/activity/yunxiao/Beta2020

欢迎扫码加入云效开发者俱乐部(钉群:34532418)

退出移动版