git hooksを使用して、git commit前に何がしかの処理を実行する

`git commit`前に諸々の処理を実行するのは、テンポが悪くなるため、実際にやるかどうかは要検討かと思います。
その上で備忘録として記事を残します。

### 最終的にやりたいこと
`git commit`実行前にfastlaneでテストを実行する
→[git hooksをトリガにしてfastlaneからユニットテストを実行する](https://qiita.com/wtrmiya/items/5adc97ca1c3dd1c7d311)

### この記事で記載していること
`git commit`実行前に何がしかの処理を実行する

### 前提
- ターミナルから`git commit`を実行してコミットする

### 本題

https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF
を参照すると、

```
フックスクリプトを有効にするには、Gitディレクトリの `hooks` サブディレクトリに、実行可能なファイルを適切な名前（拡張子は使えません）で配置すれば、以降そのファイルが呼び出されます。 ここでは重要なフックファイル名をいくつか取り上げます。
```

と記載されている。

「Gitディレクトリの `hooks` サブディレクトリ」とやらの内部を見てみる。

```shell
$ cat ./README.sample 
#!/bin/sh
#
# Place appropriately named executable hook scripts into this directory
# to intercept various actions that git takes.  See `git help hooks` for
# more information.
```

`git help hooks`で色々情報が見られるとのこと。

`DESCRIPTION`の内容を整理する

```
Hooks are programs you can place in a hooks directory to trigger actions at certain points in git’s execution. Hooks that don’t have the executable bit set are ignored.

       By default the hooks directory is $GIT_DIR/hooks, but that can be changed via the core.hooksPath configuration variable (see git-config(1)).

       Before Git invokes a hook, it changes its working directory to either $GIT_DIR in a bare repository or the root of the working tree in a non-bare repository. An exception are hooks triggered during a push (pre-receive, update, post-receive, post-update, push-to-checkout)
       which are always executed in $GIT_DIR.

       Hooks can get their arguments via the environment, command-line arguments, and stdin. See the documentation for each hook below for details.

       git init may copy hooks to the new repository, depending on its configuration. See the "TEMPLATE DIRECTORY" section in git-init(1) for details. When the rest of this document refers to "default hooks" it’s talking about the default template shipped with Git.

       The currently supported hooks are described below.
```

DeepLによる日本語訳

```
フックとは、hooks ディレクトリに置いて、git の実行中の特定の時点でアクションをトリガーするプログラムのことです。実行可能ビットがセットされていないフックは無視されます。

デフォルトではフックディレクトリは $GIT_DIR/hooks ですが、これは設定変数 core.hooksPath で変更することができます（git-config(1) を参照）。

Git がフックを起動する前に、ベアリポジトリの場合は $GIT_DIR に、そうでない場合は作業ツリーのルートに作業ディレクトリを変更します。例外は、プッシュ（pre-receive、update、post-receive、post-update、push-to-checkout）中に実行されるフックです。
これらは常に $GIT_DIR で実行されます。

フックは環境、コマンドライン引数、標準入力を介して引数を取得することができます。詳細は以下の各フックのドキュメントを参照ください。

git init は、設定によってはフックを新しいリポジトリにコピーすることがあります。詳細は git-init(1) の「TEMPLATE DIRECTORY」セクションを参照してください。この文書で「デフォルトのフック」と呼ぶのは、Git に同梱されているデフォルトのテンプレートのことです。

現在サポートされているフックを以下に説明します。
```

対応しているhooksはたくさんある。

今やりたいのはGitでのcommit時に何がしかの処理を実行したい、ということ。
関係ありそうなhookは以下か。

```swift
pre-commit

This hook is invoked by git-commit(1), and can be bypassed with the --no-verify option. It takes no parameters, and is invoked before obtaining the proposed commit log message and making a commit. Exiting with a non-zero status from this script causes the git commit
command to abort before creating a commit.

The default pre-commit hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when such a line is found.

All the git commit hooks are invoked with the environment variable GIT_EDITOR=: if the command will not bring up an editor to modify the commit message.

The default pre-commit hook, when enabled—and with the hooks.allownonascii config option unset or set to false—prevents the use of non-ASCII filenames.
```

```
pre-commit

このフックは git-commit(1) によって起動され、--no-verify オプションで回避することができます。これはパラメータをとらず、コミットログメッセージを取得してコミットを行う前に実行されます。このスクリプトをゼロ以外のステータスで終了させると、git commit
コマンドはコミットを行う前に中止します。

デフォルトの pre-commit フックを有効にすると、末尾に空白文字を含む行を検出し、そのような行が見つかった場合にコミットを中断します。

すべての git commit フックは、コマンドがコミットメッセージを変更するためのエディタを起動しない場合は、環境変数 GIT_EDITOR=: を指定して起動します。

デフォルトのコミット前フックでは、hooks.allownonascii 設定オプションが有効になっていないか false に設定されている場合に、ASCII 以外のファイル名の使用を禁止します。
```

今時点での情報をまとめると、
- `.git/hooks/`ディレクトリ内に指定されたhooks名と同名の実行可能ファイルを置く。
- やりたいことはコミット前に何がしかの処理を実行すること。: `pre-commit`というファイルを`.git/hooks/`に置くべし。
- `pre-commit`には全員への実行許可を設定すること。

以下内容のファイルを`.git/hooks/`に置く。
```shell
#!/bin/bash
echo "Hello"
```
- 自分はMacではbashを使用しているため、上記の記載になっている。

実行権限を一旦確認する

```
$ ls -al
total 16
drwxr-xr-x   4 myname  staff  128  2  6 06:39 .
drwxr-xr-x  12 myname  staff  384  2  6 06:13 ..
-rwxr-xr-x   1 myname  staff  177  2  6 04:52 README.sample
-rw-r--r--   1 myname  staff   25  2  6 06:39 pre-commit
```

`pre-commit`の実行権限がついていない(パーミッションが`-rw-r--r-x`になっていない。)。

実行権限を`pre-commitに対して付加する`

```swift
$ chmod +x pre-commit
```

確認してみる。
```swift
$ ls -al
total 16
drwxr-xr-x   4 myname  staff  128  2  6 06:39 .
drwxr-xr-x  12 myname  staff  384  2  6 06:13 ..
-rwxr-xr-x   1 myname  staff  177  2  6 04:52 README.sample
-rwxr-xr-x   1 myname  staff   25  2  6 06:39 pre-commit
```

これで良さそう。

ワーキングツリーに戻って、コミットを実行してみる。

```swift
$ git commit -m "commit for pre-commit hook test"
Hello
[main b9feaec] commit for pre-commit hook test
 1 file changed, 1 insertion(+)
$
```

`Hello`という文字列が表示されている。動いたっぽい。

次は失敗させられるか確認する。
`Exiting with a non-zero status from this script causes the git commit`とのことなので、終了コードで1を返却してみる。

```shell
#!/bin/bash
echo "Hello"
exit 1
```

実行してみる。

```swift
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   SwfitUIPractice20240206/ContentView.swift

$ git log --oneline
b9feaec (HEAD -> main) commit for pre-commit hook test
5d95426 test commit for git hook
92bca6d Initial Commit
$ git commit -m "commit for pre-commit hook fail test"
Hello
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   SwfitUIPractice20240206/ContentView.swift

$ git log --oneline
b9feaec (HEAD -> main) commit for pre-commit hook test
5d95426 test commit for git hook
92bca6d Initial Commit
```

コミットログが増えていないので、コミットは実行されていないようだ。