章节 ▾ 第二版

6.5 GitHub - GitHub 脚本化

GitHub 脚本化

现在我们已经介绍了 GitHub 的所有主要功能和工作流程,但是任何大型团队或项目都可能希望进行自定义或集成外部服务。

对我们来说幸运的是,GitHub 在很多方面都是可以被“hack”的。 在本节中,我们将介绍如何使用 GitHub 的 hooks 系统及其 API,使 GitHub 按照我们想要的方式工作。

服务和 Hooks

GitHub 存储库管理中的“Hooks”和“服务”部分是将 GitHub 与外部系统交互的最简单方法。

服务

首先,我们来看看“服务”。 “Hooks”和“服务”集成都可以在存储库的“设置”部分中找到,我们在之前查看添加协作者和更改项目的默认分支时看到过这个位置。 在“Webhooks 和服务”选项卡下,您将看到类似 服务和 Hooks 配置部分

Services and Hooks configuration section
图 129. 服务和 Hooks 配置部分

您可以从中选择数十种服务,其中大多数是与其他商业和开源系统的集成。 它们中的大多数用于持续集成服务、错误和问题跟踪器、聊天室系统和文档系统。 我们将逐步介绍如何设置一个非常简单的服务,即“邮件”hook。 如果您从“添加服务”下拉列表中选择“邮件”,您将获得一个类似于 邮件服务配置的配置屏幕。

Email service configuration
图 130. 邮件服务配置

在这种情况下,如果我们点击“添加服务”按钮,那么我们指定的电子邮件地址将在每次有人推送到存储库时收到一封电子邮件。 服务可以侦听许多不同类型的事件,但大多数服务只侦听 push 事件,然后使用该数据执行某些操作。

如果你正在使用的系统希望与 GitHub 集成,你应该在这里查看是否有现成的服务集成可用。例如,如果你使用 Jenkins 在你的代码库上运行测试,你可以启用 Jenkins 内置的服务集成,以便在每次有人推送到你的仓库时启动一次测试运行。

钩子 (Hooks)

如果你需要更具体的东西,或者你想与列表中未包含的服务或站点集成,你可以使用更通用的钩子系统。GitHub 仓库钩子非常简单。你指定一个 URL,GitHub 会在你想要的任何事件发生时向该 URL 发送一个 HTTP payload。

通常,这种方式的工作原理是你可以设置一个小型的 web 服务来监听 GitHub 钩子的 payload,然后在收到数据时做一些事情。

要启用一个钩子,你可以在 服务和钩子配置 部分点击 “添加 webhook” 按钮。这将带你到一个看起来像 Web hook 配置 的页面。

Web hook configuration
图 131. Web hook 配置

web hook 的配置非常简单。在大多数情况下,你只需输入一个 URL 和一个密钥,然后点击 “添加 webhook”。有一些选项可以选择你希望 GitHub 向你发送 payload 的事件 — 默认情况下,只会接收到 push 事件的 payload,即当有人将新代码推送到你仓库的任何分支时。

让我们看一个你可能设置的用来处理 web hook 的小型 web 服务示例。我们将使用 Ruby web 框架 Sinatra,因为它相当简洁,你应该可以很容易地看到我们在做什么。

假设我们希望在特定的人推送到我们项目的特定分支修改特定文件时收到一封电子邮件。我们可以很容易地用这样的代码做到这一点

require 'sinatra'
require 'json'
require 'mail'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON

  # gather the data we're looking for
  pusher = push["pusher"]["name"]
  branch = push["ref"]

  # get a list of all the files touched
  files = push["commits"].map do |commit|
    commit['added'] + commit['modified'] + commit['removed']
  end
  files = files.flatten.uniq

  # check for our criteria
  if pusher == 'schacon' &&
     branch == 'ref/heads/special-branch' &&
     files.include?('special-file.txt')

    Mail.deliver do
      from     'tchacon@example.com'
      to       'tchacon@example.com'
      subject  'Scott Changed the File'
      body     "ALARM"
    end
  end
end

在这里,我们获取 GitHub 传递给我们的 JSON payload,并查找谁推送了它,他们推送到了哪个分支,以及在所有被推送的提交中触及了哪些文件。然后我们根据我们的标准进行检查,如果匹配则发送一封电子邮件。

