LoginSignup
26
11

More than 3 years have passed since last update.

Git Hooksでうっかりミスを防ぎたい

Last updated at Posted at 2020-12-03

この記事は、富士通クラウドテクノロジーズ Advent Calendar 2020 4日目の記事です。
3日目は @yaaamaaaguuu さんの VMware製品を気軽に検証するためのtips でした。
自宅でマイvCenterを作って色々試せるのはすごく勉強になりそうです。

はじめに

こんにちは。FJCT新人エンジニアの @Syuparn です。

社会人になって、はじめてチームでの開発を経験しました。
コミットは他の人が読んでも分かりやすいようきれいにすることが大切だと学びました。

...のですが、うっかり変なコミットをマージリクエスト(プルリクエスト)に出してしまうことがあります。理想は遥か遠い...

そこで本記事では、push前にうっかりミスに気付くためにGit Hooksで設定した内容を紹介します。

紹介した設定ファイルは GitHub - Syuparn/git-hooks-example にもアップロードしています。

Git Hooks

Git HooksはGitの標準機能で、commit, push等のアクションの前後に処理を追加(フック)することができます。

Git フック

フックの設定ファイルは各ローカルリポジトリの .git/hooks/ 以下にshell形式で記述します。
このディレクトリにはデフォルトでフックのサンプルが生成されているので、改造しながらフックを自作できます。

$ ls .git/hooks/
applypatch-msg.sample*      pre-applypatch.sample*      pre-push.sample*
commit-msg.sample*          pre-commit.sample*          pre-rebase.sample*
fsmonitor-watchman.sample*  pre-merge-commit.sample*    pre-receive.sample*
post-update.sample*         prepare-commit-msg.sample*  update.sample*

注意!

Git Hooksを有効化するには、設定ファイルに実行権限を与える必要があります。

権限がないと実行できない。。。
hint: The '.git/hooks/commit-msg' hook was ignored because it's not set as executable.
hint: You can disable this warning with `git config advice.ignoredHook false`

$ chmod +x .git/hooks/(設定ファイル) で実行可能にしておきましょう。

Case 1. うっかりファイル生成/テスト忘れ

  • さっきのコミットにタイポがあってCIのテストが落ちた...
  • 自動生成ファイル更新し忘れた...
  • linterかけ忘れてインデントががちゃがちゃだ...

pushする前にローカルで確認、実行したいことを、コミットの度にリマインドしてくれるようにします。

post-commit フックに設定した内容は、毎回のコミットの直後に実行されます。コミットの度にTODOリストが表示されるようにしてみます。

commit.png

毎回リマインドすればプッシュまでに打ち忘れに気づくはず...!
さらに、休み明けに実行したいコマンドを忘れてしまっても安心です
もちろん表示だけでなく、実際にコマンドを実行することも可能です。 1

フックの中身はこのようになっています。

.git/hooks/post-commit
#!/bin/bash

function show_reminder() {
  # 表示したいTODOリスト(ただの文字列です)
  cat <<-EOS
    --------------------------------------------------

    [REMIND] Before pushing, run these commands!
    $ echo "hello, world!"
    $ make
    $ go fmt ./...

    --------------------------------------------------
    EOS
    return 0
}

remind_msg=$( show_reminder )
# エスケープシーケンスを前後に付けて、文字を青くして表示
printf '\033[1;36m%s\033[m\n' "$remind_msg"
exit 0

Case 2. うっかりリポジトリ破壊

  • tabで補完したら git push origin master 😱 2
  • さっきのコミットにファイル入れ忘れたからrebaseしてforce pushして...そのブランチじゃない!(手遅れ)

pre-push を使うことで、特定のブランチへのプッシュを禁止したり警告を出したりすることができます。

master/develop/mainへのプッシュを禁止

pre-pushフックは、異常終了した場合pushを取りやめます。そこで、push先のブランチ名がmaster, develop, mainのいずれかに一致する場合は異常終了するようにします。

main.png

.git/hooks/pre-push
#!/bin/bash

# これらのブランチへのプッシュを禁止する
prohibited_branches="(master|develop|main)"

while read local_ref local_sha remote_ref remote_sha
do
    # プッシュ先ブランチ名が正規表現でマッチしたら...
    if [[ "$remote_ref" =~ ^.*/$prohibited_branches$ ]]; then
        cat <<-EOS
        --------------------------------------------------

        DO NOT PUSH TO $remote_ref DIRECTLY!

        --------------------------------------------------
        EOS

        # 異常終了(プッシュはされない)
        exit 1
    fi
done

# それ以外のブランチはプッシュする
exit 0

force push時に警告を出す

masterへのプッシュは全て禁止すればよいですが、force pushはマージリクエスト(プルリクエスト)の修正に使うことがあるので無くしてしまうのはつらいです。

