DiffDaily

Deep & Concise - OSS変更の定点観測

[Rails] Rails.app.revisionがアプリケーションルートのGitリポジトリを正しく参照するように修正

rails/rails

背景

#56490で導入された変更により、Rails.app.revisionがカレントワーキングディレクトリの.gitをチェックするようになっていました。この実装では、アプリケーションが別のディレクトリから実行された場合に正しいリビジョン情報を取得できない問題がありました。

例えば、Railsアプリケーションが/var/www/myappにデプロイされているが、/tmpから実行された場合、/tmp/.gitを参照してしまい、意図したリビジョン情報が得られませんでした。

技術的な変更内容

変更前の実装

if Dir.exist?(".git")
  rev = `git rev-parse HEAD 2> /dev/null`.strip.presence
  rev if $?.success?
end

この実装では、カレントディレクトリに.gitが存在するかをチェックし、バッククォートでgitコマンドを実行していました。実行ディレクトリに依存するため、アプリケーションルート以外から起動すると失敗します。

変更後の実装

r, w = IO.pipe
if system("git", "-C", root.to_s, "rev-parse", "HEAD", in: File::NULL, err: File::NULL, out: w)
  r.read.strip
else
  r.close
  nil
end

改善された実装では以下の技術的な変更が加えられています。

主要な改善点

1. Git作業ディレクトリの明示的な指定

git -Cオプションを使用することで、gitコマンドを実行する作業ディレクトリを明示的に指定できます。root.to_sはRailsアプリケーションのルートディレクトリを指すため、カレントディレクトリに関係なく正しいリポジトリを参照します。

2. セキュアなコマンド実行

バッククォート構文からRubyのsystemメソッドに変更されています。systemメソッドでは引数を個別に渡すことで、シェルインジェクションのリスクを軽減できます。

3. パイプを使った出力のキャプチャ

r, w = IO.pipe

IO.pipeを使用して、読み取り側(r)と書き込み側(w)のパイプを作成しています。gitコマンドの標準出力をパイプの書き込み側にリダイレクトし、読み取り側から結果を取得します。

4. 標準入出力の制御

in: File::NULL, err: File::NULL, out: w
  • in: File::NULL: 標準入力を/dev/nullにリダイレクト
  • err: File::NULL: 標準エラー出力を/dev/nullにリダイレクト
  • out: w: 標準出力をパイプの書き込み側にリダイレクト

これにより、不要な出力を抑制しつつ、必要な情報だけを取得できます。

5. エラーハンドリングの改善

if system(...)
  r.read.strip
else
  r.close
  nil
end

systemメソッドはコマンドが成功した場合にtrueを返します。失敗時には明示的にパイプをクローズしてnilを返すことで、リソースリークを防ぎます。

実用的な影響

この修正により、以下のようなシナリオで正しく動作するようになります。

# /var/www/myapp がRailsアプリケーションルート
Dir.chdir("/tmp")  # 別のディレクトリに移動

# 修正前: /tmp/.git を探してしまう → 失敗
# 修正後: /var/www/myapp/.git を正しく参照 → 成功
Rails.application.revision  # => "abc123def456..."

デプロイスクリプトやバックグラウンドジョブなど、Railsアプリケーションを異なるディレクトリから起動するケースで、Rails.app.revisionが確実に機能するようになります。