为了开发和测试类似这样的东西,你在设置钩子的同一个屏幕上有一个不错的开发者控制台。你可以看到 GitHub 尝试为该 webhook 做的最后几次交付。对于每个钩子,你可以深入查看交付的时间、是否成功以及请求和响应的主体和标头。这使得测试和调试你的钩子变得非常容易。

Web hook debugging information
图 132. Web hook 调试信息

另一个很棒的功能是你可以重新交付任何 payload 以轻松测试你的服务。

有关如何编写 webhooks 以及你可以监听的所有不同事件类型的更多信息,请访问 GitHub 开发者文档,网址为 https://githubdocs.cn/en/webhooks-and-events/webhooks/about-webhooks

GitHub API

服务和钩子让你能够接收关于你的仓库中发生的事件的推送通知,但是如果你需要关于这些事件的更多信息怎么办?如果你需要自动化一些事情,例如添加协作者或标记 issue 怎么办?

这就是 GitHub API 派上用场的地方。GitHub 有大量的 API 端点,可以以自动化的方式执行几乎所有你可以在网站上做的事情。在本节中,我们将学习如何验证身份并连接到 API,如何评论一个 issue 以及如何通过 API 更改 Pull Request 的状态。

基本用法

你可以做的最基本的事情是对不需要身份验证的端点执行一个简单的 GET 请求。这可能是一个用户或一个开源项目的只读信息。例如,如果我们想了解更多关于名为 “schacon” 的用户的信息,我们可以运行如下命令

$ curl https://api.github.com/users/schacon
{
  "login": "schacon",
  "id": 70,
  "avatar_url": "https://avatars.githubusercontent.com/u/70",
# …
  "name": "Scott Chacon",
  "company": "GitHub",
  "following": 19,
  "created_at": "2008-01-27T17:19:28Z",
  "updated_at": "2014-06-10T02:37:23Z"
}

有大量的类似端点可以获取关于组织、项目、issue、commit 的信息 — 几乎所有你可以在 GitHub 上公开看到的东西。你甚至可以使用 API 来渲染任意 Markdown 或找到一个 .gitignore 模板。

$ curl https://api.github.com/gitignore/templates/Java
{
  "name": "Java",
  "source": "*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
"
}

评论一个 Issue

但是,如果你想在网站上执行一个动作,例如评论一个 Issue 或 Pull Request,或者如果你想查看或与私有内容交互,你需要进行身份验证。

有几种方法可以进行身份验证。你可以使用仅包含你的用户名和密码的基本身份验证,但通常最好使用个人访问令牌。你可以从你的设置页面的 “Applications” 选项卡生成它。

Generate your access token from the “Applications” tab of your settings page
图 133. 从你的设置页面的 “Applications” 选项卡生成你的访问令牌

它会询问你希望为此令牌使用哪些范围以及描述。确保使用一个好的描述,以便在你不再使用你的脚本或应用程序时放心地删除该令牌。

GitHub 只会向你显示一次令牌,所以请务必复制它。你现在可以使用它在你的脚本中进行身份验证,而不是使用用户名和密码。这很好,因为你可以限制你想做的事情的范围,并且令牌是可撤销的。

这也增加了你的速率限制。在不进行身份验证的情况下,你将被限制为每小时 60 个请求。如果进行身份验证,你每小时最多可以发出 5,000 个请求。

所以让我们用它来在一个 issue 上发表评论。假设我们想在一个特定的 issue 上发表评论,Issue #6。为此,我们必须向 repos/<user>/<repo>/issues/<num>/comments 发送一个 HTTP POST 请求,并将我们刚刚生成的令牌作为 Authorization 标头。

$ curl -H "Content-Type: application/json" \
       -H "Authorization: token TOKEN" \
       --data '{"body":"A new comment, :+1:"}' \
       https://api.github.com/repos/schacon/blink/issues/6/comments
{
  "id": 58322100,
  "html_url": "https://github.com/schacon/blink/issues/6#issuecomment-58322100",
  ...
  "user": {
    "login": "tonychacon",
    "id": 7874698,
    "avatar_url": "https://avatars.githubusercontent.com/u/7874698?v=2",
    "type": "User",
  },
  "created_at": "2014-10-08T07:48:19Z",
  "updated_at": "2014-10-08T07:48:19Z",
  "body": "A new comment, :+1:"
}