そこで、代わりに -f したら戻せないけど大丈夫? と目立つ警告を出すようにします。

force.png

デフォルトでキャンセルするようにすれば、エンターを連打しても安心です。

.git/hooks/pre-push
#!/bin/bash

function show_force_alert() {
    cat <<-EOS
    --------------------------------------------------

                  ▄▄▄▄
                 ██▀▀▀
               ███████
                 ██
      █████      ██
                 ██     
                 ▀▀     

    breaks remote branch!
    Do you really want force push? [y/N]
    --------------------------------------------------
    EOS
    return 0
}

# コマンド行全体を取得 (例: "git push -f origin mybranch")
push_command=$(ps -ocommand= -p $PPID)

while read local_ref local_sha remote_ref remote_sha
do
    # コマンドにforceのオプションがついているか正規表現チェック
    if [[ "$push_command" =~ (force|[[:space:]]\-f[[:space:]]) ]]; then
        alert_msg=$( show_force_alert )
        # 警告文を黄色く表示
        printf '\033[33m%s\033[m\n' "$alert_msg"

        # 入力を受け取る。y以外なら異常終了し、プッシュしない
        exec < /dev/tty
        read yn
        if [[ "$yn" =~ ^[yY]$ ]]; then
            exit 0
        fi

        exit 1
    fi

exit 0

正規表現で-fの前後にスペースが入っているのは、入れないと-fが含まれるブランチ名をforce pushと誤検知するためです(例: bug-fix)。

ちなみに、-f のデカ文字はtoiletコマンドで生成しました。デカ文字にしたい文字列とフォントを指定するだけで簡単に作れます。

$ echo "-f" | toilet -f mono12

              ▄▄▄▄  
             ██▀▀▀  
           ███████  
             ██     
  █████      ██     
             ██     
             ▀▀     

参考:

Git hooks を使って push を行うときに確認を出し誤 push を防止する | Developers.IO

git push -f origin master したら Get Wild が流れる pre-hook - YuG1224 blog

Case 3. うっかり違うブランチをプッシュ

  • 危険な操作は制限したから、安心してプッシュできるぞ...あ、そのブランチじゃない!(2度目)

そもそも、間違えるほど沢山のブランチがローカルにあるのが原因です。
特に、他のブランチにマージされたブランチは消しても問題ありません(全てのコミットがマージ先にも記録されているため)。

post-checkout フックで、ブランチ作成時やマスターに戻ってくる際にいらなくなったブランチを掃除するようにします。

checkout.png

.git/hooks/post-checkout
#!/bin/bash

function merged_branches() {
    # マージされたいらないブランチのリスト(ただし、今いるブランチやmaster等は消したらマズいので除外)
    git branch --merged | grep -vE '(^\*|main|master|develop)'
}

# いらないブランチが無ければ何もしない
if [[ $( merged_branches | wc -l ) -eq 0 ]]; then
    exit 0
fi

# ダイアログ表示
cat <<-EOS
--------------------------------------------------
[Cleaner] These branches have already been merged.
EOS

merged_branches

cat <<-EOS
Do you want to delete them? [Y/n]
--------------------------------------------------
EOS

exec < /dev/tty
read yn
if [[ "$yn" =~ ^[nN]$ ]]; then
    # cancel    
    exit 0
fi

# 実際に各ブランチを消す
for branch in $( merged_branches )
do
    git branch -d $branch
done

Case 4. うっかり変なコミットメッセージ

$ git log --oneline
c6f65b8 test; medium test for division inputs # あれ?
95db852 fix: raise error if divisor is zero
575b4a8 add: division button to calculator

commit-msg フックを使えば、コミットメッセージが書式に合わないときはコミットできないようにできます。 これでタイポがあっても安心です。

commit-msg.png

.git/hooks/commit-msg
#!/bin/bash

# $1 にコミットメッセージの一時保存ファイル名が入っているので、catでメッセージを取り出す
commit_msg=$( cat $1 )

# フォーマットに合っていない場合、異常終了しコミット失敗
if [[ ! "$commit_msg" =~ ^.*:[[:space:]].*$ ]]; then
    cat <<-EOS
        --------------------------------------------------

        COMMIT MESSAGE MUST BE
        "word: some explanations..."

        --------------------------------------------------
        EOS
    exit 1
fi

最後に

以上、git pushのうっかりミス対策の紹介でした。
Git Hooksにはまだまだ種類がたくさんあるので、少しずつ活用していけたらと考えています。


  1. 現状は、テストやlinterが自動で走るのは少し重いかなと思いtodoリスト表示だけにしています 

  2. 幸いにも、実行したことはまだありません 

26
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
11