Git
Chapters ▾ 2nd Edition

6.5 GitHub - スクリプトによる GitHub の操作

スクリプトによる GitHub の操作

ここまでで、GitHub の主要な機能や作業の流れはすべて紹介し終えました。 しかし、大規模なグループやプロジェクトでは、もう少しカスタマイズしたり、外部のサービスを組み込んだりしたくなることもあるかもしれません。

GitHub は、そういったハックも簡単にできるようになっています。 ここでは、GitHub のフックシステムとその API の使いかたを説明します。GitHub の動きが望みどおりになるようにしてみましょう。

フック

GitHub のリポジトリのページ上にある Hooks や Services を利用すると、GitHub と外部のシステムとのやりとりを簡単に行えます。

サービス

まずはサービスから見てみましょう。 フックやサービスの統合は、どちらもリポジトリの設定画面から行えます。 先ほど Collaborator を追加したり、デフォルトのブランチを変更したりしたのと同じ画面です。 “Webhooks and Services” タブを開くと、サービスとフックの設定画面 のような表示になるでしょう。

サービスとフック
図 129. サービスとフックの設定画面

何十種類ものサービスの中から、追加するサービスを選べます。そのほとんどが、他の商用システムやオープンソースシステムとの統合を行うものです。 継続的インテグレーションサービス、バグ (課題) 追跡システム、チャットシステム、ドキュメント作成システムなどと統合できます。 ここでは、シンプルなサービスの例として、メール送信機能を組み込む方法を示します。 “Add Service” のドロップダウンから “email” を選ぶと、メールサービスの設定 のような設定画面が表示されます。

メールサービス
図 130. メールサービスの設定

ここで “Add service” ボタンを押すと、誰かがリポジトリにプッシュするたびに、指定したアドレスにメールが届くようになります。 サービスでは、プッシュ以外にもさまざまなイベントを待ち受けることができます。 しかし、大半のサービスは、プッシュイベントだけを待ち受けて、そのデータを使って何かをするというものです。

自分たちが使っているシステムを GitHub と統合したいという場合は、 まずここをチェックして、統合のためのサービスが用意されていないかどうかを確かめましょう。 たとえば Jenkins を使ってテストを実行している場合は、Jenkins のサービスを組み込めば、 誰かがプロジェクトにプッシュするたびにテストを実行できるようになります。

フック

もう少し細やかな処理をしたい場合や、統合したいサービスが一覧に含まれていない場合は、 より汎用的な機能であるフックシステムを使うことができます。 GitHub リポジトリのフック機能は、きわめてシンプルです。 URL を指定すると、何かのイベントが発生するたびに、GitHub がその URL に HTTP POST を行います。

この機能を使うには、GitHub のフック情報を含む投稿を待ち受けるちょっとした Web サービスを準備して、 受け取ったデータに対して何かの操作をさせればいいでしょう。

フックを有効にするには、サービスとフックの設定画面 で “Add webhook” ボタンを押します。すると、Web フックの設定 のようなページに移動します。

Web フック
図 131. Web フックの設定

設定項目は、このようにシンプルです。 たいていは、URL とシークレットキーを入力して “Add webhook” を押すだけで済むことでしょう。 どのイベントに対して GitHub から情報を送らせたいのかを選ぶこともできます。 デフォルトでは、push イベントの情報だけを送るようになっており、 誰かがどこかのブランチにプッシュするたびに、情報が送られます。

Web フックを処理するための、ちょっとした Web サービスの例を見てみましょう。 ここでは、Ruby のフレームワークである Sinatra を使いました。コードが簡潔で、何をやっているかがわかりやすいだろうからです。

特定のプロジェクトの特定のブランチ上にある特定のファイルへの変更を、特定の誰かがプッシュしたときにだけ、メールを送ろうとしています。 こんなコードを書けば、これを簡単に実現できます。

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

post '/payload' do
  push = JSON.parse(request.body.read) # JSONをパースする

  # 使いたいデータを収集する
  pusher = push["pusher"]["name"]
  branch = push["ref"]

  # 変更されたファイルの一覧を取得する
  files = push["commits"].map do |commit|
    commit['added'] + commit['modified'] + commit['removed']
  end
  files = files.flatten.uniq

  # 条件をチェックする
  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 がそのフックを使おうとした際の記録が、直近の数回ぶん残されています。 それぞれのフックについて、この記録をたどれば、成功したかどうかを調べたり、リクエストとレスポンスの内容を確認したりすることができます。 これを利用すれば、フックのテストやデバッグがとても楽になることでしょう。

Webhook のデバッグ
図 132. Web フックのデバッグ情報

また、このコンソールからは、任意のペイロードをサービスに再送することもできます。

Web フックの書きかたや待ち受け可能なイベントなどの情報は、GitHub の開発者向けドキュメント (https://developer.github.com/webhooks/) をご覧ください。

GitHub API

サービスやフックを使えば、リポジトリ上で発生したイベントについてのプッシュ通知を受け取ることができます。 しかし、そのイベントについて、さらに詳しい情報が知りたい場合はどうすればいいのでしょう? Collaborator への追加や issue へのラベル付けなどを自動化したい場合は、どうすればいいのでしょう?

そんなときに使えるのが GitHub API です。 GitHub はさまざまな API エンドポイントを提供しており、Web サイト上でできることならほぼすべて、自動化できます。 ここでは、API の認証と接続の方法を学び、 さらに、issue にコメントしたりプルリクエストの状態を変更したりといった操作を、API を使って行います。

基本的な使いかた

一番基本的な使いかたは、認証が不要なエンドポイントへのシンプルな 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 http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
"
}

