从零到英雄:回传拉取请求

Quarkus 项目的迭代速度非常快,当我们在准备 bug 修复版本时,通常需要回溯几十个 pull request。需要回溯的 pull request 数量巨大,通过 GitHub UI 进行操作非常不便且耗时(点击 PR,复制/粘贴 commit hash 进行 cherry-pick,移除标签,分配里程碑,为所有修复的问题分配里程碑,然后继续下一个),并且 UI 还有一些限制(例如,无法按合并日期排序以避免冲突)。因此,我们决定自动化这项工作,并构建了一个应用程序。当然,是用 Quarkus 构建的!

在我们深入探讨解决方案之前,让我先快速解释一下我们的发布流程。

发布流程

Quarkus 团队采用 `Major.Minor.Patch.Classifier`(例如,1.7.0.CR11.7.0.Final)的版本模式。根据版本号的提升,采用不同的流程。

主版本和次版本更新

我们的主分支始终为下一个主版本或次版本发布做好准备。这个过程通常非常顺利,不需要回溯。

补丁版本或分类器更新

每当 Quarkus 即将发布新的补丁版本(或第二个 CR)时,我们的发布团队就会开始回溯合并到 master 分支的 pull request 中的 commit。他们如何知道要抓取哪个 pull request?我们有一个 `triage/backport?` 标签,我们的团队会将其添加到希望包含在即将发布的补丁版本中的功能 pull request 上。

我们如何实现自动化?

该应用程序主要通过 GitHub 提供的 GraphQL API 查询包含特定标签的 GitHub 存储库的已合并 pull request 和已关闭 issues,并将它们的里程碑更改为 UI 中选定的里程碑(然后移除该特定标签)。通过在每个 pull request 旁边提供一个按钮来将这些 pull request 引入的更改(即 cherry-picking)的必要 `git cherry-pick` 命令复制到剪贴板,这一过程得到了简化——我们更希望发布工程师手动执行此步骤。

截图

选择里程碑

index

选择要回溯的 pull request

backports

我们在 Quarkus 中的经验

在这里,您可以找到反向移植应用程序中使用的扩展的摘要。

  • ArC (CDI):管理服务生命周期。

  • MicroProfile Config:用于外部化属性。

  • RESTEasy:公开一个 UI 可以使用的端点。还使用 Qute 的类型安全模板(参见 Qute)提供 UI。

  • Qute:用于所有模板(UI 和 GraphQL 查询 payload)。

  • Rest Client:用于调用 GitHub 的 GraphQL 端点

  • Cache:用于从 GitHub 的 GraphQL 端点缓存打开的里程碑

  • Hibernate Validator:用于验证输入。

  • Jackson (JSON 库):用于解析 GitHub GraphQL 端点的结果。

实时编码

实时编码是 Quarkus 的一个杀手级功能,在开发过程中更改类和方法时提供了快速的反馈。作为经验法则,在开发 Quarkus 应用程序时,请始终使用 `./mvnw quarkus:dev`。

MicroProfile Config

配置要使用的 GitHub 存储库(用于测试,甚至用于非 Quarkus 存储库)和 GitHub 身份验证令牌(以及必要时不同的回溯标签),应该可以轻松配置,而无需更改任何源代码。Quarkus 使用 Microprofile Config,因此我们将这些属性外部化了。Quarkus 还支持 `.env` 文件,我们在测试时使用了它们。这使得本地测试更加容易,因为我们无需直接更改 `application.properties` 或在运行应用程序之前设置任何环境变量或系统属性。

Qute

Qute 是一个模板引擎。我们使用它来生成 UI 和生成 GraphQL 查询,在这些查询中,仅使用 GraphQL 变量是不够的——例如,从问题编号列表中获取问题数据。我们使用类型安全模板来生成 UI 和 GraphQL 查询。

Rest Client

简而言之,GraphQL 就是向 HTTP 端点 POST 一些 JSON 数据并将结果解析为 JSON 文档。就是这么简单。Microprofile REST Client 是执行此任务的一个好选择,因此我们想到了这个。

import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.core.HttpHeaders;

import io.vertx.core.json.JsonObject;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@RegisterRestClient(baseUri = "https://api.github.com/graphql")
public interface GraphQLClient {

    @POST
    JsonObject graphql(@HeaderParam(HttpHeaders.AUTHORIZATION) String authentication, JsonObject query);
}

在 GitHubService 中,我们现在可以消费 GraphQLClient 对象。

    @Inject
    @RestClient
    GraphQLClient graphQLClient;

您可以在这里看到客户端是如何被调用的。

后续步骤

考虑到动态查询的性质,我们决定不使用 SmallRye GraphQL 扩展,但当扩展支持该功能时,这一点可以改变。

鸣谢

该应用程序的开发大约花费了一周时间(包括学习 GraphQL)。这得益于以下 Quarkus 团队成员的贡献:

  • 48 Guillaume Smet:负责漂亮的Frontend 工作。

  • 48 George Gastaldi:负责后端和 GraphQL 集成的开发。

  • 48 David Lloyd:负责从 commit 消息中提取 issue 编号所需的疯狂正则表达式。