Mercurial的Gitaly协议服务器端实现
项目描述
HGitaly
HGitaly是Mercurial的Gitaly服务器。
它实现了与Mercurial存储库相关的Gitaly gRPC协议子集,以及自己的HGitaly协议,具有针对Mercurial的特定方法。
它有两个重叠的变体
-
HGitaly本身是用Python编写的,使用官方的
grpcio
库。 -
RHGitaly是一个高性能的部分实现,用Rust编写,并基于
tonic
gRPC框架。截至本文撰写时,RHGitaly实现了HGitaly中实现的方法的严格子集,但将来可能仅会在RHGitaly中实现一些方法。
安装
HGitaly (Python)
以下内容中,$PYTHON
通常是虚拟环境中的Python解释器,但也可以是全局的(在容器中的典型情况,在用户系统上强烈不建议)。
-
安装带有Rust部分的Mercurial(确切版本,请参阅Heptapod主仓库源代码中的要求文件)
$PYTHON -m pip install --no-use-pep517 --global-option --rust Mercurial==6.6.2
-
安装HGitaly本身(确保它不会重新安装Mercurial)
$PYTHON -m pip install hgitaly
RHGitaly
我们提供了一个自包含的源tar包。它包括适当的hg-core
Rust源代码。
-
获取tar包
wget https://download.heptapod.net/rhgitaly/rhgitaly-x.y.z.tgz
-
获取并验证GPG签名
wget https://download.heptapod.net/rhgitaly/rhgitaly-x.y.z.tgz.asc gpg --verify rhgitaly-x.y.z.tgz.asc
-
构建
tar xzf rhgitaly-x.y.z.tgz cd rhgitaly-x.y.z/rust cargo build --locked --release
-
安装到您想要的位置。以下为系统级安装的示例
sudo install -o root -g root target/release/rhgitaly /usr/local/bin
-
定义一个服务。以下为systemd的示例,根据您的需求进行调整。确保用户和所有目录都存在,并具有适当的权限。
[Unit] Description=Heptapod RHGitaly Server [Service] User=hgitaly # HGRCPATH not needed yet but probably will be at some point Environment=HGRCPATH=/etc/heptapod/heptapod.hgrc Environment=RHGITALY_LISTEN_URL=unix:/run/heptapod/rhgitaly.socket Environment=RHGITALY_REPOSITORIES_ROOT=/home/hg/repositories ExecStartPre=rm -f /run/heptapod/rhgitaly.socket ExecStart=/user/local/bin/rhgitaly Restart=on-failure [Install] WantedBy=default.target
外部可执行文件
HGitaly需要安装几个其他程序,并将它们作为单独的进程运行。
默认情况下,它期望在$PATH
中找到它们,但每个可执行文件的实际路径可以配置。
Tokei
Tokei是一个用Rust编写的编程语言分析工具。它被CommitLanguages方法使用。
Tokei可在多个Linux发行版中使用。
截至本文写作时,HGitaly支持版本12.0和12.1
Go license-detector
通常安装为license-detector
,这个独立可执行文件是go-enry
套件的一部分。它的库版本也被Gitaly使用。
它用于FindLicense方法。
Git
HGitaly可以利用一些不涉及仓库的Git命令!例如,GetPatchID的情况:git patch-id
命令不访问任何仓库。相反,它将任何补丁计算为一个标识符。
Mercurial
在未来的版本中,HGitaly和/或RHGitaly可能会调用Mercurial子进程。
截至本文写作时,这种情况尚未发生(HGitaly 1.1 / Heptapod 1.1)。
配置
HGitaly的配置在Mercurial世界中是以标准方式进行的:通过HGRC文件。
在一个典型的Heptapod安装中,这些文件被分为一个受管理的文件,与其他组件保持一致性,以及另一个由系统管理员编辑的文件(在Omnibus/Docker实例中为/etc/gitlab/heptapod.hgrc
)。
许多Mercurial调整只是因为HGitaly内部调用Mercurial,但HGitaly也有自己的部分。以下是截至HGitaly 1.1可用的设置
[hgitaly]
# paths to external executables
tokei-executable = tokei
license-detector-executable = license-detector
git-executable = git
# The number of workers process default value is one plus half the CPU count.
# It can be explicitly set this way:
#workers = 4
# Time to let a worker finish treating its current request, if any, when
# gracefully restarted. Default is high because of backup requests.
worker.graceful-shutdown-timeout-seconds = 300
# Maximum allowed resident size for worker processes (MiB).
# They get gracefully restarted if they cross that threshold
worker.max_rss_mib = 1024
# Interval between memory monitoring of workers (results dumped in logs)
worker.monitoring-interval-seconds = 60
如果未在命令行上传递--repositories-root
,则也会使用heptapod.repositories-root
。
操作
日志记录
HGitaly使用标准的logging
Python模块,以及loggingmod
Mercurial扩展从Mercurial核心和其他扩展中发出日志。因此,日志配置是在Mercurial配置中完成的,通常是从Heptapod HGRC文件之一。
通常的约定是,所有由hgitaly.service
发出的日志都提供GitLab的correlation_id
在extra
字典中,使其在格式字符串中可用。以下是一个最小示例
[correlation_id=%(correlation_id)s] [%(levelname)s] [%(name)s] %(message)s"
相反,发出日志的格式字符串必须不使用correlation_id
,因为如hgitaly.branch
、hgitaly.message
等子包无法提供值:如果发出器没有提供它,则使用依赖于某些额外信息的格式是一个硬错误。
总结结果政策
- 在
hgitaly.service
中,所有日志都必须通过hgitaly.logging.LoggerAdapter
进行记录。在格式中使用correlation_id
被强烈鼓励。 - 在
hgitaly.service
之外,日志应该自包含且有用,不需要与调用gRPC方法的明显链接。例如,应将仓库不一致记录在WARNING
级别,消息包括路径。
开发
自动化测试和持续集成
如何运行测试
通常,这将在virtualenv中运行,但这不是必需的。
python3 -m pip install -r test-requirements.txt
./run-all-tests
提示:检查run-all-tests
的内容,它只是带有标准选项集的pytest
(大多数用于覆盖率,见下文)。
单元和Mercurial集成测试
这些是主要的测试。它们位于hgitaly
和hgext3rd.hgitaly
Python包中。布局遵循每个子包都有自己的测试包的风格,以方便未来的重构。
Mercurial集成测试是用mercurial-testhelpers库编写的。它们的职责是断言HGitaly按预期工作,并与多个版本的Mercurial以及可能的其他依赖项(如grpcio)保持兼容。
这些测试的隐含假设是测试作者实际上知道预期的内容。HGitaly旨在直接替换,或者说是在Mercurial术语中对Gitaly进行翻译,这些期望实际上是一系列
- 设计选择,例如分支/主题组合与GitLab分支之间的映射规则。
- Gitaly文档和源代码。
- 对Gitaly响应的采样。
Gitaly比较测试
如果找到适当的Gitaly安装,run-all-tests
还将运行来自tests_with_gitaly
包的测试。这将在HDK工作空间内自动发生。
这些测试的目的是针对Mercurial集成测试无法做到的事情:检查HGitaly的响应是否以各种Gitaly客户端期望的形式出现,通过直接与参考Gitaly实现进行比较。
比较是通过使用由py-heptapod
提供的Git转换来实现的,这正是HGitaly旨在作为向GitLab公开Mercurial内容的一种方式。
一旦明确了Gitaly客户端的期望,实现的各种边缘情况的正确性应该留给Mercurial集成测试。
测试覆盖率
该项目正在以严格的测试覆盖率策略进行开发,由CI强制执行:没有Gitaly比较测试,覆盖率必须保持在100%。
这并不意味着贡献者必须达到这个目标才值得,甚至被考虑。贡献者可以期待维护者帮助他们达到所需的100%覆盖率,特别是如果他们是新手。当然,贡献者不能期望维护者走得更远,为他们编写缺失的测试,尽管这在紧急和关键的问题上仍然可能发生。
当然,可以使用# pragma no cover
排除一些有充分理由的选定语句。
根据Mercurial版本提供的覆盖率排除由mercurial-testhelpers的覆盖率插件提供。
在不同版本的Mercurial中意外下降的覆盖率是一个强大的警告系统,表明某些不明显的问题出现了错误,但Gitaly比较测试是在CI中针对一组固定的依赖项运行的,因此必须在不使用Gitaly比较测试的情况下实现100%的覆盖率。
另一方面,如果上游GitLab发生了关键行为的变化,Gitaly比较测试将警告我们。
测试问答和开发提示
没有Gitaly比较测试的100%覆盖率规则不是意味着要写两次相同的测试吗?
在某些情况下,是的,但这是有限的。
例如,比较测试可以告诉我们FindAllBranchNames
实际上应该返回GitLab refs(refs/heads/some-branch
),而不是GitLab分支名称。这可以通过几个非常基本的测试用例来解决。没有必要测试所有主题的映射规则,更不用说比较测试中的各种相关边缘情况。另一方面,这些边缘情况与Mercurial内部紧密相关,并且绝对需要持续在各种Mercurial版本上进行全面测试。
此外,在Mercurial集成测试和Gitaly比较测试中,对几乎相同的情况进行去重也是可能的:将两个都可用辅助函数中的公共代码进行提取。问题是这样做是否值得。
最后,比较测试应该关注Gitaly和HGitaly的结果是否一致,而不是它们包含的内容。在上面的例子中,对FindAllBranchNames
的比较可以简单地断言返回的分支名称集合的相等性。这要简单一些,也更易于维护。
如何重现由compat
CI阶段发现的覆盖率下降?
这些通常是因为只有Gitaly比较测试覆盖了语句,导致main
阶段达到100%覆盖率,但在compat
阶段没有。
首先要做的是运行时不包含Gitaly比较测试
SKIP_GITALY_COMPARISON_TESTS=yes ./run-all-tests
(环境变量中的任何非空值,即使是no
或false
,都会触发跳过)
在某些罕见情况下,覆盖率下降可能是由于Mercurial版本之间的实际变化引起的。如果发生这种情况,很可能存在一个潜在的bug。
如何运行带有Gitaly比较测试覆盖率的测试?
./run-all-tests --cov tests_with_gitaly --cov-report html
如果你没有100%覆盖率,HTML报告会很好。要显示它,只需执行以下操作
firefox htmlcov/index.html
默认情况下,Gitaly比较测试本身并未覆盖,这是事实。这是因为run-all-tests
不知道是否会因为缺少Gitaly安装而跳过它们——这是合理的。
但在启动它们的CI作业中,它们是覆盖的,因为假设Gitaly可用。对于这些,覆盖率会告诉我们有什么东西坏了,阻止了测试的运行。
如何探究Gitaly协议?
Gitaly比较测试正好提供了这样的工具:取一个测试,按需修改它,插入一个pdb
断点,然后开始。
这里的一个大优势是Gitaly比较测试的启动几乎是瞬间的,尤其是在与需要一分钟才能启动即使是完全微不足道的测试的RSpec相比。
当然,这会引发一个问题,即这些实验是否真的有用。
何时需要Gitaly比较测试?
每当需要确定预期内容时,它都可以帮助回答这个问题。它不必做更多。
何时在Heptapod Rails中优先编写RSpec测试,而不是在HGitaly中使用Gitaly比较测试?
如果你需要确保Heptapod Rails作为一个Gitaly客户端发送了正确的请求,因为那可能取决于特定的分发代码。
例如,我们目前在Rails端仍然在转换到Git。一个bug的来源可能是向HGitaly发送Git提交ID。
除此之外,预期使用Gitaly比较测试将大大提高效率。
随着Heptapod的进步,所有这一切都应该变得更加简单。
更新Gitaly gRPC协议
必须激活virtualenv
-
运行
pip install -r dev-requirements.txt
-
从与所需GitLab上游版本匹配的Gitaly检查点复制新的
proto
文件。示例在HDK环境中cp ../gitaly/proto/*.proto protos/ # we dont want the `go` subdir
-
运行
./generate-stubs
-
运行测试:
./run-all-tests
-
在仔细检查
hg status
后执行必要的hg add
更新HGitaly特定的gRPC协议
此包定义并实现了一个额外的gRPC协议,其中包含特定的Mercurial或更广泛的Heptapod的gRPC服务和方法。
协议规范
源文件位于protos/
目录中的proto
文件,与Gitaly协议相同。
它们通过以下声明来区分
package hgitaly;
每次对协议进行修改时,所有提供的编程语言的库都必须重新生成和提交,理想情况下与协议变更同时进行。
Python 库
它具有特殊地位,与协议和服务器实现一起进行版本控制。它作为 hgitaly.stub 包提供。
Python 模块由负责 Gitaly proto
文件的相同脚本生成
./generate-stubs
Ruby 库
请参阅单独的文档
其他语言
可能很快就需要为 Workhorse 或 Heptapod Shell 准备 Go 库。
有一个 Rust 库将是一个不错的选择
hgitaly-2.6.0.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | c8d5cd9dc753aabbecf2a36d28f9d3737498e980a3cf9ea700d8861c7076337d |
|
MD5 | 6d7a5222a4186735a5db2451a8fa47a6 |
|
BLAKE2b-256 | de80c906f01e172bf19276e4b7dd3c87a3e1d82b21d53fbf74493048029de12d |