UTF-8 Git ユーザー名のデプロイロックでのエンコード不整合を解消
Kamal はデプロイロック生成時に Git の user.name が UTF-8 を含む場合、Ruby が出力を US‑ASCII とみなすことで Encoding::CompatibilityError が発生していたが、本 PR でエンコーディングを明示的に UTF‑8 に強制し、シェルコマンドは ASCII のみを保ちつつロック詳細を正しく保持できるようになった。
背景
git config user.name の結果が UTF‑8 バイト列であっても、Ruby が外部エンコーディングを US‑ASCII と判定すると文字列のエンコードが不整合となり、ロック情報をシェルへ渡す段階で Encoding::CompatibilityError がスローされた。(このエラーは Cyrillic 文字列 "Сергей Федоров" で再現された。)
デプロイロックは base64 でエンコードされた詳細を SSHKit 経由でリモートに送信し、リモート側で echo "..." > .kamal/lock‑app‑production/details と展開するため、シェルコマンド自体は ASCII‑only である必要がある。(ロック詳細だけが非 ASCII になることは許容される。)
したがって、Git ユーザー名が非 ASCII を含む環境でも、ロック生成ロジックがエンコードエラーで中断しないように、Git 情報取得時点で文字列のエンコーディングを正しく設定する必要があった。
技術的な変更
lib/kamal/git.rb の user_name メソッドに force_encoding(Encoding::UTF_8) を挿入し、取得した文字列を UTF‑8 に強制した上で strip するように変更した。
@@
- `git config user.name`.strip
+ `git config user.name`.force_encoding(Encoding::UTF_8).strip
この修正に伴い、テストスイートが 3 つ追加された。test/commands/lock_test.rb では、スタブした Kamal::Git.user_name が "Сергей Федоров" を返すケースでロック取得コマンドを生成し、コマンド全体が ASCII のみであることと、エンコードされた詳細をデコードすれば元の UTF‑8 名が復元できることを検証した。
@@
test "acquire encodes non-ASCII lock details for shell" do
Kamal::Git.stubs(:user_name).returns("Сергей Федоров")
@@
assert_predicate command, :ascii_only?
assert_includes Base64.decode64(encoded_details).force_encoding(Encoding::UTF_8), "Locked by: Сергей Федоров"
end
test/git_test.rb では、外部エンコーディングが US‑ASCII に設定された状態で git config user.name が UTF‑8 バイト列を返すことをスタブし、Kamal::Git.user_name が正しく UTF‑8 文字列を返すことと valid_encoding? が真であることを確認した。
@@
test "user name reads UTF-8 bytes under US-ASCII external encoding" do
user_name = +"Сергей Федоров\n"
user_name.force_encoding(Encoding::US_ASCII)
@@
result = Kamal::Git.user_name
assert_equal "Сергей Федоров", result
assert_predicate result, :valid_encoding?
end
これらのテストは、エンコード強制が期待通りに機能し、ロックコマンドが ASCII のみで安全に実行できることを証明している。
設計判断
エンコーディングの強制は Git 情報取得層で行う という方針が採られた。これにより、ロック生成ロジックや SSHKit の呼び出し側は変更不要で、既存のフローをそのまま利用できる。
代替案としては、SSHKit 側で文字列を自動的にエンコード変換する実装や、ロック詳細を UTF‑8 のままシェルに埋め込む方法が考えられたが、シェルの文字セットは環境依存が強く、実行時エラーのリスクが増大するため採用されなかった。
今回の実装は 後方互換性 を保ちつつ、UTF‑8 名を安全に扱える最小限の変更に留めた点が評価できる。既存コードは user_name が文字列を返すだけなので、他の呼び出し箇所への影響はなく、テストカバレッジも追加されたことで信頼性が向上した。
まとめ
Kamal::Git.user_name が UTF‑8 エンコーディングを明示的に適用することで、US‑ASCII 環境下でも非 ASCII Git ユーザー名が正しく扱われ、デプロイロック作成時の Encoding::CompatibilityError を防止した。ロックコマンドは依然として ASCII のみで安全に実行でき、ロック詳細は Base64 エンコードにより元の UTF‑8 名を保持できるようになった。