まだ Github を手動で操作してるの？？コマンド化のススメ ~~レポジトリ管理編~~

## はじめに

こんにちは、@uchidash456 です！！直近はライフスタイル変化の乱れ打ちにバタバタと対応しております。

さて今回は、「レポジトリの乱れは心の乱れ」ということで、**レポジトリ管理をコマンド化し、1 つのレポジトリで一括管理できるようにした話**について書いていきます。

## 背景

昨今は AWS や GCP のコード管理は当たり前になっていますね。業務ではほとんどの設定を Terraform で記述しています。
一方で、コード管理をするための Github Repository がコード管理されていることは少ないのではないでしょうか？一度レポジトリの設定をしてしまえば、滅多に変更することはないので、コード化するほどでもないかもしれません。

私の経験からすると、新たにレポジトリ作成する際に**毎回同じ設定をするのは手間**ですし、頻度が少ないので**普段の設定を忘れてしまう**こともあります。
特に企業であれば、何百とレポジトリがある場合が多く、管理が曖昧になり微妙なレポジトリが散見しがちです。後に粛清祭りが開催されることでしょう。。。

**微妙なレポジトリの特徴**を以下に挙げてみました。

- 管理人不在（レポジトリのステータスが不明。削除したいときに確認が手間。）
- オプション設定が荒い（ブランチが保護されていない、デフォルトブランチに直 push できるなど）
- 統一感の欠如（命名規則や Description の有無など）

コード管理することの**メリット**は、上記のような問題を解決することにあります。逆に、コード管理することの**デメリット**を挙げると、以下のようなことが考えられます。

- コード化が手間
- Gihtub API 仕様が変更されてしまった場合に対応が必要

## 成果物 🎁

