章节 ▾ 第二版

6.5 GitHub - 脚本化 GitHub

脚本化 GitHub

现在我们已经涵盖了 GitHub 的所有主要功能和工作流程,但任何大型组织或项目都可能有自己想要进行的定制化需求,或者想要集成的外部服务。

幸运的是,GitHub 在许多方面都具有很强的可定制性(hackable)。在本节中,我们将介绍如何利用 GitHub 的钩子(hooks)系统及其 API,让 GitHub 按照我们想要的方式运作。

服务(Services)与钩子(Hooks)

GitHub 仓库管理中的“钩子与服务”部分是让 GitHub 与外部系统交互的最简单方式。

服务

首先我们来看看“服务”。钩子和服务集成都可以在仓库的“设置”(Settings)部分找到,我们之前曾在那里添加协作者和更改项目的默认分支。在“Webhooks and Services”标签页下,你会看到类似 服务与钩子配置部分 的内容。

Services and Hooks configuration section
图 129. 服务与钩子配置部分

你可以选择数十种服务,其中大多数是与其他商业和开源系统的集成。大部分用于持续集成服务、Bug 和问题跟踪器、聊天室系统以及文档系统。我们将演示如何设置一个非常简单的服务:邮件钩子(Email hook)。如果你从“添加服务”(Add Service)下拉菜单中选择“email”,你将看到一个类似 邮件服务配置 的配置界面。

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

在这种情况下,如果我们点击“添加服务”按钮,每当有人推送到仓库时,我们指定的电子邮件地址就会收到一封邮件。服务可以监听许多不同类型的事件,但大多数只监听推送(push)事件并对该数据进行处理。

如果你使用的某个系统希望与 GitHub 集成,你应该在此处检查是否有现成的服务集成。例如,如果你使用 Jenkins 在你的代码库上运行测试,你可以启用内置的 Jenkins 服务集成,以便在每次有人向你的仓库推送代码时触发一次测试运行。

钩子

如果你需要更具体的功能,或者想要集成不在该列表中的服务或网站,可以使用更通用的钩子系统。GitHub 仓库钩子非常简单。你指定一个 URL,GitHub 就会在发生你想要的任何事件时,向该 URL 发送 HTTP 有效负载(payload)。

通常的做法是,你可以搭建一个小型的 Web 服务来监听 GitHub 的钩子有效负载,并在接收到数据时对其进行处理。

要启用钩子,请点击 服务与钩子配置部分 中的“添加 webhook”按钮。这将带你进入一个看起来像 Web 钩子配置 的页面。

Web hook configuration
图 131. Web 钩子配置

Web 钩子的配置非常简单。在大多数情况下,你只需输入 URL 和密钥,然后点击“添加 webhook”。你可以选择 GitHub 发送有效负载的事件类型——默认情况下,仅在有人向仓库的任何分支推送新代码时触发 push 事件。

让我们看一个你可能设置用来处理 Web 钩子的小型 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 有效负载,查看是谁推送的、推送到哪个分支,以及推送的所有提交中修改了哪些文件。然后我们将这些信息与我们的标准进行比对,如果匹配,则发送一封电子邮件。

为了开发和测试此类功能,你在设置钩子的同一界面中拥有一个很好的开发者控制台。你可以查看 GitHub 最近尝试为该 Web 钩子进行的几次交付。对于每个钩子,你可以深入查看交付时间、是否成功,以及请求和响应的头部和正文。这使得测试和调试你的钩子变得非常容易。

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

另一个很棒的功能是,你可以重新发送任何有效负载以轻松测试你的服务。

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

GitHub API

服务和钩子为你提供了一种接收仓库事件推送通知的方法,但如果你需要关于这些事件的更多信息怎么办?如果你需要自动化诸如添加协作者或给问题添加标签等任务怎么办?

这时 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、提交等信息——几乎所有你在 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,或者如果你想查看或交互私有内容,则需要进行身份验证。

有几种验证方式。你可以使用用户名和密码进行基本身份验证,但通常使用个人访问令牌(Personal Access Token)是更好的做法。你可以从设置页面的“Applications”标签页生成该令牌。

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

它会询问你希望此令牌具备哪些权限(scopes)以及描述信息。请确保使用清晰的描述,以便在你的脚本或应用程序不再使用时,你可以放心地删除该令牌。

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,通过测试推送的代码来响应推送事件,然后报告该提交是否通过了所有测试。你也可以利用它来检查提交消息格式是否正确、提交者是否遵循了你的贡献指南、提交是否经过了有效签名等等。

假设你在仓库上设置了一个 Web 钩子,它会触发一个小型的 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 钩子处理程序中,我们遍历刚刚推送的每一个提交,查找提交消息中的“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/