PRを作ったのにdescriptionを書き忘れる、あるいは「後で書こう」と思ったまま放置してしまうことがよくあります。
なので「書け」と自分に言い聞かせるのではなく、git pushしたときに機械的に通知が出るようにしてみました。
Web打刻の打刻忘れをタスクスケジューラで防いだときと同じ発想で、人の記憶に頼らず、仕組みに怒ってもらいます。
やること
gitのpre-pushフックを使います。pushの直前に走るスクリプトで、次の3パターンに分けて動きます。
| 状況 | 動き |
|---|---|
| そのブランチのPRがまだ無い | 「PRを作ったらdescriptionを書いてね」と通知してpushは通す |
| PRはあるがdescriptionが空 | 通知してpushを止める(環境変数で回避可) |
| PRがありdescriptionも書いてある | 何もしない |
通知はWindowsのトースト通知(右下に出るやつ)です。追加モジュールは要りません。
macOSとLinuxも一応osascriptとnotify-sendで分岐していますが、僕はWindowsでしか試していません。
準備するもの
- Git for Windows(Git Bashが入っていればOK)
- GitHub CLI(
gh)。PRの本文を取るのに使います。gh auth loginまで済ませておいてください - Windows PowerShell 5.1(Windows 11なら最初から入っています)
ghが無い環境でも動くようにしてあって、その場合は本文の確認はせず通知だけ出します。
手順の流れ
1. フック用のフォルダを作る
リポジトリ直下に.githooksというフォルダを作り、その中にスクリプトを2つ置きます。
your-repo/
.githooks/
pre-push ← gitが呼ぶ本体(シェルスクリプト)
notify.ps1 ← トースト通知を出すだけのPowerShell
.git/hooks/に直接置かない理由は後述します。
2. notify.ps1 を作る
トースト通知を出すだけのスクリプトです。BurntToastのようなモジュールを入れなくても、Windowsに元からあるWinRTのAPIをPowerShellから呼べます。
# Windows のトースト通知を出すだけのスクリプト(追加モジュール不要・Windows PowerShell 5.1 で動作)
param(
[string]$Title = "PR の description を書いてください",
[string]$Body = "push しました。PR の description に「見てほしいところ」を書きましょう。"
)
$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]
# 通知の送り元として PowerShell 自身の AppId を使う(スタートメニューに登録済みのものなら何でもよい)
$appId = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
$t = [System.Security.SecurityElement]::Escape($Title)
$b = [System.Security.SecurityElement]::Escape($Body)
$xml = "<toast><visual><binding template='ToastGeneric'><text>$t</text><text>$b</text></binding></visual></toast>"
$doc = New-Object Windows.Data.Xml.Dom.XmlDocument
$doc.LoadXml($xml)
$toast = New-Object Windows.UI.Notifications.ToastNotification $doc
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId).Show($toast)
ポイント
- ファイルはUTF-8(BOM付き)で保存してください。 Windows PowerShell 5.1はBOMが無いとANSIとして読むので、日本語が化けてパースエラーになります。僕は最初ここでハマりました
$appIdはトーストの「送り元」です。スタートメニューに登録されているアプリのIDなら何でもよく、PowerShellのものを使うのが一番手軽です- 単体で試すには次のように叩きます
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\.githooks\notify.ps1 -Title "テスト" -Body "通知が出れば成功"
(スクリーンショット: 右下に出たトースト通知)
3. pre-push を作る
本体です。gh pr viewでそのブランチのPRを探し、本文が空かどうかを見ています。
#!/bin/sh
# pre-push: push する前に PR の description を書いたか確認する
# - PR がまだ無い → 「push 後に PR を作って description を書いてね」と通知して push
# - PR があるが description が空 → 通知して push を止める(SKIP_PR_DESC=1 で回避)
# - PR があり description もある → 何もしない
HOOK_DIR=$(cd "$(dirname "$0")" && pwd)
BRANCH=$(git rev-parse --abbrev-ref HEAD)
notify() {
# $1=タイトル $2=本文
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*)
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$HOOK_DIR/notify.ps1" -Title "$1" -Body "$2" ;;
Darwin)
osascript -e "display notification \"$2\" with title \"$1\"" ;;
*)
command -v notify-send >/dev/null 2>&1 && notify-send "$1" "$2" ;;
esac
printf '\n[pre-push] %s\n%s\n\n' "$1" "$2" >&2
}
# main への直接 push など、PR を作らないブランチは対象外
case "$BRANCH" in
main|master) exit 0 ;;
esac
# gh が無い環境では、確認はせず通知だけ出す
if ! command -v gh >/dev/null 2>&1; then
notify "PR の description を書いてください" "ブランチ $BRANCH を push します。PR を作ったら「見てほしいところ」を書きましょう。"
exit 0
fi
# 現在のブランチに紐づく PR の番号を取る(PR が無い/GitHub 以外の remote なら失敗する)
PR_NUMBER=$(gh pr view "$BRANCH" --json number --jq .number 2>/dev/null)
if [ -z "$PR_NUMBER" ]; then
notify "PR の description を書いてください" "ブランチ $BRANCH を push します。PR を作ったら「見てほしいところ」を書きましょう。"
exit 0
fi
# PR の本文(空白だけなら空とみなす)
PR_BODY=$(gh pr view "$BRANCH" --json body --jq .body 2>/dev/null | tr -d '[:space:]')
if [ -z "$PR_BODY" ]; then
notify "PR #$PR_NUMBER の description が空です" "「見てほしいところ」を書いてから push してください。(SKIP_PR_DESC=1 で回避できます)"
[ "$SKIP_PR_DESC" = "1" ] && exit 0
exit 1
fi
exit 0
ポイント
gh pr view <ブランチ名> --json body --jq .bodyで、そのブランチに紐づくPRの本文だけが取れます。ghにはjqが内蔵されているので別途インストールは不要です- 本文を
tr -d '[:space:]'で潰してから空判定しているので、改行や空白だけのPRも「空」扱いになります SKIP_PR_DESC=1 git pushとすれば止めずに通せます。hotfixなどで急いでいるときの逃げ道ですmain/masterはPRを作らないので最初に抜けています。運用に合わせてブランチ名は変えてください- Git Bashで実行されるので、Windowsでもシェルスクリプトのままで動きます
4. gitにフォルダの場所を教える
chmod +x .githooks/pre-push git config core.hooksPath .githooks
これで.git/hooks/ではなく.githooks/のフックが使われるようになります。
.git/hooks/はリポジトリに含まれないので、直接置くと自分のPCでしか動きません。.githooks/に置いてコミットしておけば、チームの誰がcloneしても上のコマンド1行で同じフックが効きます。せっかく作った仕組みは共通化しておきたいので、僕はこちらにしています。
5. 動作確認
featureブランチで適当にコミットしてpushしてみます。
git checkout -b feature/test-hook echo test >> README.md git commit -am "test: hook" git push -u origin feature/test-hook
まだPRが無いので、通知が出たうえでpushは通ります。
[pre-push] PR の description を書いてください ブランチ feature/test-hook を push します。PR を作ったら「見てほしいところ」を書きましょう。 * [new branch] feature/test-hook -> feature/test-hook
(スクリーンショット: pushと同時に出たトースト通知)
次にGitHubでdescriptionを空のままPRを作り、もう一度コミットしてpushします。
[pre-push] PR #12 の description が空です 「見てほしいところ」を書いてから push してください。(SKIP_PR_DESC=1 で回避できます) error: failed to push some refs to 'github.com:xxx/your-repo.git'
止まりました。PRのdescriptionを書いてからpushし直すと、今度は何も言われずに通ります。
descriptionに何を書くか
通知を出したところで、書く中身が決まっていないと結局手が止まります。僕は次の3つだけ書くことにしています。
## 見てほしいところ - (レビュアーに特に見てほしい箇所。ロジックの分岐、設定値、迷った選択) ## やったこと - (1〜3行) ## 確認したこと - (動かして確認した内容。していないならしていないと書く)
「見てほしいところ」が一番上です。レビューする側は、まずここを読んで、あればdiffのその部分から見ます。
自分がレビューされるときも、これがあるだけで「なぜこの変更?」の往復が減りました。
.github/pull_request_template.mdにこの雛形を置いておくと、PR作成時に自動で本文に入ります。ただしテンプレートが入っただけで満足して中身を書かない、というのも起きるので、上のフックとセットで使うのがよいと思います。
まとめ
pre-pushフックでPRのdescriptionが空なら通知してpushを止める、というだけの仕組みです。
「忘れないようにする」を意志でやるのは無理だと打刻で学んだので、今回も仕組みに任せました。
descriptionを書き忘れがちな皆様の助けになれば幸いです。
