もう手動でバージョンを指定しない!git describeでバージョン文字列生成を自動化

Git tutorial - IT technology blog
Git tutorial - IT technology blog

バージョン命名の話:リリースごとの「あの作業」をなくそう

キャリアの初期、リリースごとにチームが混乱するプロジェクトに参加していました。「今回のビルド、バージョンいくつだっけ?」という具合です。当時は完全に「人力」のプロセスで、設定ファイルを開き、手動で 1.0.1 から 1.0.2 に書き換えてからビルドを始めていました。

ミスは避けられませんでした。ある時、同僚がファイルを更新し忘れたまま Git タグv1.1.0 として付与してしまいました。その結果、実行ファイルの表示と Git ラベルが一致しなくなりました。顧客が v1.1.0 でバグを報告した際、そのタグのコードを必死に調べましたが、実際に動いていたのは2週間前のコードだったのです。教訓:Git タグとコード内のバージョン情報を切り離すのは、致命的な間違いです。

なぜ手動のラベル付けは失敗しやすいのか?

手動入力の最大の問題は、ソースコードと実態の乖離です。現代の開発フローでは、コードは常に変化しています:

  • タグ付きコミット(正式リリース)。
  • タグ間のコミット(開発中、不安定)。
  • ローカルの未コミット修正(未コミット)。

package.json のようなファイルで 1.2.0 という静的な数字を使うのは非常にリスクが高いです。手動で照合しない限り、そのビルドが Git 履歴のどこにあるか正確に把握できません。特に DevOps 環境で Jenkins や GitHub Actions による自動ビルドを行う場合、コンピュータが Git 履歴を読み取り、論理的にバージョンを命名する仕組みが必要です。

手動から自動化へ:現実的な解決策を求めて

多くのチームは Commit Hash (例: 7a3b1c2) をバージョンとして採用します。各コミットは一意なので正確ですが、人間には優しくありません。ランダムな文字列を見ても、それが新しいのか古いのか、直近のリリースからどれくらい離れているのか分かりません。

ここで git describe が「秘密兵器」となります。無機質な数字の代わりに、直近のタグ、そこからのコミット数、現在のコミットハッシュを組み合わせます。その結果、読みやすく、かつ絶対的に正確な文字列が得られます。

git describeの極意:コミット履歴をバージョン文字列に変える

あなたのリポジトリでこのコマンドを試してみてください:

bash
git describe

もしタグを付与したコミット(例:v1.0.0)にいるなら、Git はそのまま v1.0.0 を返します。面白くなるのは、そのタグの後に新しいコミットを作成した時です。

git describe の構造を解読する

例えば、v1.0.0-5-g7a3b1c2 という結果が得られたとしましょう。その意味を分解してみます:

  • v1.0.0: Git が履歴を遡って見つけた直近のタグ名。
  • 5: タグ v1.0.0 以降に作成された新しいコミットの数。
  • g7a3b1c2: 現在のコミットの短縮ハッシュ(g は Git の略)。

これを見れば一目瞭然です。「このビルドは v1.0.0 をベースに、5つの変更が加わっており、コミットハッシュは 7a3b1c2 である」。非常に透明性が高いです!

最も実用的なオプション

最大の効果を得るために、私は通常以下のパラメータを組み合わせています:

bash
git describe --tags --always --dirty

各オプションの解説:

  • --tags: デフォルトでは git describe は「注釈付きタグ (annotated tags)」のみを探します。このオプションで「軽量タグ (lightweight tags)」もスキャン対象になります。
  • --always: リポジトリにタグが一つもない場合、エラーを出す代わりにコミットハッシュを返します。これによりビルドスクリプトが突然停止するのを防げます。
  • --dirty: 私のお気に入りです。ローカルのコードが未コミットの場合、末尾に -dirty を付与します。これは、未整理のコードをステージングや本番環境に持ち込まないための保護メカニズムです。

実際のCI/CDプロセスへの適用

以前の Python プロジェクトでは、これを Makefile に組み込んで完全に自動化しました。手動でバージョンを修正する代わりに、スクリプトが Git から情報を取得してビルドに注入します。これにより、リリースごとの確認作業を 15〜20 分ほど短縮できました。

例えば、_version.py ファイルを自動生成する簡単な例です:

bash
# Gitからバージョン文字列を取得
VERSION=$(git describe --tags --always --dirty)

# プロジェクトのバージョンファイルに書き込む
echo "__version__ = '$VERSION'" > src/myapp/_version.py

Docker イメージをビルドする際も、この文字列をタグとして使用します:

bash
IMAGE_TAG=$(git describe --tags --always)
docker build -t myapp:$IMAGE_TAG .

導入以来、バージョンの不一致は完全に解消されました。バグが発生した際は、アプリのバージョンを確認し、そのハッシュ値で git checkout するだけで、即座に不具合を再現できます。

実戦経験からのちょっとした注意点

非常に強力なツールですが、git describe を円滑に運用するために覚えておくべきことがあります:

  1. こまめにタグを付ける: セマンティックバージョニング (v1.0.0, v1.1.0…) を遵守しましょう。タグがないと、このコマンドはハッシュ値を返すだけになり、本来の利便性が失われます。
  2. マージに注意: 古いブランチをメインブランチにマージする際、Git がより近いコミットを持つ古いブランチのタグを誤って選択することがあります。表示がおかしい場合はコミットグラフを確認してください。
  3. 履歴を完全にフェッチする: GitHub Actions などのシステムは通常、最新のコミットのみを取得します (shallow clone)。タグを正しく取得するために fetch-depth: 0 を設定する必要があります。そうしないと、エラーになったり誤動作したりします。

git describe の導入はわずか 15 分の設定で済みますが、そのメリットは絶大です。もしあなたのチームがまだ手動でバージョンを修正しているなら、今すぐ変えてみてください。DevOps エンジニアの仲間たちは、きっと心の中であなたに感謝するはずです!

Share: