Git code hosting platform to explore a number of issues --2019 version

About Git

Version control software variety, the earliest version control system Wikipedia included 1972 Bell Labs developed the Source Code Control System. 1986 Concurrent Versions System (CVS) was born, CVS has been very popular, but this time with the few, but still in use OpenBSD CVS. Created in 2000 CollabNet Subversion project, in 2009, it was accepted Subversion Apache Foundation as the top project and named Apache Subversion. In 2005 Linus Torvalds created the Git, born after 2007 Github, Git Github with the development of increasingly popular, 14 years, Git has become the most popular version control system, either Windows or Linux or Android, MySQL and so on large-scale software use the git version control. Throughout the history of the popular version control system, after the former CVS has SVN, Git today more romantic. Both to the men, a few heroes, also see today, version control systems all working the case.

And CVS / Subversion such a centralized version control system is different, Git repository data will be locally submission also occurred in local, remote, it can be seen as a local repository of mirrored storage. The CVS / Subversion are submitted online. This is the central feature of a distributed version control system. (Understanding of the problem lies in distinguishing the work associated tree  worktree and repository  repository.)

Git source code hosted on git.kernel.org, there are read-only image on Github github.com/git/git. Home https://git-scm.com Git source code page is hosted on Github. Usually submitted to git PR need to register public-inbox.org mailing list, then send patches. Usually too much trouble, but fortunately there are Microsoft developer Johannes Schindelin use TypeScript development gitgitgadget, when you submit PR like gitgitgadget / git on Github, gitgitgadget will send your PR to public-inbox, once the patch is accepted git defenders , gitgitgadget will close the PR. gitgitgadget simplify difficult to git contribute code, eliminating the hassle of registration Inbox, most developers these days have Github account. I use git gitgitgadget to submit a patch to support HTTP / 2.

Johannes Schindelin who is also git-for-windows defenders. Git maintainer is Google's developer Junio ​​C Hamano. Most Git developers from Google / Microsoft (including Github). libgit2 developers mainly from Microsoft (including Github). The JGit developers mainly from Google. JGit of the late founder Shawn Pearce has also developed the famous Gerrit Code Review. The developer's dedication to make us spend such a good version control system, thanks to their efforts.

