[インデックス 14415] ファイルの概要
このコミットは、Go言語のプロジェクトにおいて、Gitのプリコミットフックとしてgofmt
を導入するスクリプトを追加するものです。これにより、コミットされるGoのソースコードが自動的にgofmt
によってフォーマットされているかを確認し、されていない場合にはコミットを拒否することで、コードベース全体の一貫したフォーマットを強制します。
コミット
commit 1395d3d9bfbba924b2bd9638860380fd380c1351
Author: Andrew Gerrand <adg@golang.org>
Date: Thu Nov 15 19:58:49 2012 +0100
misc/git: add gofmt git pre-commit hook
R=golang-dev, bradfitz, ftrvxmtrx, franciscossouza, r, minux.ma
CC=golang-dev
https://golang.org/cl/6843044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1395d3d9bfbba924b2bd9638860380fd380c1351
元コミット内容
misc/git: add gofmt git pre-commit hook
R=golang-dev, bradfitz, ftrvxmtrx, franciscossouza, r, minux.ma
CC=golang-dev
https://golang.org/cl/6843044
変更の背景
Go言語のプロジェクトでは、コードの可読性と一貫性を保つために、公式のフォーマッタであるgofmt
の使用が強く推奨されています。しかし、開発者が手動でgofmt
を実行し忘れると、コードベースにフォーマットの不一致が生じ、コードレビューの際に余計な指摘が増えたり、開発環境によってコードの見た目が異なったりする問題が発生します。
このコミットの背景には、このようなフォーマットの不一致を防ぎ、開発プロセスをよりスムーズにするという目的があります。Gitのプリコミットフックを利用することで、開発者がコミットを行う前に自動的にgofmt
によるチェックを強制し、フォーマットされていないコードがリポジトリに混入するのを防ぐことができます。これにより、コードベース全体の品質と一貫性が向上し、開発チーム全体の生産性向上に寄与します。
前提知識の解説
Git Hooks (Git フック)
Gitフックは、Gitの特定のイベント(コミット、プッシュ、リベースなど)が発生した際に自動的に実行されるスクリプトです。これらは.git/hooks/
ディレクトリ内に配置され、実行可能ファイルとして設定されます。フックは、コードの品質チェック、テストの実行、デプロイの自動化など、様々な自動化タスクに利用されます。
- プリコミットフック (
pre-commit
): コミットメッセージが作成され、コミットが完了する前に実行されます。このフックが非ゼロの終了コードを返すと、コミットは中断されます。今回のコミットで追加されるのはこのタイプのフックです。
gofmt
gofmt
は、Go言語のソースコードを標準的なスタイルに自動的にフォーマットするツールです。Go言語のツールチェインに標準で含まれており、Goコミュニティではgofmt
によってフォーマットされたコードが「Goらしい」コードとして広く認識されています。gofmt
を使用することで、開発者間のコードスタイルの議論をなくし、コードレビューの焦点をロジックや設計に移すことができます。
gofmt -l <files>
: 指定されたファイルの中で、gofmt
によってフォーマットされていないファイルのリストを出力します。gofmt -w <files>
: 指定されたファイルをgofmt
によってインプレースでフォーマットし、変更をファイルに書き込みます。
シェルスクリプトの基本
このプリコミットフックはシェルスクリプトで書かれています。
#!/bin/sh
: シバン(Shebang)。このスクリプトが/bin/sh
で実行されることを指定します。git diff --cached --name-only --diff-filter=ACM
:git diff --cached
: ステージングエリア(インデックス)とHEAD(最後のコミット)の間の差分を表示します。これにより、コミットされようとしている変更のみを対象とします。--name-only
: 変更されたファイルの名前のみを出力します。--diff-filter=ACM
: 追加(Added)、コピー(Copied)、変更(Modified)されたファイルのみを対象とします。削除されたファイルは対象外です。
grep '.go$'
: パイプで渡された入力から、.go
で終わる行(Goのソースファイル)をフィルタリングします。[ -z "$variable" ]
: シェルスクリプトの条件式で、$variable
が空文字列であるかどうかをチェックします。空であれば真(true)となります。exit 0
: スクリプトが正常終了したことを示します。exit 1
: スクリプトがエラーで終了したことを示します。Gitフックの場合、非ゼロの終了コードはコミットの中断を意味します。echo >&2 "message"
: 標準エラー出力(stderr)にメッセージを出力します。これにより、ユーザーはコミットが失敗した理由をコンソールで確認できます。for fn in $list; do ... done
:list
内の各要素に対してループ処理を実行します。
技術的詳細
このプリコミットフックは、以下のステップで動作します。
-
ステージングされたGoファイルの特定:
git diff --cached --name-only --diff-filter=ACM | grep '.go$'
コマンドを使用して、現在ステージングされている(つまり、次のコミットに含まれる予定の)ファイルの中から、拡張子が.go
であるもの(Goのソースファイル)を抽出します。これらのファイル名はgofiles
変数に格納されます。 -
Goファイルが存在しない場合の早期終了:
[ -z "$gofiles" ] && exit 0
は、もしgofiles
変数が空(つまり、ステージングされたGoファイルがない)であれば、スクリプトを正常終了させます。これにより、無関係なコミット(例えば、ドキュメントの変更のみのコミット)ではgofmt
チェックがスキップされます。 -
gofmt
によるフォーマットチェック:gofmt -l $gofiles
コマンドを実行し、gofiles
に含まれるすべてのGoファイルがgofmt
の規約に従ってフォーマットされているかをチェックします。フォーマットされていないファイルがあれば、そのファイル名がunformatted
変数に格納されます。 -
フォーマットされていないファイルがない場合の早期終了:
[ -z "$unformatted" ] && exit 0
は、もしunformatted
変数が空(つまり、すべてのGoファイルが正しくフォーマットされている)であれば、スクリプトを正常終了させます。この場合、コミットは続行されます。 -
フォーマットされていないファイルがある場合の処理: もし
unformatted
変数にファイル名が含まれている場合(つまり、フォーマットされていないGoファイルがある場合)、スクリプトは以下の処理を行います。- 標準エラー出力に「Go files must be formatted with gofmt. Please run:」(Goファイルはgofmtでフォーマットされている必要があります。以下を実行してください:)というメッセージを出力します。
unformatted
リスト内の各ファイル名に対してループ処理を行い、それぞれのファイルについてgofmt -w $PWD/$fn
というコマンド例を標準エラー出力に表示します。$PWD
は現在の作業ディレクトリの絶対パスを表し、ユーザーがコマンドをコピー&ペーストしてすぐに実行できるように配慮されています。- 最後に
exit 1
を実行し、スクリプトをエラー終了させます。これにより、Gitはコミットを中断し、ユーザーにフォーマットの問題があることを通知します。
このフックは、ファイル名にスペースが含まれる場合には正しく動作しないという既知の制限があります。これは、シェルスクリプトがスペースを区切り文字として解釈するためです。Goのファイル名にスペースが含まれることは稀であるため、実用上大きな問題にはならないと考えられます。
コアとなるコードの変更箇所
misc/git/pre-commit
という新しいファイルが追加されています。
--- /dev/null
+++ b/misc/git/pre-commit
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright 2012 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# git gofmt pre-commit hook
+#
+# To use, store as .git/hooks/pre-commit inside your repository and make sure
+# it has execute permissions.
+#
+# This script does not handle file names that contain spaces.
+
+gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$')
+[ -z "$gofiles" ] && exit 0
+
+unformatted=$(gofmt -l $gofiles)
+[ -z "$unformatted" ] && exit 0
+
+# Some files are not gofmt'd. Print message and fail.
+
+echo >&2 "Go files must be formatted with gofmt. Please run:"
+for fn in $unformatted; do
+ echo >&2 " gofmt -w $PWD/$fn"
+done
+
+exit 1
コアとなるコードの解説
-
1行目:
#!/bin/sh
このスクリプトがBourne Shell (sh
) で実行されることを指定するシバンです。 -
2-5行目: コピーライトとライセンス情報 Goプロジェクトの標準的なコピーライトとBSDスタイルのライセンスに関するコメントです。
-
7-12行目: スクリプトの目的と使用方法、既知の制限に関するコメント このスクリプトが
gofmt
のGitプリコミットフックであること、使用するには.git/hooks/pre-commit
として保存し、実行権限を付与する必要があること、そしてファイル名にスペースが含まれる場合には対応していないことが説明されています。 -
14行目:
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$')
ステージングエリアにある変更されたGoファイルのリストを取得し、gofiles
変数に格納します。git diff --cached
: ステージングされた変更を対象とします。--name-only
: ファイル名のみを出力します。--diff-filter=ACM
: 追加、コピー、変更されたファイルのみを対象とします。grep '.go$'
:.go
で終わるファイル名(Goソースファイル)のみを抽出します。
-
15行目:
[ -z "$gofiles" ] && exit 0
もしgofiles
が空(ステージングされたGoファイルがない)であれば、スクリプトを正常終了(exit 0
)させ、コミットを続行します。 -
17行目:
unformatted=$(gofmt -l $gofiles)
gofiles
に含まれるGoファイルに対してgofmt -l
を実行し、フォーマットされていないファイルのリストをunformatted
変数に格納します。 -
18行目:
[ -z "$unformatted" ] && exit 0
もしunformatted
が空(すべてのGoファイルが正しくフォーマットされている)であれば、スクリプトを正常終了(exit 0
)させ、コミットを続行します。 -
20行目:
# Some files are not gofmt'd. Print message and fail.
フォーマットされていないファイルがある場合の処理に関するコメントです。 -
22行目:
echo >&2 "Go files must be formatted with gofmt. Please run:"
標準エラー出力に、Goファイルがgofmt
でフォーマットされている必要がある旨のメッセージを出力します。>&2
は標準エラー出力へのリダイレクトです。 -
23-25行目:
for fn in $unformatted; do ... done
フォーマットされていない各ファイル名(fn
)に対してループ処理を行います。echo >&2 " gofmt -w $PWD/$fn"
: 各フォーマットされていないファイルについて、gofmt -w
コマンドの例を標準エラー出力に表示します。$PWD
は現在の作業ディレクトリの絶対パスです。
-
27行目:
exit 1
スクリプトをエラー終了(exit 1
)させ、Gitのコミット処理を中断します。これにより、フォーマットされていないコードがコミットされるのを防ぎます。
関連リンク
- Git Hooks: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
- gofmt: https://go.dev/blog/gofmt
参考にした情報源リンク
- Git公式ドキュメント
- Go公式ドキュメント
- Stack Overflowなどの技術Q&Aサイト(
gofmt
とGitフックに関する一般的な情報収集のため) - Go言語のオープンソースプロジェクトにおける
gofmt
の利用慣行 - コミットメッセージと変更されたファイルの内容
git diff
コマンドのmanページgofmt
コマンドのmanページ- シェルスクリプトの構文に関する一般的な知識