GitHub API v3を使ってPull Requestの最終Push日時を取得する
こんにちは、CX事業本部の若槻です。
最近、Pull RequestのCloseの自動化のために、「最後にCommitがPushされた日時を取得」する処理を実装する必要がありました。
そこで今回は、GitHub API v3を使ってPull Requestの最終Push日時を取得する方法を確認してみました。
やってみた
アクセストークンの発行
まだの場合は下記から発行します。スコープはrepo
のrepo:status
を指定します。
Pull Requestの最終Push日時を取得する
まずPull Requestの情報を取得します。GitHub API v3の下記のエンドポイントを使用すれば取得可能です。
GET /repos/{owner}/{repo}/pulls/{pull_number}
GITHUB_ACCESS_AOKEN
には先ほどのアクセストークンを指定します。octocat/hello-world
はGitHubが公開しているRepositoryです。このRepositoryの番号997
のPull Requestを取得してみます。
% GITHUB_ACCESS_AOKEN=##GITHUB_ACCESS_AOKEN## % result=$(curl \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${GITHUB_ACCESS_AOKEN}" \ https://api.github.com/repos/octocat/hello-world/pulls/997)
するとPull Requestの情報がJSONで出力されます。(長いため折りたたんでいます。)
クリックで展開
{ "url": "https://api.github.com/repos/octocat/Hello-World/pulls/997", "id": 685581540, "node_id": "MDExOlB1bGxSZXF1ZXN0Njg1NTgxNTQw", "html_url": "https://github.com/octocat/Hello-World/pull/997", "diff_url": "https://github.com/octocat/Hello-World/pull/997.diff", "patch_url": "https://github.com/octocat/Hello-World/pull/997.patch", "issue_url": "https://api.github.com/repos/octocat/Hello-World/issues/997", "number": 997, "state": "open", "locked": false, "title": "modify by logerror", "user": { "login": "logerrors", "id": 31824303, "node_id": "MDQ6VXNlcjMxODI0MzAz", "avatar_url": "https://avatars.githubusercontent.com/u/31824303?v=4", "gravatar_id": "", "url": "https://api.github.com/users/logerrors", "html_url": "https://github.com/logerrors", "followers_url": "https://api.github.com/users/logerrors/followers", "following_url": "https://api.github.com/users/logerrors/following{/other_user}", "gists_url": "https://api.github.com/users/logerrors/gists{/gist_id}", "starred_url": "https://api.github.com/users/logerrors/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/logerrors/subscriptions", "organizations_url": "https://api.github.com/users/logerrors/orgs", "repos_url": "https://api.github.com/users/logerrors/repos", "events_url": "https://api.github.com/users/logerrors/events{/privacy}", "received_events_url": "https://api.github.com/users/logerrors/received_events", "type": "User", "site_admin": false }, "body": "", "created_at": "2021-07-07T23:48:41Z", "updated_at": "2021-07-07T23:51:57Z", "closed_at": null, "merged_at": null, "merge_commit_sha": "7f951a24c1a3e83ce53230e80f243b7a5585ddb6", "assignee": null, "assignees": [ ], "requested_reviewers": [ ], "requested_teams": [ ], "labels": [ ], "milestone": null, "draft": false, "commits_url": "https://api.github.com/repos/octocat/Hello-World/pulls/997/commits", "review_comments_url": "https://api.github.com/repos/octocat/Hello-World/pulls/997/comments", "review_comment_url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}", "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/997/comments", "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/9f81418f73fbebff5974c33bd705eaa8957f1dfc", "head": { "label": "logerrors:master", "ref": "master", "sha": "9f81418f73fbebff5974c33bd705eaa8957f1dfc", "user": { "login": "logerrors", "id": 31824303, "node_id": "MDQ6VXNlcjMxODI0MzAz", "avatar_url": "https://avatars.githubusercontent.com/u/31824303?v=4", "gravatar_id": "", "url": "https://api.github.com/users/logerrors", "html_url": "https://github.com/logerrors", "followers_url": "https://api.github.com/users/logerrors/followers", "following_url": "https://api.github.com/users/logerrors/following{/other_user}", "gists_url": "https://api.github.com/users/logerrors/gists{/gist_id}", "starred_url": "https://api.github.com/users/logerrors/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/logerrors/subscriptions", "organizations_url": "https://api.github.com/users/logerrors/orgs", "repos_url": "https://api.github.com/users/logerrors/repos", "events_url": "https://api.github.com/users/logerrors/events{/privacy}", "received_events_url": "https://api.github.com/users/logerrors/received_events", "type": "User", "site_admin": false }, "repo": { "id": 383948788, "node_id": "MDEwOlJlcG9zaXRvcnkzODM5NDg3ODg=", "name": "Hello-World", "full_name": "logerrors/Hello-World", "private": false, "owner": { "login": "logerrors", "id": 31824303, "node_id": "MDQ6VXNlcjMxODI0MzAz", "avatar_url": "https://avatars.githubusercontent.com/u/31824303?v=4", "gravatar_id": "", "url": "https://api.github.com/users/logerrors", "html_url": "https://github.com/logerrors", "followers_url": "https://api.github.com/users/logerrors/followers", "following_url": "https://api.github.com/users/logerrors/following{/other_user}", "gists_url": "https://api.github.com/users/logerrors/gists{/gist_id}", "starred_url": "https://api.github.com/users/logerrors/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/logerrors/subscriptions", "organizations_url": "https://api.github.com/users/logerrors/orgs", "repos_url": "https://api.github.com/users/logerrors/repos", "events_url": "https://api.github.com/users/logerrors/events{/privacy}", "received_events_url": "https://api.github.com/users/logerrors/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/logerrors/Hello-World", "description": "My first repository on GitHub!", "fork": true, "url": "https://api.github.com/repos/logerrors/Hello-World", "forks_url": "https://api.github.com/repos/logerrors/Hello-World/forks", "keys_url": "https://api.github.com/repos/logerrors/Hello-World/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/logerrors/Hello-World/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/logerrors/Hello-World/teams", "hooks_url": "https://api.github.com/repos/logerrors/Hello-World/hooks", "issue_events_url": "https://api.github.com/repos/logerrors/Hello-World/issues/events{/number}", "events_url": "https://api.github.com/repos/logerrors/Hello-World/events", "assignees_url": "https://api.github.com/repos/logerrors/Hello-World/assignees{/user}", "branches_url": "https://api.github.com/repos/logerrors/Hello-World/branches{/branch}", "tags_url": "https://api.github.com/repos/logerrors/Hello-World/tags", "blobs_url": "https://api.github.com/repos/logerrors/Hello-World/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/logerrors/Hello-World/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/logerrors/Hello-World/git/refs{/sha}", "trees_url": "https://api.github.com/repos/logerrors/Hello-World/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/logerrors/Hello-World/statuses/{sha}", "languages_url": "https://api.github.com/repos/logerrors/Hello-World/languages", "stargazers_url": "https://api.github.com/repos/logerrors/Hello-World/stargazers", "contributors_url": "https://api.github.com/repos/logerrors/Hello-World/contributors", "subscribers_url": "https://api.github.com/repos/logerrors/Hello-World/subscribers", "subscription_url": "https://api.github.com/repos/logerrors/Hello-World/subscription", "commits_url": "https://api.github.com/repos/logerrors/Hello-World/commits{/sha}", "git_commits_url": "https://api.github.com/repos/logerrors/Hello-World/git/commits{/sha}", "comments_url": "https://api.github.com/repos/logerrors/Hello-World/comments{/number}", "issue_comment_url": "https://api.github.com/repos/logerrors/Hello-World/issues/comments{/number}", "contents_url": "https://api.github.com/repos/logerrors/Hello-World/contents/{+path}", "compare_url": "https://api.github.com/repos/logerrors/Hello-World/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/logerrors/Hello-World/merges", "archive_url": "https://api.github.com/repos/logerrors/Hello-World/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/logerrors/Hello-World/downloads", "issues_url": "https://api.github.com/repos/logerrors/Hello-World/issues{/number}", "pulls_url": "https://api.github.com/repos/logerrors/Hello-World/pulls{/number}", "milestones_url": "https://api.github.com/repos/logerrors/Hello-World/milestones{/number}", "notifications_url": "https://api.github.com/repos/logerrors/Hello-World/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/logerrors/Hello-World/labels{/name}", "releases_url": "https://api.github.com/repos/logerrors/Hello-World/releases{/id}", "deployments_url": "https://api.github.com/repos/logerrors/Hello-World/deployments", "created_at": "2021-07-07T23:32:53Z", "updated_at": "2021-07-07T23:41:01Z", "pushed_at": "2021-07-08T01:18:14Z", "git_url": "git://github.com/logerrors/Hello-World.git", "ssh_url": "git@github.com:logerrors/Hello-World.git", "clone_url": "https://github.com/logerrors/Hello-World.git", "svn_url": "https://github.com/logerrors/Hello-World", "homepage": "", "size": 1, "stargazers_count": 0, "watchers_count": 0, "language": null, "has_issues": false, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": null, "forks": 0, "open_issues": 0, "watchers": 0, "default_branch": "master" } }, "base": { "label": "octocat:master", "ref": "master", "sha": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", "user": { "login": "octocat", "id": 583231, "node_id": "MDQ6VXNlcjU4MzIzMQ==", "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "repo": { "id": 1296269, "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", "name": "Hello-World", "full_name": "octocat/Hello-World", "private": false, "owner": { "login": "octocat", "id": 583231, "node_id": "MDQ6VXNlcjU4MzIzMQ==", "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/octocat/Hello-World", "description": "My first repository on GitHub!", "fork": false, "url": "https://api.github.com/repos/octocat/Hello-World", "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", "events_url": "https://api.github.com/repos/octocat/Hello-World/events", "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", "created_at": "2011-01-26T19:01:12Z", "updated_at": "2021-07-09T02:07:41Z", "pushed_at": "2021-07-07T23:48:42Z", "git_url": "git://github.com/octocat/Hello-World.git", "ssh_url": "git@github.com:octocat/Hello-World.git", "clone_url": "https://github.com/octocat/Hello-World.git", "svn_url": "https://github.com/octocat/Hello-World", "homepage": "", "size": 1, "stargazers_count": 1670, "watchers_count": 1670, "language": null, "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 1577, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 534, "license": null, "forks": 1577, "open_issues": 534, "watchers": 1670, "default_branch": "master" } }, "_links": { "self": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/997" }, "html": { "href": "https://github.com/octocat/Hello-World/pull/997" }, "issue": { "href": "https://api.github.com/repos/octocat/Hello-World/issues/997" }, "comments": { "href": "https://api.github.com/repos/octocat/Hello-World/issues/997/comments" }, "review_comments": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/997/comments" }, "review_comment": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}" }, "commits": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/997/commits" }, "statuses": { "href": "https://api.github.com/repos/octocat/Hello-World/statuses/9f81418f73fbebff5974c33bd705eaa8957f1dfc" } }, "author_association": "NONE", "auto_merge": null, "active_lock_reason": null, "merged": false, "mergeable": true, "rebaseable": true, "mergeable_state": "clean", "merged_by": null, "comments": 0, "review_comments": 0, "maintainer_can_modify": true, "commits": 1, "additions": 1, "deletions": 0, "changed_files": 1 }
このうちPull Requestの最終Push日時は.head.repo.pushed_at
の値となります。
% echo ${result} | jq -r .head.repo.pushed_at 2021-07-08T01:18:14Z
これでPull Requestの最終Push日時が取得できました。
別解
以前、前述のエンドポイントのバグ?で.head.repo
に同Pull Requestの.base.repo
と同じ値がなぜか入ってきて、うまく最終Push日時が取得できない時がありました。
その時には別解として以下のエンドポイントを代替で利用していました。
GET /repos/{owner}/{repo}/events
このエンドポイントを使用すると指定のRepositoryで発生したEventが取得できます。EventのType一覧は下記で確認できます。
しかしこのエンドポイントはRepository内の全てのEventが取得できてしまうため、その結果から指定のPull RequestのPushEvent
を下記のようなループ処理を実装するなどしてフィルターする必要があります。
page=1 pushed_at=$null while [ -z "$pushed_at" ] && [ $page -le 5 ]; do echo "page="$page pushed_at=$(curl -sS -H "$ACCEPT_HEADER" -H "$AUTH_HEADER" "${GH_API_HOST}/events?per_page=100&page=${page}" | \ jq --arg ref $pull_branch_ref '.[] | select(.payload.ref == $ref and .type == "PushEvent")' | \ jq -rs '.[0].created_at' \ ) page=$((page+1)) done
しかし最初のGET /repos/{owner}/{repo}/pulls/{pull_number}
が正常に使えるようになったため、上記の冗長な方法を取らずに済んで良かったです。
以上