はじめに
GitHub APIを使って、リポジトリのissueを削除する方法を紹介する。
動作確認環境
- Ubuntu 22.04 x86_64
- Python 3.10.12
環境構築
Bash
BashでJSONを扱うためにjqをインストールする。
sudo apt update
sudo apt install -y jq
Python
Pythonの環境を準備しておく。requestsインストールする。
pip install requests
方法
GitHub APIは、REST APIとGraphQL APIが用意されている。REST APIは簡便で扱いやすいが、今回行いたいissueの削除は直接サポートされていない(以下のAPIリストの画像を参照)。
そこで本記事では、REST APIとGraphQL APIを組み合わせて、issueを削除する方法を紹介する。
基本的な流れは以下である。
- APIのアクセストークンを取得し設定する
- REST APIで、削除したいissueのissueIdを取得する
- GraphQL APIで、上記issueIdを指定して、issueを削除する
APIのアクセストークンを取得し設定する
Personal access tokens (classic)、または、Fine-grained personal access tokensを使い、削除したいリポジトリへの書き込み権限を与える。詳細は、こちらの記事を参照。本記事では、Personal access tokens (classic)で動作確認を行った。
REST APIで、削除したいissueのissueIdを取得する
REST APIのList repository issuesを使い、リポジトリ内の全てのissueIdを取得する。
以下のコマンドでAPIの動作確認が可能。
curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/OWNER/REPO/issues
- : 上記設定したアクセストークン
- OWNER : リポジトリ所有者名 (Organizationsまたは個人)
- REPO : リポジトリ名
結果例
[
{
"id": 1,
"node_id": "MDU6SXNzdWUx",
"url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
"repository_url": "https://api.github.com/repos/octocat/Hello-World",
"labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}",
"comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments",
"events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events",
"html_url": "https://github.com/octocat/Hello-World/issues/1347",
"number": 1347,
"state": "open",
"title": "Found a bug",
"body": "I'm having a problem with this.",
"user": {
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"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
},
"labels": [
{
"id": 208045946,
"node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
"name": "bug",
"description": "Something isn't working",
"color": "f29513",
"default": true
}
],
"assignee": {
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"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
},
"assignees": [
{
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"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
}
],
"milestone": {
"url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
"html_url": "https://github.com/octocat/Hello-World/milestones/v1.0",
"labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels",
"id": 1002604,
"node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==",
"number": 1,
"state": "open",
"title": "v1.0",
"description": "Tracking milestone for version 1.0",
"creator": {
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"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
},
"open_issues": 4,
"closed_issues": 8,
"created_at": "2011-04-10T20:09:31Z",
"updated_at": "2014-03-03T18:58:10Z",
"closed_at": "2013-02-12T13:22:01Z",
"due_on": "2012-10-09T23:39:01Z"
},
"locked": true,
"active_lock_reason": "too heated",
"comments": 0,
"pull_request": {
"url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347",
"html_url": "https://github.com/octocat/Hello-World/pull/1347",
"diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
"patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"
},
"closed_at": null,
"created_at": "2011-04-22T13:33:48Z",
"updated_at": "2011-04-22T13:33:48Z",
"closed_by": {
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"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
},
"author_association": "COLLABORATOR",
"state_reason": "completed"
}
]
上記の、node_id
が、issueに割り当てられているユニークIDであるissueIdである。削除したいissueIdをメモしておく。また、number
は、GitHubリポジトリから参照できるissue番号なので、こちらもメモしておく。
GraphQL APIで、上記issueIdを指定してissueを削除する
GraphQL APIの、deleteIssueで、上記のissueIdを指定してissueを削除する。deleteIssue APIの入力の詳細はDeleteIssueInputを参照。
以下にissueIdからissueを削除するBashスクリプトの例を示す。リポジトリ名、issue番号は、削除には不要だがわかりやすさのために記載している。
#!/bin/bash
ISSUE_ID=<ISSUE_ID> # issueId
TOKEN=<YOUR_TOKEN> # アクセストークン
REPO=<REPO> # リポジトリ名 (API呼び出しには不要)
ISSUE_NUMBER=<ISSUE_NUMBER> # issue番号 (API呼び出しには不要)
# GraphQLエンドポイント
URL="https://api.github.com/graphql"
# GraphQLクエリ
QUERY=$(cat <<EOF
{
"query": "mutation { deleteIssue(input: {issueId: \\"$ISSUE_ID\\"}) { repository { id } } }"
}
EOF
)
# curlコマンドでGraphQLクエリを実行
RESPONSE=$(curl -s -H "Authorization: bearer $TOKEN" -H "Content-Type: application/json" -X POST -d "$QUERY" $URL)
# ステータスコードの取得
STATUS_CODE=$(echo $RESPONSE | jq -r '.status_code')
# 結果の確認
if [ "$STATUS_CODE" -eq 200 ]; then
echo "$REPO issue #$ISSUE_NUMBER deleted successfully."
exit 0
else
echo "Failed to delete $REPO issue #$ISSUE_NUMBER: $STATUS_CODE"
echo $RESPONSE | jq
exit 1
fi
例: リポジトリ内の全てのissueを削除する(Python)
Pythonを使って、あるリポジトリ内の全てのissueを削除する例を示す。
警告
下記スクリプトを実行する場合は、テスト用リポジトリで試すことを推奨
#!/usr/bin/python3
# -*- coding: utf-8 -*-
OWNER="<OWNER>"
REPO="<REPO>"
TOKEN="<TOKEN>"
# リポジトリ内の全てのGitHub issueを削除する関数
def delete_all_github_issues(self):
headers = {'Authorization': f'token {TOKEN}'}
issues_url = f'https://api.github.com/repos/{OWNER}/{REPO}/issues'
response = requests.get(issues_url, headers=headers)
issues = response.json()
print(issues)
if isinstance(issues, list):
print(f'{REPO} issues count {len(issues)}')
else:
print(f'{REPO} issues are not found: {issues}')
return False
success = True
for issue in issues:
issue_number = issue['number']
issue_id = issue['node_id']
# issueIdからissueを削除するスクリプトを呼び出す
success = success & self._delete_github_issue(REPO, issue_number, issue_id)
return success
# リポジトリ内の、あるGitHub issueを削除する関数
def _delete_github_issue(self, repo_name: str, issue_number: int, issue_id: int):
url = 'https://api.github.com/graphql'
headers = {
'Authorization': f'bearer {TOKEN}',
'Content-Type': 'application/json'
}
query = """
mutation {
deleteIssue(input: {issueId: "%s"}) {
repository {
id
}
}
}
""" % issue_id
response = requests.post(url, headers=headers, json={'query': query})
if response.status_code == 200:
print(f'{repo_name} issue #{issue_number} deleted successfully.')
return True
else:
print(f'Failed to delete {repo_name} issue #{issue_number}: {response.status_code}')
print(response.json())
return False
def main():
delete_all_github_issues()
if __name__ == '__main__':
main()
まとめ
GitHub APIを使って、リポジトリのissueを削除する方法を紹介した。リポジトリの移行などでissueの削除を自動化したい場合に有用。
参考