今回 [github_manager](https://github.com/shoot16625/github_manager/tree/main) という管理用レポジトリを作成し、以下の一連のレポジトリ設定をコマンド化しました。

1. レポジトリの作成
   - レポジトリが存在しない場合に新規作成します。
2. レポジトリの更新
   - デフォルトブランチ名やトピックス、Description などの設定の更新を行います。
3. ブランチ保護設定の作成
   - Branch Protection Rule を作成します。
4. ブランチ保護設定の更新
   - PR 必須、Approve 必須などの設定を更新します。

![t-rec.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343913/ef8a0210-2ceb-2b14-5993-89fe9cb4018e.gif)

### PC 環境

```sh
# sw_vers
ProductName: macOS
ProductVersion: 12.6
BuildVersion: 21G115

# gh --version
gh version 2.23.0 (2023-02-07)
```

### コード紹介

実際のソースコードの一部を紹介します。
まずは、レポジトリの作成と更新を行う部分を抜粋します。

```sh:main.sh
#!/bin/sh

set -u

readonly DEV_NULL=/dev/null

readonly OWNER_NAME=shoot16625
readonly REPO_NAME=github_manager
readonly LICENSE=MIT
readonly DEFAULT_BRANCH=main
readonly DESCRIPTION="Github を管理するレポジトリ"
readonly TOPICS="github,cli,gh"

# create repo
err_msg=$(gh repo view $OWNER_NAME/$REPO_NAME 2>&1 >/dev/null)
if [ -n "$err_msg" ]; then # if repo does not exist
 gh repo create $REPO_NAME --add-readme --public --license $LICENSE
fi

# update repo
gh repo edit $OWNER_NAME/$REPO_NAME --add-topic $TOPICS --default-branch $DEFAULT_BRANCH --delete-branch-on-merge --description "$DESCRIPTION"
```

基本的に`gh repo`で楽できる部分は利用しつつ、非対応なコマンドは`gh api`で愚直にクエリ指定していきます。（ref. [GitHub CLI manual](https://cli.github.com/manual/), [GitHub CLI で始める快適 GitHub 生活](https://qiita.com/ryo2132/items/2a29dd7b1627af064d7b)）
レポジトリが既に存在する場合に、`gh repo create`した場合はエラー（`HTTP 422: Repository creation failed. (https://api.github.com/user/repos) name already exists on this account`）が発生するので、その前に存在確認をしています。ただ、シェルスクリプトで `set -e` しない場合はエラーが発生しても処理が継続されるので、エラーハンドリングせずとも良い気もします。※万が一、今後 `gh repo create`の仕様が変わり、レポジトリの初期化が強制されるようになった場合、コードが吹っ飛ぶ恐れはあります。

これを実行すると画像のような設定ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343913/ad8e5a2d-f856-3c67-d519-979c5ee50a8a.png)
次に、ブランチ保護の設定を行う部分を抜粋します。

```sh:main.sh
# create a branch protection rule
repositoryId=$(gh api graphql -f query='
{
 repository(owner:"'$OWNER_NAME'", name:"'$REPO_NAME'"){id}
}
' -q .data.repository.id)

gh api graphql -f query='
mutation($repositoryId:ID!,$branch:String!) {
 createBranchProtectionRule(input: {
  repositoryId: $repositoryId
  pattern: $branch
 }) { clientMutationId }
}
' -f repositoryId="$repositoryId" -f branch="$DEFAULT_BRANCH" >$DEV_NULL

# update a branch protection rule
branchProtectionRuleId=$(gh api graphql -f query='
{
 repository(owner:"'$OWNER_NAME'", name:"'$REPO_NAME'"){
  branchProtectionRules(first:100){
   nodes{
    id,
    pattern
   }
  }
 }
}
' -q ' .data.repository.branchProtectionRules.nodes.[] | select(.pattern=="'$DEFAULT_BRANCH'") | .id ')

gh api graphql -f query='
mutation($branchProtectionRuleId:ID!) {
 updateBranchProtectionRule(input: {
  branchProtectionRuleId: $branchProtectionRuleId
  requiresApprovingReviews: true
  requiredApprovingReviewCount: 1
  requiresCodeOwnerReviews: true
  isAdminEnforced: false
 }) { clientMutationId }
}
' -f branchProtectionRuleId="$branchProtectionRuleId" >$DEV_NULL
```

こちらでは、`gh api graphql`を利用しています（Graphql の勉強も兼ねています。ref. [ここさえ抑えれば GitHub API v4 がわかる! GraphQL 入門](https://zenn.dev/hsaki/articles/github-graphql)）。query の指定内容については、ドキュメント（ref. [branchProtectionRules](https://docs.github.com/ja/graphql/reference/objects), [updateBranchProtectionRule](https://docs.github.com/ja/graphql/reference/mutations#updatebranchprotectionrule)）を確認しつつ、トライアンドエラーで何とか動くようになりました。

`>$DEV_NULL`しているのは、`gh api`の出力結果を表示させないためです。Terminal が操作モード(?)になってしまい処理が停止してしまいます。

これを実行すると画像のような設定ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343913/68e960d2-31d7-813b-4868-254fe08b34f8.png)
最終的には下記のコマンドでシュッと登録ができるようになっています！！

```sh
# レポジトリ操作
make exec_query REPO_NAME=repository_name

# 全てのレポジトリ操作
make exec_all_queries
```

今後としては、さらに発展して Github Actions でスクリプトの実行を自動化できると便利ですね。ちなみに、Github Actions の権限設定も[コチラ](https://github.com/shoot16625/github_manager/blob/6dd49c1f41926d82305bf8279c4057b0118313c1/repository/s3_search/main.sh#L66-L72)でコード化できました。※GraphQL は非対応でした。

## おわりに

実際に使ってみると思った以上に体験がよく、「毎回同じ設定をせずに済む」「Description の書き忘れ防止になる」と喜びの声が上がっています（自称）。レポジトリ数が多くなってくると、さらに体験はよいのではないでしょうか？
ぜひ、ご興味がありましたら試してみてください！！

P.S. GraphQL の勉強ができてよかったです！！