现在如果你转到该 issue,你可以看到我们刚刚成功发布的评论,如 通过 GitHub API 发布的评论

A comment posted from the GitHub API
图 134. 通过 GitHub API 发布的评论

你可以使用 API 来做几乎所有你可以在网站上做的事情 — 创建和设置里程碑,将人员分配给 Issue 和 Pull Request,创建和更改标签,访问提交数据,创建新的提交和分支,打开、关闭或合并 Pull Request,创建和编辑团队,评论 Pull Request 中的代码行,搜索站点等等。

更改 Pull Request 的状态

我们将看的最后一个例子,因为它在你处理 Pull Request 时非常有用。每个提交可以有一个或多个与之关联的状态,并且有一个 API 可以添加和查询该状态。

大多数持续集成和测试服务都使用这个 API 来响应推送,通过测试被推送的代码,然后报告该提交是否已通过所有测试。你也可以使用它来检查提交消息的格式是否正确,提交者是否遵循了你所有的贡献指南,提交是否已有效签名 — 任何数量的事情。

假设你在你的仓库上设置了一个 webhook,它会访问一个小型的 web 服务,该服务检查提交消息中是否有 Signed-off-by 字符串。

require 'httparty'
require 'sinatra'
require 'json'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON
  repo_name = push['repository']['full_name']

  # look through each commit message
  push["commits"].each do |commit|

    # look for a Signed-off-by string
    if /Signed-off-by/.match commit['message']
      state = 'success'
      description = 'Successfully signed off!'
    else
      state = 'failure'
      description = 'No signoff found.'
    end

    # post status to GitHub
    sha = commit["id"]
    status_url = "https://api.github.com/repos/#{repo_name}/statuses/#{sha}"

    status = {
      "state"       => state,
      "description" => description,
      "target_url"  => "http://example.com/how-to-signoff",
      "context"     => "validate/signoff"
    }
    HTTParty.post(status_url,
      :body => status.to_json,
      :headers => {
        'Content-Type'  => 'application/json',
        'User-Agent'    => 'tonychacon/signoff',
        'Authorization' => "token #{ENV['TOKEN']}" }
    )
  end
end

希望这很容易理解。在这个 web hook 处理程序中,我们查看刚刚推送的每个提交,我们在提交消息中查找字符串 'Signed-off-by',最后我们通过 HTTP POST 到 /repos/<user>/<repo>/statuses/<commit_sha> API 端点并附带状态。

在这种情况下,你可以发送一个状态('success'、'failure'、'error'),一个关于发生了什么事情的描述,一个用户可以访问以获取更多信息的目标 URL,以及一个 “context”,以防单个提交有多个状态。例如,一个测试服务可能会提供一个状态,而像这样的验证服务也可能会提供一个状态 — “context” 字段是区分它们的方式。

如果有人在 GitHub 上打开一个新的 Pull Request 并且设置了这个钩子,你可能会看到类似于 通过 API 提交状态 的内容。

Commit status via the API
图 135. 通过 API 提交状态

你现在可以看到在消息中包含 “Signed-off-by” 字符串的提交旁边有一个小小的绿色复选标记,以及作者忘记签名的提交旁边的一个红色叉号。你还可以看到 Pull Request 采用了分支上最后一个提交的状态,并在失败时警告你。如果你使用此 API 获取测试结果,这将非常有用,这样你就不会意外地合并最后一个提交未通过测试的内容。

Octokit

虽然我们几乎在这些示例中都是通过 curl 和简单的 HTTP 请求来完成所有操作的,但存在几个开源库以更惯用的方式提供此 API。在撰写本文时,支持的语言包括 Go、Objective-C、Ruby 和 .NET。查看 https://github.com/octokit 了解更多关于这些的信息,因为它们会为你处理大部分 HTTP。

希望这些工具可以帮助你自定义和修改 GitHub,以便更好地为你的特定工作流程服务。有关整个 API 的完整文档以及常见任务的指南,请查看 https://githubdocs.cn/

scroll-to-top