Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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内の各要素に対してループ処理を実行します。

技術的詳細

このプリコミットフックは、以下のステップで動作します。

  1. ステージングされたGoファイルの特定: git diff --cached --name-only --diff-filter=ACM | grep '.go$' コマンドを使用して、現在ステージングされている(つまり、次のコミットに含まれる予定の)ファイルの中から、拡張子が.goであるもの(Goのソースファイル)を抽出します。これらのファイル名はgofiles変数に格納されます。

  2. Goファイルが存在しない場合の早期終了: [ -z "$gofiles" ] && exit 0 は、もしgofiles変数が空(つまり、ステージングされたGoファイルがない)であれば、スクリプトを正常終了させます。これにより、無関係なコミット(例えば、ドキュメントの変更のみのコミット)ではgofmtチェックがスキップされます。

  3. gofmtによるフォーマットチェック: gofmt -l $gofiles コマンドを実行し、gofilesに含まれるすべてのGoファイルがgofmtの規約に従ってフォーマットされているかをチェックします。フォーマットされていないファイルがあれば、そのファイル名がunformatted変数に格納されます。

  4. フォーマットされていないファイルがない場合の早期終了: [ -z "$unformatted" ] && exit 0 は、もしunformatted変数が空(つまり、すべてのGoファイルが正しくフォーマットされている)であれば、スクリプトを正常終了させます。この場合、コミットは続行されます。

  5. フォーマットされていないファイルがある場合の処理: もし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公式ドキュメント
  • Go公式ドキュメント
  • Stack Overflowなどの技術Q&Aサイト(gofmtとGitフックに関する一般的な情報収集のため)
  • Go言語のオープンソースプロジェクトにおけるgofmtの利用慣行
  • コミットメッセージと変更されたファイルの内容
  • git diffコマンドのmanページ
  • gofmtコマンドのmanページ
  • シェルスクリプトの構文に関する一般的な知識