概要
- 直近の作業内容を振り返るため、リポジトリ/ブランチをまたいだGitHub上でのコミット一覧を取得したい
- GitHub Rest APIではコミットの一覧をブランチごとに別々に取得する必要がある
- GitHub GraphQL APIならクエリを工夫すると一括で取得することができる
- 以下のようなWebアプリケーションを実装して運用
背景
直近の作業内容を振り返ったり、まとめて報告するタイミングでGitHub上で各リポジトリ/ブランチのコミット履歴を確認していました。個人としての作業内容をまとめるならリポジトリ、ブランチをまたいだコミットの一覧を見たいものです。GitHub上でそうした表示はできなさそうなので自作することにしました。
GitHub API
GitHubのAPIにはRest APIとGraphQL APIの二種類があります。いずれにおいてもGitHub上のリポジトリやブランチ、コミットを取得することができます。
Rest API
Rest APIでは組織、リポジトリなどの各エンドポイントにリクエストを送ることで情報を取得することができます。今回の目的を達成するためには自分が作成したか、自分の組織に属するリポジトリの一覧を取得した上でそれぞれの最近のコミット一覧を取得する必要があります。
Rest APIのCommitsエンドポイントを見てみましょう。リポジトリとオーナーをパラメータで指定してコミットの一覧を取得することができます。一見リポジトリのコミット一覧が取得できるように見えますが、デフォルトブランチ(masterなど)のコミット一覧しか取得することができません。sha
パラメータにブランチ名を指定することでデフォルトブランチ以外のブランチのコミットを取得することができます。しかしながらリポジトリのコミット一覧を取得するにはそのリポジトリのブランチ全てについてリクエストを送る必要があります。
GitHub APIにはのリクエスト数制限があり、1時間あたり5000回までに制限されています。リポジトリ多数あり、かつそのブランチが大量にある場合にその全てに対してコミットの一覧を取得しようとするとこの制限に容易に到達してしまいます。リポジトリ数*ブランチ数の回数リクエストが送られるからです。一度取得するだけなら可能ですが、例えばアプリケーションとして実装して取得/表示することは難しいと考えられます。
こうした点から今回の目的にはRest APIは適さないと判断しました。
GraphQL API
GraphQL APIではエンドポイントは一つで、そこに対してGraphQLクエリを送ると対応する構造のデータが取得できる仕組みです。このAPIではユーザーの関わっているリポジトリのブランチのコミットの……というように一つの階層構造を持ったクエリによってネストしたデータを取得することができます。複数のブランチのコミット一覧を一つのクエリで取得することができるのでリクエスト回数制限に引っかかる可能性も低くなります。
問題はそのようなクエリをどのように構築していくかという点です。ドキュメントが複雑なので一つずつ読み解きながらクエリを構築していきましょう。
まずはuser
クエリを使ってログインユーザーの情報を取得します。
取得されるデータはUser
というスキーマで定義されています。repositoriesContributedTo
というフィールドがあり、contributionTypes
のパラメータを指定することでそのユーザーがコミットを行ったリポジトリを取得できます。orderBy
パラメータで順序も指定できます。
以下のようなクエリでuser
クエリを利用します。repositoriesContributedTo
フィールドの項目については後述します。
query(
$userName: String!
) {
user(login: $userName) {
repositoriesContributedTo(
includeUserRepositories: true,
contributionTypes: COMMIT,
first: 10,
orderBy: {
direction: DESC,
field: PUSHED_AT
}
) {
# 取得するフィールドを指定
}
}
}
{
"userName":"sample"
}
次にrepositoriesContributedTo
フィールド中身を取得するため、RepositoryConnection
スキーマの定義を見ていきましょう。
ややこしいですが、ページングされたリポジトリの配列のスキーマです。nodes
フィールドで取得できるRepository
オブジェクトには各リポジトリの情報が入っています。
ここまででクエリは以下のようになります。login
フィールドで指定したユーザーがコミットしたリポジトリの情報が取得できました。
query(
$userName: String!
) {
user(login: $userName) {
repositoriesContributedTo(
includeUserRepositories: true,
contributionTypes: COMMIT,
first: 10,
orderBy: {
direction: DESC,
field: PUSHED_AT
}
) {
nodes {
name
url
owner {
login
}
}
}
}
}
ややわかりづらいですがリポジトリのブランチ一覧は、refs
というフィールドで取得することができます。このフィールドのパラメータにrefPrefix
があり、これをrefs/heads/
と指定することでブランチの一覧が取得されます。このrefs
フィールドで取得される一覧もページングされています。このフィールドはRefConnection
型になります。
このRefConnection
のnodes
フィールドはRef
型になっています。
ここまでで以下のようなクエリになります。指定したユーザーがコミットしたリポジトリの情報と、ブランチの一覧が取得できました。
query(
$userName: String!
) {
user(login: $userName) {
repositoriesContributedTo(
includeUserRepositories: true,
contributionTypes: COMMIT,
first: 10,
orderBy: {
direction: DESC,
field: PUSHED_AT
}
) {
nodes {
name
url
owner {
login
}
refs(
refPrefix: "refs/heads/",
first: 100
) {
nodes {
name
}
}
}
}
}
}
Ref
型の詳細な内容はtarget
フィールドに含まれています。このフィールドはGitObject
という抽象的な型(インターフェース)になっていて、フラグメントを使って抽出します。
今回はブランチのコミットの一覧を取得したいので、Commit
スキーマを利用します。このオブジェクト自体はブランチの最新のコミットです。history
フィールドを利用するとそこから遡ってブランチのコミット一覧を取得できます。引数author
でコミットしたユーザーのメールアドレスを指定することで、抽出対象のユーザーが行ったコミットに絞り込むことができます。また直近のものだけに絞り込みたいのでsince
フィールドに1週間前など適当なタイムスタンプを指定しています。
history
フィールドもページングされているので、同様にnodes
フィールドから個々のコミットを取得します。nodes
フィールドもCommit
型なのであとは必要な情報のフィールドを指定するだけです。以下のようなクエリがひとまずの完成形です。
query(
$userName: String!,
$email: String!,
$since: GitTimestamp,
) {
user(login: $userName) {
repositoriesContributedTo(
includeUserRepositories: true,
contributionTypes: COMMIT,
first: 10,
orderBy: {
direction: DESC,
field: PUSHED_AT
}
) {
nodes {
name
url
owner {
login
}
refs(
refPrefix: "refs/heads/",
first: 100
) {
nodes {
name
target {
... on Commit {
history(
first: 100,
author:{
emails: [$email]
},
since: $since
){
nodes {
message
url
committedDate
oid
changedFiles
additions
deletions
}
}
}
}
}
}
}
}
}
}
{
"userName":"sample",
"email":"sample@sample.com",
"since":"YYYY-MM-DDTHH:mm:ss"
}
上のクエリでは簡単のため、条件に該当するリポジトリの最初の10件だけを取得していました。取得できる件数の上限は100件までになっています。
それ以降のリポジトリを取得するなら、pageInfo
フィールドからendCursor
とhasNextPage
を取得しておきます。hasNextPage
がtrue
ならendCursor
の値をrepositoriesContributedTo
フィールドのafter
パラメータに入れたリクエストを送ります。そうすると10件以降のリポジトリが取得できます。これをhasNextPage
がfalse
になるまで繰り返します。
以下のクエリではこの方法で対象ユーザーが最近コミットしたリポジトリを全件取得しています。
query(
$userName: String!,
$nextCursor: String
) {
user(login: $userName) {
repositoriesContributedTo(
includeUserRepositories: true,
contributionTypes: COMMIT,
first: 10,
orderBy: {
direction: DESC,
field: PUSHED_AT
},
after: $nextCursor
) {
totalCount
pageInfo {
endCursor
hasNextPage
}
nodes {
name
url
owner {
login
}
}
}
}
}
{
"userName":"sample",
"nextCursor":"xxxx"
}
この場合、取得されたリポジトリそれぞれについてコミットの一覧を取得する必要があります。以下のクエリをそれぞれのリポジトリに対して実行します。リポジトリ名とオーナー名を指定してrepository
クエリで任意のリポジトリの情報を取得できます。
query(
$name: String!,
$owner: String!,
$email: String!,
$since: GitTimestamp,
$nextCursor: String
) {
repository(
name: $name,
owner: $owner
) {
name
url
owner {
login
}
refs(
refPrefix: "refs/heads/",
first: 100,
after: $nextCursor
) {
totalCount
pageInfo {
endCursor
hasNextPage
}
nodes {
name
target{
... on Commit {
history(
first:100,
author:{
emails: [$email]
},
since: $since
){
nodes {
message
url
committedDate
oid
changedFiles
additions
deletions
}
}
}
}
}
}
}
}
{
"name":"sample",
"owner":"sample",
"email":"sample@sample.com",
"since":"YYYY-MM-DDTHH:mm:ss",
"nextCursor":"xxxx"
}
さらに対象のリポジトリのブランチも一度に100件までしか取得できないので、同様にendCursor
を使ってそれ以降を取得していきます。各ブランチのコミット一覧も同様にページネーションされていますが、ひとまず直近のコミットが取得できればいいので100件で十分と判断しました。
リポジトリごとにリクエストを送ることになりますが、ブランチ100件ごとにコミット一覧を取得できるのでRest APIに比べて必要なリクエストの数は少なくなっています。
運用
以下のようなWebアプリケーションにして運用しています。Next.js/Tailwind CSSを利用しています。
OAuth認証した上で上記のクエリを使ってコミットの一覧を取得、フロントエンドで日毎や週ごとにまとめて時系列で表示することができます。14日前や1ヶ月前などどの期間のコミット一覧を取得するか選択したり、コミットした週やリポジトリ単位でまとめて表示する機能があります。
プライバシーポリシーの関係で弊社で利用しているアプリケーションをそのまま公開できないため、ソースコードだけ置いておきます。クローンしてローカルで起動したり適当にデプロイしていただければ利用いだけます。以下に利用手順を書いておきます。
git clone https://github.com/oshima-valuesccg/commit-timeline.git
# or
git clone git@github.com:oshima-valuesccg/commit-timeline.git
OAuthクライアント作成
以下の手順でGitHub APIのOAuthクライアントを作成します。
ローカル環境で動作させる場合はAuthorization callback URL
をhttp://localhost:3000/api/auth/authorize
に設定してください。
環境変数設定
またClient ID
とClient secret
をコピーしてアプリケーションの環境変数に設定する必要があります。.env.development
ファイルを作成して以下のように設定します。最後のCOOKIE_PASSWORD
は認証用のCookieを暗号化するパスワードです。32文字以上で適当にランダムな文字列を指定してください。
GITHUB_CLIENT_ID="xxxxxxxxxxxxxxxxxxxx"
GITHUB_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GITHUB_REDIRECT_URL="http://localhost:3000/api/auth/authorize"
COOKIE_PASSWORD="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
環境変数を設定した上で以下のコマンドで起動させます。
npm run dev
localhost:3000
にアクセスしてGitHubのOAuth認証を行えばコミットの一覧を表示できます。
おわりに
GitHub GraphQL API、575で美しいですね。いい下の句が思い付いた方はコメントお願いいたします。
参考文献