跳转到主要内容

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解释器,但也可以是全局的(在容器中的典型情况,在用户系统上强烈不建议)。

  1. 安装带有Rust部分的Mercurial(确切版本,请参阅Heptapod主仓库源代码中的要求文件)

    $PYTHON -m pip install --no-use-pep517 --global-option --rust Mercurial==6.6.2
    
  2. 安装HGitaly本身(确保它不会重新安装Mercurial)

    $PYTHON -m pip install hgitaly
    

RHGitaly

我们提供了一个自包含的源tar包。它包括适当的hg-core Rust源代码。

  1. 获取tar包

    wget https://download.heptapod.net/rhgitaly/rhgitaly-x.y.z.tgz
    
  2. 获取并验证GPG签名

    wget https://download.heptapod.net/rhgitaly/rhgitaly-x.y.z.tgz.asc
    gpg --verify rhgitaly-x.y.z.tgz.asc
    
  3. 构建

    tar xzf rhgitaly-x.y.z.tgz
    cd rhgitaly-x.y.z/rust
    cargo build --locked --release
    
  4. 安装到您想要的位置。以下为系统级安装的示例

    sudo install -o root -g root target/release/rhgitaly /usr/local/bin
    
  5. 定义一个服务。以下为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_idextra字典中,使其在格式字符串中可用。以下是一个最小示例

[correlation_id=%(correlation_id)s] [%(levelname)s] [%(name)s] %(message)s"

相反,发出日志的格式字符串必须不使用correlation_id,因为如hgitaly.branchhgitaly.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集成测试

这些是主要的测试。它们位于hgitalyhgext3rd.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

(环境变量中的任何非空值,即使是nofalse,都会触发跳过)

在某些罕见情况下,覆盖率下降可能是由于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

  1. 运行 pip install -r dev-requirements.txt

  2. 从与所需GitLab上游版本匹配的Gitaly检查点复制新的proto文件。示例在HDK环境中

    cp ../gitaly/proto/*.proto protos/  # we dont want the `go` subdir
    
  3. 运行 ./generate-stubs

  4. 运行测试: ./run-all-tests

  5. 在仔细检查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 库将是一个不错的选择

支持者

AWSAWS云计算和安全赞助商DatadogDatadog监控FastlyFastlyCDNGoogleGoogle下载分析MicrosoftMicrosoftPSF赞助商PingdomPingdom监控SentrySentry错误日志StatusPageStatusPage状态页面