GitHub API でファイルを読み書きする
この記事は すごくなりたいがくせいぐるーぷ Advent Calendar 2021 22日目の記事です。
この記事では、Octokitを用いてGitHub API v3を呼び出し、GitHub上のファイルを読み書きしていきます。
この記事の内容はこのリポジトリに記載しています。
認証
公開リポジトリに対する読み取り操作には認可が必要ありません。書き込み操作を行う場合には、Octokit
クラスのコンストラクタなどで以下の指定をしてください。
1. Personal Access Tokenを使う場合
GitHubの Settings
→ Developer settings
→ Personal access tokens
から、Personal Access Tokenを発行します。当然ですが、最低限 repo
スコープを有効化する必要があります。
Octokit
クラスのコンストラクタで、 auth
オプションにPersonal Access Tokenを指定します。
import { Octokit } from "octokit";
new Octokit({ auth: "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" });
2. OAuth Appとして認証する場合
OAuth 2を使って発行したアクセストークンも、同様に auth
オプションに指定します。
new Octokit({ auth: "gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" });
OAuthの認可手順についてはこちらから。
なお、OAuthApp
クラスを使うことでもOAuth関連の操作を行うことができます。
3. GitHub Appとして認証する
App
クラスを経由してGitHub Appとして認証したOctokit
クラスのインスタンスを取得します。
インストールIDがわかっている場合
await new App({
appId: 99999999, // AppのID
privateKey: "-----BEGIN RSA PRIVATE KEY-----\n ..." // 秘密鍵
}).getInstallationOctokit(999999); // インストールID
getInstallationOctokit
が Promise<Octokit>
であるため、await
することで Octokit
クラスのインスタンスを取得します。
インストールIDについては上記サイトの通りにAPIを呼び出したり、次のApp#eachInstallation
から抽出したりすることができます。
ユーザーのIDなどがわかっている場合
// パターン1. コールバック関数
new App({
appId: 99999999, // AppのID
privateKey: "-----BEGIN RSA PRIVATE KEY-----\n ..." // 秘密鍵
}).eachInstallation(({ installation, octokit }) => {
if(installation.account.login === "ユーザーID") {
// [ユーザーID]にインストールされたAppとして認証したoctokit
}
});
// パターン2. AsyncIterable
const iterator = new App({
appId: 99999999, // AppのID
privateKey: "-----BEGIN RSA PRIVATE KEY-----\n ..." // 秘密鍵
}).eachInstallation.iterator();
for await (const installation of iterator) {
if(installation.account.login === "ユーザーID") {
// [ユーザーID]にインストールされたAppとして認証したoctokit
}
}
App#eachInstallation
によってインストールされた各Appに対して操作が行えます。また、App#eachInstallation.iterator
がAsyncIterable
であるため、for-await-of
でのループ処理もできます。
ファイルを読む
const owner = "e-chan1007"; // 所有者(ユーザー/組織)
const repo = "1222-github-api"; // リポジトリ
const barnch = "main"; // ブランチ
const latestCommit = (await octokit.rest.repos.getBranch({ owner, repo, branch })).data.commit;
const files = (await octokit.rest.git.getTree({ owner, repo, tree_sha: latestCommit.sha })).data.tree;
const blob = (await octokit.rest.git.getBlob({ owner, repo, file_sha: files.find(file => file.path === "README.md")?.sha! })).data;
const content = Buffer.from(blob.content, "base64").toString("utf-8");
- 最終コミットのハッシュを取得
- 最終コミットに紐付いたツリーを取得
- ツリーから任意のファイルのハッシュを取得
- ファイルを取得
- base64で送られたファイルの内容をデコードする
(ここでは最終コミットとしますが、特定のコミットを対象にする場合はそのハッシュを指定してください。)
ファイルを書き込む
const owner = "e-chan1007"; // 所有者(ユーザー/組織)
const repo = "1222-github-api"; // リポジトリ
const barnch = "main"; // ブランチ
const createdBlob = (await octokit.rest.git.createBlob({
owner,
repo,
content: Buffer.from("Hello GitHub API With Base64 Encoded!", "utf-8").toString("base64"),
encoding: "base64"
})).data;
const latestCommit = (await octokit.rest.repos.getBranch({ owner, repo, branch })).data.commit;
const createdTree = (await octokit.rest.git.createTree({
owner,
repo,
tree: [{
type: "blob",
path: "test.txt",
mode: "100644",
content: "Hello GitHub API!"
}, {
type: "blob",
path: "base64/test.txt",
mode: "100644",
sha: createdBlob.sha
}],
base_tree: latestCommit.sha
})).data;
const createdCommit = (await octokit.rest.git.createCommit({
owner,
repo,
message: "Test Commit with GitHub API",
tree: createdTree.sha,
parents: [latestCommit.sha],
})).data;
await octokit.rest.git.updateRef({
owner,
repo,
ref: `heads/${target.branch}`,
sha: createdCommit.sha
});
- (ファイルをアップロード)
- 最終コミットのハッシュを取得
- 最終コミットをもとにツリーを作成
- 最終コミットに繋がるようにコミット
-
refs/heads/ブランチ
が最新コミットを指すようにする
ファイルのアップロードについては、事前に行うことも、ツリーの作成と同時に行うこともできます。事前に行う場合はツリーの作成時にハッシュを、同時に行う場合は内容を直接指定します。
(おそらく)バイナリをアップロードする場合は事前にbase64エンコードしてアップロードします。
平文の場合はどちらでも可能です。(コード中ではbase64エンコード済)
ツリーの作成時に、base_tree
にもとにするツリー(コミット)のハッシュを指定します。そうすることで、base_tree
にあったファイルはそのままに、tree
パラメーターにあるファイルで上書きor追加します。
一方で、base_tree
を指定しない場合、tree
パラメーターにないファイルは削除されたとみなされます。
tree で指定 |
tree で未指定 |
|
---|---|---|
base_tree を指定 |
上書き&新規作成 | 保持 |
base_tree 未指定 |
上書き&新規作成 | 削除 |
削除を伴う場合には、base_tree
を指定せずに、保持するすべてのファイルを指定する必要があるようです。
ツリーの作成時にmode
を指定していますが、これはファイルの種類を示しています。通常のファイルには100644
を指定しておきます。その他のパラメーターについて、詳しくはこちらから。
ちなみに、GitHubではPGP署名がされたコミットにverified
という表示がつきます。GitHub APIを用いる場合、GitHub Appとして認証を行うと署名済コミットとして扱われます。他にも、コミット時にsignature
パラメーターを指定することもできます。
さいごに
以上、GitHub上のファイルをGitHub APIを利用して読み書きする方法を紹介しました。GitHubをデータベースとしたサービスの開発などに使えそうですね。
少し手順が複雑な面もありますが、ぜひご自身のリポジトリで試してみてください。
Discussion