Issue へのコメント

しかし、Issue やプルリクエストに対してコメントしたり、プライベートなコンテンツを操作したりしたい場合は、 認証が必要になります。

認証には、いくつかの方法があります。 ベーシック認証を使ってユーザー名とパスワードを渡すこともできますが、 通常は、アクセストークンを使うことをお勧めします。 アクセストークンは、自分のアカウントの設定ページの “Applications” タブから生成できます。

アクセストークン
図 133. 設定ページの “Applications” タブからの、アクセストークンの生成

ここでは、新しいトークンを利用するスコープや、そのトークンについての説明の入力を求められます。 わかりやすい説明を登録するようにしましょう。 そのトークンを使っているスクリプトやアプリケーションを利用しなくなったときに、どのトークンを破棄すればいいのかが、わかりやすくなります。

GitHub は、生成したトークンを一度だけしか表示しません。忘れずにコピーしましょう。 これを使えば、ユーザー名やパスワードを使わなくても、スクリプト内で認証できるようになります。 この方式の利点は、やりたいことにあわせてトークンのスコープを絞れることと、 不要になったトークンを破棄できることです。

さらに、利用制限を緩和できるというメリットもあります。 認証なしの場合は、一時間当たり60リクエストまでという制限がかかります。 認証を済ませると、この制限が、一時間当たり5,000リクエストまでに緩和されます。

では、API を使って 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 を使って投稿したコメント のようにコメントに成功していることがわかるでしょう。

API によるコメント
図 134. GitHub API を使って投稿したコメント

API を使えば、Web サイト上でできることならほぼすべて実行できます。 マイルストーンの作成や設定、Issue やプルリクエストの担当者の割り当て、ラベルの作成や変更、 コミット情報へのアクセス、新しいコミットやブランチの作成、 プルリクエストのオープン、クローズ、そしてマージ、 チームの作成や編集、 プルリクエストの特定の行へのコメント、 サイト内検索なども、API で行えます。

プルリクエストのステータスの変更

最後にもうひとつ、サンプルを見てみましょう。これは、プルリクエストに対応するときに、とても便利なものです。 各コミットには、ひとつあるいは複数のステータスを持たせることができるようになっています。 そして、API を使って、このステータスを追加したり、問い合わせたりすることができるのです。

継続的インテグレーションやテスティングのサービスの大半は、この API を使っています。 コードがプッシュされたらそのコードをテストして、そのコミットがすべてのテストをパスした場合は、結果報告を返したりしているのです。 同様に、コミットメッセージが適切な書式になっているかどうかを調べたり、 コードを貢献するときのガイドラインに沿っているかどうかを調べたり、 適切に署名されているかどうかを調べたり、さまざまなことを行えます。

ここでは、コミットメッセージに Signed-off-by という文字列が含まれているかどうかを調べるちょっとした Web サービスを、 リポジトリのフック機能で利用することを考えてみましょう。

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

post '/payload' do
  push = JSON.parse(request.body.read) # JSONをパースする
  repo_name = push['repository']['full_name']

  # コミットメッセージを調べる
  push["commits"].each do |commit|

    # 文字列 Signed-off-by を探す
    if /Signed-off-by/.match commit['message']
      state = 'success'
      description = 'Successfully signed off!'
    else
      state = 'failure'
      description = 'No signoff found.'
    end

    # 状態を 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 という文字列が含まれるているかどうかを調べて、 API エンドポイント /repos/<user>/<repo>/statuses/<commit_sha> への HTTP POST でステータスを指定します。

ここで送信できる情報は、ステータス (success, failure, error) と説明文、詳細な情報を得るための URL、 そして単一のコミットに複数のステータスがある場合の “コンテキスト” です。 たとえば、テスティングサービスがステータスを送ることもあれば、このサンプルのようなバリデーションサービスがステータスを送ることもあります。 それらを区別するのが “context” フィールドです。

誰かが GitHub 上で新しいプルリクエストを作ったときに、もしこのフックを設定していれば、API で設定したコミットのステータス のようになるでしょう。

コミットのステータス
図 135. API で設定したコミットのステータス

メッセージに “Signed-off-by” という文字列が含まれているコミットの隣にはグリーンのチェックマークが表示されています。 一方、作者が署名し忘れたコミットの隣には、赤い×印がついています。 また、そのプルリクエストの最新のコミットのステータスを見て、もし failure だったら警告を発しているということもわかります。 テストの結果を見てこの API を使うようにすると、とても便利です。テストが通らなかったコミットを、うっかりマージしてしまわずに済むでしょう。

Octokit

ここまでほぼすべてのサンプルは、curl を使ったシンプルな HTTP リクエストだけで実現してきましたが、 オープンソースのライブラリを使えば、これらの API を、もっと慣用的な書きかたで使えるようになります。 本書の執筆時点では、Go や Objective-C、Ruby、そして .NET 用のライブラリが公開されています。 詳細は http://github.com/octokit をご覧ください。HTTP がらみの大半を、あなたの代わりに処理してくれることでしょう。

これらのツールをうまく活用して GitHub をカスタマイズして、自分自身のワークフローにうまくあてはまるようにしてみましょう。 API の完全なドキュメントや、一般的な使いかたの指針は、 https://developer.github.com をご覧ください。