Transmission protocol between the remote Git repository and there is HTTP, GIT ( git://), SSH in. "Pro Git - 2nd Edition" 4.1 Git on the Server - The Protocols have introduced. HTTP protocol which includes the smart and dumb protocol agreement, because the agreement is a read-only dumb agreement, currently most of the code hosting platforms are no longer supported. HTTP protocol and intelligent GIT protocol, similar to the SSH protocol, certain groups are client / server git input and output data transfer and exchange between the command. Git simple transport protocol, v1 intelligent transmission protocol, for example, the basic  fetch/push process is as follows:

Git pulling process:

Fetch Flow

Git push process:

Push Flow

Although in May 2018, git launched  Wire Protocol(ie Git v2 protocol), increased the complexity of Git protocol, but supports git protocol (including v2 protocol) on the server still only need to run git-upload-pack on the server / git-receive-pack. This makes it easy for developers to implement support for git protocol. Because Git protocol characterization is simple, so for different number of users and the size of the repository, Git also better than Subversion, Mercurial have more choices.

Git snapshot record file using the file change, when an object stored in the file directory of loose, each size of the same file modification increases the size of the repository corresponding to a specific file, Git uses zlib deflate compressed object, the object including an object header type, the original size. Git-based snapshots such a way that when submitting the code, check out files are more efficient, but the lack of store occupancy is relatively high. But running  git gc time, Git will loose objects packed into a pack file, this time will use a specific mechanism for storing portion of a file  OFS_DELTA, so you can save some space.

zlib(deflate) 压缩算法通常来说除了没有版权限制,无论是压缩比还是速度,CPU 使用率都不是一个最佳的选择,引用来自的 https://github.com/facebook/zstd 基准测试,zlib 看起来必后起之秀 brotli/zstd 差多了:

Compressor name Ratio Compression Decompress.
zstd 1.4.0 -1 2.884 530 MB/s 1360 MB/s
zlib 1.2.11 -1 2.743 110 MB/s 440 MB/s
brotli 1.0.7 -0 2.701 430 MB/s 470 MB/s
quicklz 1.5.0 -1 2.238 600 MB/s 800 MB/s
lzo1x 2.09 -1 2.106 680 MB/s 950 MB/s
lz4 1.8.3 2.101 800 MB/s 4220 MB/s
snappy 1.1.4 2.073 580 MB/s 2020 MB/s
lzf 3.6 -1 2.077 440 MB/s 930 MB/s

当开发者要将 git 集成到其他软件或者系统中时,可以通过命令行调用 git 命令捕获输出,也可以使用 libgit2/JGit 等库。

libgit2 最初是由 Shawn Pearce 创建了初始 commit。目前主要维护者来自微软。libgit2 提供一些基础的 API,功能基本上是完整的,除了一部分实现性能没有 git 那么好,其他方面令人满意,并且有多种语言绑定,包括 C++/D/Golang/Ruby/.NET/Node.js/Perl/Perl6/Ruby/Rust 等等。Gitee 原生钩子就使用了 libgit2,Gitee-gitlab 项目使用了 rugged。

JGit 也是有 Shawn Pearce 创建的,目前属于 Eclipse 基金会,运行在 JVM 上,国内腾讯的工峰的 TGit 也是使用的 JGit。

在 Git Rev News 第48期,编辑推荐了 gitbase 通过 SQL 的方式查询 git 存储库,这个工具基于 src-d/go-git,go-git 是纯 Golang 实现的,如果基于 Golang 的项目需要简单的读写存储库,可以使用 go-git。与 libgit2 的 Golang 绑定 git2go 相比,不需要使用 CGO。

当然还有一些其他的 git 实现,大多是实验性的,不建议用于生产环境,比如基于 Rust 的 git-rs。

不同伸缩性的 Git 代码托管平台

基于内置工具搭建 Git 代码托管服务

Git 最初由 Linus Torvalds 开发用来取代 BitKeeper 作为 Linux 内核源码的版本控制工具,所以 Git 一直和 Linux 内核源码托管在同一个服务器上。官方地址是:https://git.kernel.org/。在 git.kernel.org 上,Git 代码托管功能是由 git 内置的工具实现的。用户使用 HTTPS 协议访问 https://git.kernel.org/ 时,Nginx 会以 CGI 的方式将浏览器的请求转发到 GitWeb。GitWeb 是一个使用 Perl 编写的 CGI 程序,为用户提供简单的 git 在线交互图形界面。GitWeb 的源码地址可以在 Github Git 镜像 中查看。GitWeb 界面比较不够精美,相比于 Github 这样的代码托管平台,功能寥寥无几。当用户需要使用 HTTP/HTTPS 协议拉取推送源码时,Nginx 会以 CGI的方式将请求转发给 git-http-backend 处理。git-http-backend 是 Git Over HTTP 的服务端实现。当用户 GIT 协议 (git://) 在 git.kernel.org 上拉取源码是,请求会被 git-daemon 处理。git-daemon 默默的监听 9418 端口,静静的等待 git 客户端的访问。

使用 Git 内置的 GitWeb/git-http-backend/git-daemon,我们能够搭建一个简易的 Git 代码托管服务器,但这里没有 SSH 协议支持。而实现 SSH 协议支持也非常简单,只需要在服务器上运行 sshd (OpenSSH),并允许命令 git-upload-pack/git-receive-pack/git-upload-archive 命令的运行,对于 SSH 协议的验证,我们则可以使用 authorized_keys 机制,将需要允许的用户的 SSH 公钥添加到 authorized_keys 文件。

这种方案通常使用 Gitolite 增强访问控制,Gitolite 主要使用 Perl 编写,这和 GitWeb 一致,ssh 的验证是将 gitolite-shell 添加到 ~/.ssh/authorized_keys 中被 sshd 调用实现的。git.kernenl.org 正是使用 Gitolite 实现 Git Over SSH 访问控制。

https://git.kernel.org/ 网站托管了 Linux 内核源码,驱动,文档等大概有 1000 多个存储库,较大的存储库比如 Linux 内核源码磁盘占用大概是 2GB,因此在理想情况下,一块 2TB 磁盘的服务器便可支撑 https://git.kernel.org/ 这个网站的运行(实际情况则并不是如此,由于 Linux 内核的流行,git.kernel.org 的请求将比较多,对硬件的需求将更高一点)。基于 Git 内置功能搭建的代码托管服务,麻雀虽小五脏俱全,不过回过头来说,这样的代码托管服务功能有限,可伸缩性和扩展性不佳。

小型的 Git 代码托管平台

当用户需要搭建一个几人到几十几百人规模的 Git 代码托管服务,通常有非常多的选择,下面是几个目前仍然比较活跃的小型 Git 代码托管平台。

名称 平台 语言 技术概述
Bonobo Git Server Windows Only C#www.yaoshiyulegw.com 基于 .Net Famework 4.6(迁移到 .Net Core 的建议在 2017 年便被提出,但截至目前仍为迁移到 .Net Core)。使用 LibGit2Sharp 操作存储库,但版本较老,不支持 SSH 协议访问。
Gogs Cross Platform Golang 基于 Golang 编写,Web 读写 Git 存储库由 git-module 封装 Git 命令实现,SSH 由 Golang crypto/ssh提供,支持多种数据库,是一个极简的代码托管平台,可以在 Raspberry Pi 上运行
Gitea Cross Platform Golang 是 Gogs 的开源分叉,Web 读写 Git 存储库使用了 src-d/go-git,使用 gliderlabs/ssh 提供 SSH 接入功能,支持多种数据库,可以在 Raspberry Pi 上运行。
GitBucket Cross Platform Scala/Java 使用 Apache Mina SSHD 实现 SSH 功能。Mina SSHD 还专门针对 JGit 实现了一个 sshd-git 模块,但 GitBucket 是直接使用 JGit 的 transport 相关类。Eclipse JGit 主要由 Google 开发者参与贡献。

除了上述定位为代码托管平台的服务,还有像 Phabricator 这样的 Web 软件也提供 Git 代码托管功能,但 Phabricator 的重点更多是缺陷追踪,代码审核。LLVM https://reviews.llvm.org/ 和 libssh https://bugs.libssh.org/ 就是基于 Phabricator。

云服务级别的 Git 代码托管平台

随着用户规模和存储库规模的增长,达到一定级别后,上述代码托管平台往往变得力不从心,而下面的代码托管平台却深耕于此,能够支撑巨大规模的用户量和存储库数量。

Github 是全球最大的代码托管平台,目前 Github 官方数据显示注册用户数量为 4000万,项目数量为 1亿。Github 网站主要的技术是 Ruby on Rails 内部进程名为 github-unicorn,最近他们将其升级到了 Rails 6.0。Github 使用 Spokes 负责文件系统上存储库的复制,同步和备份。Github 之前使用 libssh 开发 Git SSH 服务器,目前的 SSH 服务器的标识为 babeld-*,但不确定 babeld 是否依然基于 libssh。Git 验证服务为 github-gitauth。Github 的大多数服务都是闭源的,因此分析 Github 的技术内幕通常是 Github 官方的一些技术博客, 当然也可以分析 Github Enterprise 去窥测 Github 内幕。

关于 Github Spokes 的大致原理可以阅读 Introducing DGit 和 Building resilience in Spokes。

在开发 Gitaly 之后, Gitlab 摆脱了 NFS 的禁锢,在平台的伸缩性方面得到了巨大的提升。要知道 Gitlab 使用 Gitaly 的原因可以阅读 The road to Gitaly v1.0。Gitaly 使用 RPC 将存储服务器上的 git 命令包转成前端服务机器上的 git 命令,并为 gitlab 服务提供存储库的读写。 Gitlab 的 SSH 功能仍然由 OpenSSH 提供,而一些静态资源,文件下载,附件等功能则由 Golang 编写的 gitlab-workhorse 实现,gitlab-workhorse 需要与 Gitaly 通信。

Bitbucket 是 Atlassian 开发的代码托管平台,与 Github/Gitlab 不同,Bitbucket 还提供了原生 Mercurial 支持,不过最近,Bitbucket 宣布要逐步关闭 Mercurial 的支持。Atlassian 还开发了 Jira/Sourcetree 这样著名的软件,Bitbucket 源码没有开发,推测主要使用 Java 技术栈(这个从一次 Bitbucket VFSForGit 安装包分析可得)。

Gitee 是目前国内最大的代码托管平台之一,早在 2015 年便开始了分布式改造,并编写了一系列服务实现分布式架构,编写了 Nginx 路由模块实现动态路由,基于 libssh 开发了 Basalt v1 SSH 服务器,基于 Golang 开发了 Basalt v2 SSH 服务器,还开发了 git-srv 智能服务后端,brzox Git HTTP/Archive 服务。以及 git-diamond git 协议内部传输服务等等。Gitee 最初代码基于 Gitlab,几年之间已经与 Gitlab 有了很大的差异,现在 Gitee 已经逐步将一些功能从 gitlab 中剥离,实现云平台的微服务,比如目前的 git/svn/hook 验证服务是基于 Golang 编写的 banjo。Gitee 需要以有限的硬件实现更多的用户接入,所以在服务的设计上更倾向于提供资源使用率,对一些比较容易造成计算资源紧张的服务进行降级。

Git 代码托管平台服务实现

<!--SSH/HTTP/GIT, LFS, GitVFS....-->

Git 代码托管平台的基本服务应该包括浏览器接入支持和 git 客户端接入支持,前者需要平台开发网页提供若干服务供用户访问。后者需要支持 git 客户端推拉代码。通过网站访问存储库意味着 HTTP 服务需要通过一定的途径读写存储库,在 GitWeb 中,这通常使用 git 命令实现,比如使用 git tree 查看 tree,使用 git archive 打包文件等等。在 Gogs 中,使用的 git-module 同样使用了命令读写存储库。而 Gogs 的分叉 Gitea 则使用的是 src-d/go-git 读写存储库。实际上我们常常有那种感觉,使用命令行可能会比直接调用 API 慢,并且错误难以处理,这通常是对的。比如我们查看 HEAD 对应的引用,使用命令我们可以运行 git symbolic-ref HEAD,运行这个命令我们需要 fork 出一个进程,fork 成功后马上在子进程中执行 exec git symbolic-ref,为了读取 git symbolic-ref 的输出,我们还需要创建几对 Pipe,并检测 git symbolic-ref 的退出值。而使用 libgit2 API 我们只需要调用 git_repository_open,git_reference_open,git_reference_symbolic_target 即可拿到对应的引用。而对于服务程序而言,fork-exec 的代价可能不小。当然你也可以直接使用 open("/path/to/.git/HEAD") 然后解析 HEAD 对应的引用。GitBucket 使用 JGit 读写存储库,Gitlab 曾经历了 Grit (Grit 部分命令部分 Git 纯 Ruby 实现,Github 曾经使用)。后来的 Rugged,到现在 Gitaly 的纯命令 + Ruby Repository(Gitlab 现在的架构我对其保留意见,至少 IO 复制将增加多次)。Github 目前使用 Rugged 读写存储库,当然一些更多的细节因为没有源码不得而知。Gitee 目前使用 Rugged,但一部分 libgit2 实现不佳的则直接采用 git 命令实现。

实现 Git Over HTTP,Gitlab 最初采用了 Grack, 运行在 unicorn 中的 Grack 并发有限且容易影响 Web 访问(即 Git 请求较多时,Web 拒绝服务),而基于 Golang 开发的 Gogs,Gitea 使用 Golang 原生 HTTP 库编写 Git HTTP Server 功能,这要比 Grack 好要好很多,Golang HTTP 模型能够支撑更多的并发。目前 Gitee 的 Git HTTP Server Brzox 也是使用 Golang 编写。

实现 Git Over SSH,Gitlab 目前依然使用的是 OpenSSH,而不像 Github/BitBucket/Gitee 直接编写 SSH 服务器,直接编写 SSH 服务器可以禁用 SSH 登录,自定义错误消息,简化验证流程,减少数据拷贝。Github 早先是基于 libssh 编写的 SSH Server, 目前不得而知。BitBucket 技术上偏向 Java, 则有可能使用 Apache Mina SSHD, GitBucket 使用 Apache Mina SSHD + JGit 实现 Git Over SSH 功能。而 Gogs/Gitea 在虽然使用 Golang crypto/ssh 编写了 SSH 服务,但在实现时仍然使用了中间命令,这就导致数据拷贝次数的增加,观测 Gogs/Gitea 的各种服务实现,这可能是设计不足的妥协吧。

实现 Git Over TCP (git:/www.jiuyueguojizc.cn / 协议)也非常简单,但 Git 协议并不提供验证机制,Git 代码托管平台提不提供 Git 协议支持也无关紧要,但 Git 协议无需加密,协议简单,作为平台内部传输服务倒是可以,目前 Gitee 使用 C++ Asio 编写 git-diamond 支持内部同步,企业存储库备份等功能。

Git 代码托管平台的伸缩性

<!--存储库分片,分布式文件系统-->

伸缩性是 Git 代码托管能否支撑成千上万用户/存储库的重要指标。像 Gogs/Gitea 这样的代码托管系统尽量认为自身运行在单一服务器上,因此这类 Git 代码托管平台伸缩性非常有限,当然如果使用 NFS/Ceph 这类分布式文件系统能够在单一服务器上支持更多的存储库,但 NFS/Ceph 这种分布式系统的做为 Git 代码托管系统的存储层,除了分布式文件系统带来的性能下降,还会带来内网带宽过高等更多的问题。

我们以使用 NFS 挂载实现伸缩性的平台和 Gitee 分布式模型 git 请求 对比,I/O 细节简化如下:

NFS I/O 细节:

NFS

Gitee Basalt I/O 细节:

Basalt

计算机是质朴的,流程的增加往往需要更多的计算资源,与 Basalt-GitSrv 相比,NFS 的 I/O 拷贝要多一些,排除 Git 协议影响我们可能会认为 Basalt 的机制要比 NFS 更节省 I/O。如果考虑到 Git 协议的影响,我们应该确信如此,git 推送或者拉取都需要耗费大量的 CPU 计算资源,而在 NFS 模型中,计算全部都是发生在前端服务器,当请求数量较多时,前端服务器则容易出现 CPU 竞争的局面,这将非常影响服务器性能,另外,对于 NFS 这样的文件系统,读写 Git 松散对象都是不得力的。另外,由于 NFS 的缓存机制,负载较高时会出现 master.lock 这样的锁定情况,导致用户使用异常。而对于 Basalt,git 则是在存储服务器上直接操作存储库,打包压缩,解压等对 CPU 需求较高的活动也在存储服务器上,这样意味着,CPU 计算被摊薄到存储服务器上了,另外 basalt-gitsrv 中间传输的是打包后的数据,这与 NFS 读写多个文件相比,网络数据量实际上是下降的。

Gitee 作为国内最早的 Git 代码托管平台之一,最开始使用 NFS 实现伸缩性,随着用户规模增长很快出现了上述所有 NFS 容易遇到的问题,后来尝试切换到 Ceph,git 松散对象给其致命一击,上线便宣告失败,出现了严重的宕机事故,数据被毁,只能从备份恢复。后来迁移到分布式架构后基本稳定运行至今(这种方案基本上增加机器即可,前端负载高加前端,存储满了加存储)。

Github 目前有大约 1亿个项目,我们假设 Github 上存储库大小平均为 10MB,目前 Github 存储库使用三副本机制,大概需要的磁盘容量为 2861 TB,按照硬盘出厂的规则(1000GB=1TB),则是需要最小 3PB。这么大的磁盘容量并不是一个标准服务器能够提供的,按照目前企业级硬盘容量较大的每个 16TB, 则需要硬盘大概 188 块。你能想象到这样大的规模能够简单的运行在分布式文件系统上吗?目前的技术基本上不太现实。

实现 Git 代码托管平台的可伸缩性重要的是实现资源的分片,最开始 Gitee 分布式时使用的是基于用户(namespace)的资源分片,也就是存储库所在的机器与 namespace 所属的机器像匹配,这实际上是一种先入为主的设计,在使用 NFS 挂载的时代,Gitee 的存储库就是按照 namespace 的前两个字母分片存储到不同服务器上,挂载到前端服务器上。因此,基于 namespace 的分片带来了一些问题,比如用户转移存储库可能需要跨机器,fork 存储库也可能需要跨机器,这就无法实现高效的轻量级 fork 功能。从去年开始迁移到基于存储库的分片,基于存储库分片基本上可以解决这些问题,但由于历史原因,轻量级 fork 等功能道阻且长。

资源的分片和请求的路由相伴而生,将存储库存储到不同服务器上后,则需要在这些服务器上实现对应的服务支持前端的请求,而前端也需要实现特定的路由机制,关于 Gitee 的路由机制架构,可以参考相关演讲或者博客。Gitee 存储服务器上使用了 git-srv 作为 Git 传输协议后端服务,而 Github 则使用了 DGit/Spokes,Gitlab 使用了 Gitaly。不同平台的技术各有侧重,比如 Gitlab Gitaly 侧重兼容旧的 OpenSSH,而 Gitee 的 Basalt-GitSrv 针对实际情况优化,与 Gitaly 相比要少一次 I/O 拷贝。 Gitee 目前不足之处是存没有完全剥离 Web(基于早期 Gitlab 发展而来),而 Gitaly 也有 Ruby 代码实现存储库读写(这块代码用 Golang 封装 I/O 多了一次拷贝)。与 Gitee 类似,Gitea 还有另一种方案,即将 Gitea 部署到多个服务器上共用 DB 支持分片,比如 gitea.com 便是这样的平台,但 gitea.com 似乎并不支持 SSH,因此并不能算有效的分片。

前端服务器的扩展性实际上要比存储服务器好,前端服务器的迁移一般不需要像存储服务器那样转移存储库,服务也一般更简单。

存储库分片之后还是无法避免特定存储库请求过多的问题,Github 的解决方案是使用三副本读写分离的 Spokes 机制,这一方案最多能够提供 3倍于单一服务器的并发读取能力,但不支持并发写入存储库。三副本机制需要解决分布式系统常见的一致性问题,引入并发写入可能会带来更多的数据冲突,破坏一致性,因此 Github 完全禁止并发写入存储库副本(即同时有不同的写存储库请求)。Gitlab 没有实现这样的技术,BitBucket 则没有披露相关资讯,Gitee 受限与硬件限制和开发资源限制,也没有实施。

github-dfs:

dgit

除了存储库的分片,代码托管平台还需要考虑数据库 SQL/NoSQL 能否支撑大规模并发,数据库的分布式集群是一个比较成熟的方案,而 Redis 最新的版本也支持集群,因此数据库的伸缩性一般不会存在太大问题,增加机器搭建集群即可。选择关系性数据库时还需要考虑许可证,数据库自身的功能等,比如 Gitlab 目前已经放弃对 MySQL 的支持,而是选择了 PostgreSQL,不过 Gitlab 的选择对于其他代码托管平台来说,也只能算作仅作参考。MariaDB 是 MySQL 的分支版本,随着 MySQL 被 Oracle 收购,开源社区渐渐丧失了对 MySQL 的兴趣,虽然 MySQL 8.0 发布已经很久,但采用 MySQL 8.0 的发行版本寥寥无几,很多还停留在 MySQL 5.X,有些发行版还使用 mariadb-connector-c 替代 libmysqlclient 作为数据库连接器,使用 MySQL 的平台很容易迁移到 MariaDB 而不用修改客户端数据库连接代码 ,MariaDB 支持线程池,而 MySQL 仅在企业版中支持线程池。一些 MariaDB 与 MySQL 的对比这里不赘述了。Gogs/Gitea 还支持使用 SQLite,但其使用 SQLite 时,基本上是放弃了伸缩性,不过目前有一个使用 Raft+libuv 实现的分布式 SQLite canonical/dqlite,可以尝试一下。Redis 一般可以作为 Web 缓存或者任务队列的中间件,目前 Redis 虽然支持集群,但就单机 Redis 而言,由于它是单线程的服务,在将内存数据持久化到磁盘是还是可能出现超时,并且单线程服务性能终究有限,在 Github 上,KeyDB 是官方 Redis 的另一个选择,KeyDB 是 Redis 的分支,完全兼容 Redis 协议,KeyDB 支持多线程,有更好的内存效率和高吞吐量。

Git 代码托管平台的增强功能

<!--大存储库,大文件,保护分支,只读目录,安全,两步验证/WebAuthn (https://github.com/duo-labs/webauthn)...-->

除了支持用户通过 Git 协议或者通过网页方式读写远程存储库,代码托管平台一般还需要提供一些与开发相关的功能增强用户体验,这些功能在不同平台之间的对比时显得非常重要。

缺陷追踪

SQLite3 使用 2007 年诞生的版本控制系统 Fossil 托管其源码,与前辈 Git 相比,它集成了 Bug 追踪,Wiki,论坛和技术报告。而对于 Git 来说,这些则需要 Git 代码托管平台自己实现,当然现在无论是 Github/Gitee/Gitlab/BitBucket 还是 Gogs/Gitea 都提供了 Issues这样的机制方便开发者第一时间报告软件缺陷或者提出功能建议。Issues 这样的功能实现主要在于让用户参与其中,也就是用的人多了,才有人气。而 Github 的 Issues 相比其他平台是最活跃的。另外 Github 还提供依赖警报功能(详情可以阅读 Introducing security alerts on GitHub),另外 Github 还收购了 Semmle 代码分析用于连续漏洞检测 (参考:Securing software, together),这也是其他 Git 代码托管平台可以借鉴的功能。

持续集成

在微软收购 Github之后,Github 有了更充足的财力在给用户提供持续集成功能,今年以来 Github 推出了 GitHub Package Registry 和 Github Actions (相关文章:GitHub Actions now supports CI/CD, free for public repositories,Introducing


www.bhylzc.cn 

GitHub Package Registry),在推出 Github Actions 之前,开发者在 Github 上大多是通过第三方软件实现 CI/CD 功能,比如我的 M2Team/Privexec 就使用 Appveroy。Windows Terminal 则使用 Azure Pipeline。平台的生态繁荣得益于第三方的支持,而对于其他平台,这些 CI/CD 支持就没有这么大的力度了,这也促使其他代码托管平台的 API 趋向 Github 化,WebHook 也逐步趋同,Github 形成了事实上的标准。比如 Gitee 的 APIv5 就保持了对 Github 的兼容。

保护分支和只读目录

Gitee 很早就实现了类似 SVN 的保护分支功能,而 Github 目前也同样支持保护分支。实现保护分支的途径很很多条,通常通过服务端 Git 钩子实现,我曾写过 《服务端 Git 钩子的妙用》 介绍了如何通过钩子实现保护分支功能。

只读目录功能同样可以通过钩子实现,如果不通过钩子,而是在 git 命令中实现,则要面临修改 git 源码,需要投入大量人力维护的情况。《服务端 Git 钩子的妙用》和 《实现 Git 目录权限控制》对实现目录权限控制有详细介绍。

其他版本控制系统接入

将使用其他版本控制系统的存储库转为 Git 非常简单,git 自身提供了 git svn 命令,可以将远程 svn 存储库一个个版本递归的转变为 Git 存储库,详细的操作可以参考 《Pro Git 2nd Edition》9.2 Git and Other Systems - Migrating to Git,这种方案的缺点比较是比较耗时,Gitee 开发者曾经帮助国内某汽车制造企业将 Subversion 存储库迁移到 Git,一开始使用 git svn,发现耗费时间太长,于是我找到了一个开源工具: git-svn-fast-import,将其编译好并修复特定 BUG 交给相关同事,后来该企业的迁移工作顺利完成。这个工具直接解析存储库将其转换为 git 存储库,省去了网络传输的消耗。

除了支持从其他版本控制系统导入外,一些代码 Git 代码托管平台也支持其他协议接入,Github/Gitee 都支持 Subversion 接入,也就是同一个存储库同时支持 git 客户端和 svn 客户端接入(像 BitBucket 支持 Mercurial 的实现实际上是单独搭建 Mercurial 存储库,不属于此类情况)。实现 Subversion 的接入几个难点,一是 Subversion 各种传输协议细节完全不同,HTTP 基于 WebDAV,而 SVN 协议又是一种自定义的 ABNF 格式协议,如果在考虑支持 Subversion 接入时还需要考虑选择哪种协议,两类协议都支持通常是不现实的,费时费力。二是 Subversion 自身也在不断发展,但实际上在愿意在 Git 代码托管平台使用 svn 的毕竟还是少数,实现 Subversion 接入通常是费力不讨好,投入与产出不成正比。

Github 实现的是 svn HTTP 协议,将 git 存储库的 commit 映射到 svn 的 revs。Github 的实现并不完美,由于需要通过 commit 计算 svn 版本信息,第一次通过 svn 协议访问存储库时会比较慢,如果当存储库较大时,检出还很容易失败,并且一次检出操作可能需要发送的非常多的请求,大概是所有目录所有文件数目之和。

Gitee 使用了 git-as-svn 实现对 svn 的支持,支持的协议有 svn:// 和 svn+ssh://,www.huizhonggjzc.cn svn+ssh:// 实际上是 svn:// 协议通过 SSH 隧道传输,在 Gitee 中,当 Basalt 接收到客户端请求在远程服务器上运行 svnserve -t 命令,则会将请求转发到 git-as-svn。在 Gitea 开发者的贡献下,git-as-svn 增加了 svnserve 命令包装,即当 Gitea 接收到 svn+ssh:// 协议请求时,则是启动包装的命令,进行一些列授权后然后在 shell 中与使用命令 exec 3<>/dev/tcp/localhost/3690 与 git-as-svn 通信,Gitee 的设计简化了验证流程,能够支持分布式架构,Gitea 目前还不能做到。git-as-svn 的基于 Java 开发,早期,开发者似乎对 git 的理念研究不够透彻,git-as-svn 的内部实现细节变动非常大,早前的实现机制不太理想,性能不佳。在 Gitee 中,我们为了避免存储库较大时开启 svn 支持带来的性能下降,额外增加了对通过 svn 协议访问存储库的限制,目前是通过 svn 协议访问存储库时,存储库的大小限制为 400MB。

在早期,兼容其他版本控制系统可能是吸引用户的一大法宝,但随着 Git 的越来越流行,支持其他版本控制系统接入逐渐成了鸡肋,前人有言:“食之无肉,弃之可惜”,正是如此。像 Github/Gitee 这样的平台虽然支持 svn,但 svn 访问的还是极少数,而支持 svn 则需要花费一些人力物力,并且在系统架构设计时增加了复杂度。如果现在开发一个 Git 代码托管平台则没有必要支持 svn。Gitee 虽然支持 svn,但 svn 每日的请求数不足 1%,在这 1% 中,又有 50% 以上的请求是特定的用户使用定时命令发送的。

大文件大存储库

公共 Git 代码托管平台很多时候实际上是给用户提供免费服务,为了过多避免大文件大存储库占用平台资源,对其作出限制必不可少,通常是大文件限制 100MB, 存储库限制 1GB. 存储库的检测简单的遍历存储库 objects 目录即可,而大文件的检测则复杂一些。Gitee 最初使用 Grit 检测 commit 是否引入了 blob 原始大小大于限制的文件,但这种机制需要解析 Git 对象,检测容易坍塌(一是检测超时,二是检测逃逸,三是存储库体积膨胀),后开使用原生钩子,改变了检测机制,则避免了这些问题。详细情况可以阅读《服务端 Git 钩子的妙用》。

禁止大文件推送这只是堵,那么大文件应该如何存放呢?Github 推出了 LFS 方案,目前 LFS 功能已经被大多数平台支持,Github 将 LFS 存储到 AWS 上,而 Gitee/Gitlab/Gogs/Gitea 大多使用自建的 LFS 服务器,存储在特定服务器上。

如果一个存储库自身就已经非常大了,如何去解决用户的访问难题呢?比如 Windows 源码超过 300GB,如果用户克隆存储库,按照每秒 1MB/s 的速度,需要 85 小时,这在任何代码托管平台都是不太现实的,好在微软 2017 年发布了 GVFS(现在叫 VFSforGit),在使用 VFSforGit 获取远程存储库时,可以只获得目录结构,并在本地创建占位文件,但用户操作这些占位文件时,VFSforGit 客户端才会去请求服务器下载对应的对象,这大大改善了巨型存储库的操作体验。VFSforGit 本地涉及到的主要技术是 ProjFS,在 Windows 上,VFSforGit 会创建 IO_REPARSE_TAG_PROJFS 类型的 ReparsePoint(NTFS 重解析点),读写到这些重解析点时,ProjFS 驱动会转发到 VFSForGit 客户端下载相应的对象。微软很多开发者在 macOS 上开发,所以官方增加了对 macOS 的支持,而 Github 的 VFSForGit fork 则增加了对 Linux 的支持,不过离实用还有一些时日,Github ProjFS 实现库是 libprojfs。

Git 代码托管平台支持 VFSforGit 客户端比较容易,目前除了 Visual Studio www.dajuhezc.cn   Online,还有 BitBucket 也增加了对 VFSforGit 的支持。我曾用 libgit2 开发了一个 git-vfs-serve 命令,用户访问 brzox 时,brzox 请求 git-srv,git-srv 执行 git-vfs-serve 便可以支持 VFSforGit 客户端的访问,不过并未上线。

安全性增强

Github 最近宣布了支持 WebAuthn: GitHub supports Web Authentication (WebAuthn) for security keys,这种机制可以使用生物识别从而避免输入用户密码,随着信息技术的不断发展,一方面,安全机制不断完善,另一方面,用户面临的风险也会多样化,复杂化。代码托管平台管理了开发者的核心资产,因此在安全上绝不能掉以轻心。当然需要做的不仅仅是及时跟进新的安全机制,还需要对整个系统及时进行安全升级,淘汰旧的协议(比如 SSL3/TLS1.1),旧的加密,哈希算法(DSA,MD5/SHA1),及时采用新的协议(TLS1.3),新的加密,哈希算法(ED25519,SHA3)等等。

文件服务

<!--附件下载,发布文件,Archive 下载-->

一个优秀的 Git 代码托管平台,应该在软件的开发整个周期都给用户提供帮助,比如下载源码,软件发布。源码下载主要指 Archive 功能,软件的发布则需要平台提供 Release/附件下载功能。

Archive

我们知道 git-archive 命令可以将存储库特定的 commit/branch 打包成一个 zip/tar 文件,而在 Git Over SSH(Git Over TCP) 实现中,只要我们允许 git-upload-archive 命令在远程服务器上运行,就打包远程服务器上的存储库的特定分支。但由于 git-upload-archive 与 git-upload-pack/git-receive-pack 存在一些不同,是的 HTTP 协议无法实现 archive 协商。提供 archive 下载则需要另辟蹊径。

我们在远程服务器上运行 git-archive 将其输出作为响应体的内容返回给 HTTP Client 便可实现 archive 下载功能,由于 archive 下载实际上是将 git tree/blob 遍历然后写入到归档文件后压缩(tar.gz/tar.bz2 ...)或者是压缩后写入文件(zip),二者都非常消耗 CPU 资源,因此我们在实现 archive 下载功能的同时应该设计 archive 的缓存功能(当然缓存应该支持过期)。gitlab-workhorse 实现的 archive 下载功能便是先尝试命中缓存,如果没有缓存则调用 git 命令然后生成写入到缓存文件。Gitee 最近实现的 blaze-archive 也采用了类似的机制,但 blaze-archive 是一个独立的命令,这个命令实际上是被 git-srv 调用,brzox 与 git-srv 通信,brzox 将 archive 返回给 HTTP Client,而缓存的删除则是 blaze 负责的。

附件,Release

附件,Release 可以选择云方案,如果要将附件和 LFS 统一管理,实际上国内的阿里云,腾讯云之类的并不合适,这些平台对并不支持类似 AWS x-amz-content-sha256 这样的头部,而是 Content-MD5 因此这些云平台要支持 LFS 则要花费多一些功夫。选择国外的 AWS, Azure 则需要考虑经济,网络等问题。当然无论如何使用云平台都需要考虑经济问题。

平台自建附件,Release 功能可以使用分布式文件系统,如 FastDFS, 但 FastFDS 并不是一个好的选择,历史比较久,存储机制安全机制现在来说都不是很优秀。有个更好的选择是 Minio, minio 使用 Golang 开发,支持 AWS API。许可协议是 Apache 2.0,商用没有阻碍,因此是用来搭建附件,Release 以及 LFS 存储服务器的不二选择。

Git 的未来

Although Git is currently the most popular code hosting system, but Git is also facing some problems, one is how to support large files large repository of these issues have Git LFS, VFSforGit such third-party solutions, but also Microsoft, Google developed participation of official Partial clone, part of the cloning needs Wire protocol support is available from too early.

February 2017, Google announced that developers break SHA1, which had for some git users a worry, because git SHA1 calculation using the object ID, but use git is actually a special SHA1, the object type and length SHA1 combined contents of the object calculated, because the length of the check, which makes SHA1 conflicts may be reduced, but in any case, is no longer safe SHA1, Git sha1collisiondetection increase in the source to avoid conflicts SHA1, and increase plans to migrate to SHA-256, and some code related to the transition from a single Hash SHA1 into  object_id. About Hash conversion, you can view the document Git hash function transition.

Git migration from SHA1 difficult to SHA-256, the first increase in two years ago has the document, and SHA-256 implementation has not seen the whole picture. Compared with the Hash migration, compression algorithm is not important evolution more difficult to implement, to date, zlib compression is no longer good, but Git may have to carry something.

Long road

Software development is an area that has been rapidly changing, and code hosting needs to continue to face new challenges, the road is long, for us endlessly.

Guess you like

Origin www.cnblogs.com/laobeipai/p/12070722.html