GitHubから指定した期間のPRから自分がコミットしたものを抽出するスクリプトを作ってみた

[SmartHR Advent Calendar 2024](https://qiita.com/advent-calendar/2024/smarthr) シリーズ2の15日の記事です。前の記事はT2mさんの[Plopで簡単にコンポーネントを自動生成する方法](https://zenn.dev/both3lectrodes/articles/5b809177c84776)でした。

## はじめに

12月に入って急激に寒くなったものの、足元のホットカーペットだけでどこまで凌げるかにチャレンジしています。冬でも短パンを履いてく系小学生だったので、寒さにどこまで耐えられるか挑戦したくなる性分のようです。

さて、12月というと一年の総決算として、今年どんな開発していたかふりかえりたくなる人もいるのではないでしょうか？私はなりました。

GitHubの自分がAuthorのPRを見るとしたいところですが、チームで開発をしていると、モブプログラミングをしたり、途中から他の人の作業を引き継いだりで、自分がコミットしているけどPRのAuthorは別の人なんてことも普通にあり、簡単に自分がコミットしたPRを見ることができません。（私が把握してないだけでGitHubにそういう機能があったら教えてください）

ここでタイトル回収、自分がコミットしているPRを抽出するスクリプトを作ってみました。

## v1 `gh pr list`で取得

GitHub謹製のCLIでPRの一覧を取得し、自分のコミットを抽出しようというアプローチです。先に結論を言ってしまうとこれではうまくいきませんでした。

https://github.com/cli/cli#installation

```bash
#!/bin/bash

# ユーザー名とリポジトリを設定
GITHUB_USERNAME="your github username"
REPO="your repository name"

# 抽出する期間の設定
DATE_FROM="2024-07-01" # yyyy-mm-dd形式で抽出開始の日付を指定
DATE_TO=$(date +"%Y-%m-%d") # yyyy-mm-dd形式で抽出開始の日付を指定。デフォルトは今日まで
DATE_RANGE=3 # API制限に引っかかるので指定した日の範囲ごとに取得

# 現在の日付が開始日を超えるまでループ
current_date="$DATE_FROM"
end_date=$(date -v+1d -j -f "%Y-%m-%d" "$DATE_TO" +"%Y-%m-%d")

while [[ "$current_date" < "$end_date" ]]; do
  # echo "Processing date: $current_date"

  # 指定期間でのコミットを含むPRを検索
  gh pr list --limit 40 --state all --repo $REPO --search "updated:$current_date..$current_date" --json number,title,url,updatedAt,commits --jq ".[] | [. | select(.commits[].authors[].login == \"${GITHUB_USERNAME}\") | {number: .number, title: .title, url: .url, updatedAt: .updatedAt}] | unique_by(.number) | select(.[].number > 0) | .[] | {title: .title, url: .url, updatedAt: .updatedAt}"

  # 日付をDATE_RANGE日進める
  current_date=$(date -v+${DATE_RANGE}d -j -f "%Y-%m-%d" "$current_date" +"%Y-%m-%d")
done
```

実行すると以下のようなJSONが出力されます。

```json
{
  "title": "[PRのタイトル]",
  "updatedAt": "2024-12-10T10:22:45Z",
  "url": "https://github.com/[リポジトリ名]/pull/[PR番号]"
}
```

おおうまくいったと思ったのですが、`gh pr list` で取得できるコミットは30件までで、それ以上のコミットがあるPRだと取得できず失敗。

それならばと改良したのが次の方法です。

## v2 `gh pr list`と`gh api graphql`の組み合わせ

GitHub APIにはRESTとGraphQLがあり、GraphQLがあれば欲しいデータを取得できそうです。

https://docs.github.com/ja/graphql

`gh pr list` ではコミットが全部取れなかったので、

- 期間内のPRのリストを取得
- PR番号からGraphQL APIを利用してコミットを取得

という組み合わせでやる方法です。

```bash
#!/bin/bash

# ユーザー名とリポジトリを設定
GIT_USERNAME="your github username" # git config user.name を指定 != github usernameなので注意

REPO_OWNER="your repository orgnization name"
REPO_NAME="your repository name"
REPO="${REPO_OWNER}/${REPO_NAME}"

# 抽出する期間の設定
DEFAULT_DATE=$(date +"%Y-%m-%d")
# 何も指定しなければ今日のPRのみ取得する
DATE_FROM=$DEFAULT_DATE # ex: "2024-07-01"
DATE_TO=$DEFAULT_DATE # ex: "2024-10-31"
DATE_RANGE=1 # API制限に引っかかるので指定した日の範囲ごとにPRを取得

# 除外するPRのタイトルを設定
EXCLUDE_PR_TITLE='(\[release\]|Dependency)'

# 現在の日付が開始日を超えるまでループ
current_date="$DATE_FROM"
end_date=$(date -v+1d -j -f "%Y-%m-%d" "$DATE_TO" +"%Y-%m-%d")

while [[ "$current_date" < "$end_date" ]]; do
  # echo "Processing date: $current_date"

  # 指定期間でのPRを検索
  PR_NUMBERS=$(gh pr list --state all --repo $REPO --search "updated:$current_date..$current_date" --json number,title,url,updatedAt --jq ".[].number")

  for pr_number in $PR_NUMBERS; do
    # echo "Processing PR: $pr_number"

    # PRのコミットを取得
    gh api graphql -f query='
      query($owner: String!, $repo: String!, $pr: Int!, $endCursor: String) {
        repository(owner: $owner, name: $repo) {
          pullRequest(number: $pr) {
            title
            url
            commits(first: 100, after: $endCursor) {
              pageInfo{ hasNextPage, endCursor }
              nodes {
                commit {
                    oid
                    committer {
                      name
                      email
                      date
                    }
                }
              }
            }
          }
        }
      }' -F owner="${REPO_OWNER}" -F repo="${REPO_NAME}" -F pr=$pr_number --paginate --jq "[. | .data.repository.pullRequest | {title: .title, url: .url, committer: .commits.nodes.[].commit.committer.name} | select(.committer == \"${GIT_USERNAME}\") | {title: .title, url: .url}] | unique_by(.url) | select(.[].url > 0) | .[] | .url + \" \" + .title" | uniq | grep -v -E $EXCLUDE_PR_TITLE
  done

  # 日付をDATE_RANGE日進める
  current_date=$(date -v+${DATE_RANGE}d -j -f "%Y-%m-%d" "$current_date" +"%Y-%m-%d")
done
```

一応これで取得はできました:tada:
出力はこんな感じ。

```
https://github.com/[リポジトリ名]/pull/[PR番号] [PRタイトル]
https://github.com/[リポジトリ名]/pull/[PR番号] [PRタイトル]
https://github.com/[リポジトリ名]/pull/[PR番号] [PRタイトル]
```

ただ、めちゃくちゃ遅い、遅すぎる :cry: 
無駄なAPIリクエストが多く、効率が悪すぎてめちゃくちゃ時間がかかります。

v3として、ローカルのgitで自分のコミットからブランチを抽出して効率化をする案を考えているので高速化に挑戦中です。

（以下追記）
## v3 ローカルで自分がコミットしたブランチ名を抽出 + `gh pr list`の組み合わせ

v2は取得できるようになったものの、無駄なAPIリクエストが多く遅かったのでローカルのコミットからブランチを抽出し、そのブランチの情報を取得する方式に変更しました。

```bash
#!/bin/bash

# ユーザー名とリポジトリを設定
COMMITER_NAME="your github username"

REPO_OWNER="your repository organization"
REPO_NAME="your repository name"
REPO="${REPO_OWNER}/${REPO_NAME}"

# 抽出する期間の設定
DEFAULT_DATE=$(date +"%Y-%m-%d")
DATE_FROM="2024-12-01"
DATE_TO=$DEFAULT_DATE
DATE_RANGE=1 # API制限に引っかかるので指定した日の範囲ごとにPRを取得

# 除外するPRのタイトルを設定
EXCLUDE_PR_TITLE='(\[release\]|Dependency)'
# 除外するブランチ名を設定
EXCLUDE_BRANCH='(HEAD|main)'

# 現在の日付が開始日を超えるまでループ
current_date="$DATE_FROM"
end_date=$(date -v+1d -j -f "%Y-%m-%d" "$DATE_TO" +"%Y-%m-%d")

cd [your repository path]

TEMP_FILE=/tmp/$$-get-commit-pr.txt
: > $TEMP_FILE

COMMIT_HASHES=()
PR_NUMBERS=()

while [[ "$current_date" < "$end_date" ]]; do
  # echo "Processing date: $current_date"

  # 指定期間でのPRを検索
  COMMIT_HASHES=$(git --no-pager log -200 --oneline --reverse --committer="$COMMITER_NAME" --after="${current_date} 00:00:00" --before="${current_date} 23:59:59" --pretty=%h)

  BRANCHES=()

  for hash in $COMMIT_HASHES; do
    # echo "Processing commit hash: $hash"
    branch=$(git name-rev --name-only $hash | grep remotes | sed -E 's/^remotes\/origin\///' | sed 's/[~^].*//')
    # echo "Processing branch: $branch"
    if [[ $branch =~ $EXCLUDE_BRANCH ]]; then
      continue
    fi
    echo $branch >> $TEMP_FILE
  done

  # 日付をDATE_RANGE日進める
  current_date=$(date -v+${DATE_RANGE}d -j -f "%Y-%m-%d" "$current_date" +"%Y-%m-%d")
done

COMMIT_BRANCHES=$(cat $TEMP_FILE | sort | uniq)

# echo "COMMIT_BRANCHES: $COMMIT_BRANCHES"

for branch in $COMMIT_BRANCHES; do
  # echo "Processing branch: $branch"
  gh pr list --state all --head $branch --json number,title,url,updatedAt --jq '.[] | "\(.number) \(.title) \(.url) \(.updatedAt)"'
done

rm $TEMP_FILE
```

実行結果はこんな感じ

```
[PR番号] [PRタイトル] [PR URL] [PR更新日時]
[PR番号] [PRタイトル] [PR URL] [PR更新日時]
[PR番号] [PRタイトル] [PR URL] [PR更新日時]
```

現実的な速度で動かすことができました。
ただ、テストが不足しているので正しく抽出できていなかったらごめんなさい:bow:

雑なスクリプトで書いてしまったので、標準入力で条件を受け取るとかリファクタするところはたくさんありそうですが、そこはお許しください :pray: 

自分がコミットしたPRを取得して、一年のふりかえりにご活用いただけたらうれしいです。