発生のメカニズム
なぜDiff画面だけ?
なぜ、ファイルビューでは文字化けしないのにDiff画面では文字化けするのでしょうか。
ファイルビューでは、CharlockHolmesというオープンソースのライブラリを使ってファイルのエンコーディングを推測し、UTF-8以外のエンコーディングだった場合は内部的にUTF-8に変換することで、GitHubのWeb UIの上で問題なく表示できるようにしています。つまり、ファイルのエンコーディング方式が何であっても、表示上はすべてUTF-8に統一されているということです。
しかしDiff画面では、この推測がボトルネックになります。Diffとはある時点からある時点へのファイルの変更の差分です。更に、その変更はShift_JISからEUC-JPといったような、エンコーディングの変更を含んでいる可能性もあります。したがって、推測処理はそのコミットに含まれるファイルの2倍(差分の変更前と変更後の分)行う必要があります。さらに、差分データは小さいことが多いため、エンコーディングの推測精度が下がります。間違った推測に基づく変換は、さらなる文字化けを引き起こすでしょう。
このような状況を踏まえ、GitHubでは、Diff画面に限ってはエンコーディングの推測・変換を行わず、全てのBlobのバイト列をそのままUTF-8として解釈するという方針を採用しました。また、その際Unicodeとして不正な文字が現れた場合は全て�という文字に置き換えています。その結果、UTF-8以外のエンコーディングは全て文字化けしますが、Diff画面のレンダリング速度は向上しました。
したがって、この文字化けは、アプリケーションパフォーマンスとのトレードオフの結果と言えます。しかし、後述するSyntax Highlighted Diffsの登場で、この状況が少し変わりました。
シンタックスハイライト
GitHubでは2014年12月にSyntax Highlighted Diffsという機能をリリースしました。これはソースコードのシンタックスハイライト(構文ハイライト)をDiff画面でも実現する機能です。ただし、全てのファイルがシンタックスハイライトできるわけではありません。マークダウン形式のファイルなど、プログラムのソースコードではないものはハイライトできません。
Syntax Highlighted Diffsでは、ハイライトの可否を「そのDiffに含まれるBlobの言語が判定できて、かつ言語のTextMateスコープが適切なものかどうか」という基準で判断しています。
少し脱線しますが、それぞれもう少し詳しく説明します。まず、プログラミング言語の判定にはGitHubが開発したオープンソースライブラリLinguistが使われています。これはテキストファイルの種類を判定したり、そのテキストファイルがプログラミングのソースコードであれば、使用されている言語を判定してくれるライブラリです。GitHub.comのリポジトリ上部にある言語バーの実装にもこのライブラリが使われています。
また、TextMateというのはオープンソースのmacOS向けテキストエディタです。そして、TextMateのスコープというのは、テキストデータをツリーオブジェクトと見なし、そのツリーの特定の範囲(Scope)ごとに文法(Grammar)を定義することで、一つのファイル内に複数の言語が存在しても適切にシンタックスハイライトや補完が実行できるようにする仕組みです。詳細はIntroduction to scopesという記事で詳しく解説されているので、興味があれば読んでみてください。
このTextMateのスコープと文法は、現在ではSublime Text、Atom、Visual Studio Codeといった他のテキストエディタでもサポートされていて、モダンなテキストエディタ界隈ではデファクトスタンダードとなっています。GitHubでは、このTextMate互換の文法をベースにしたPrettyLightsというシンタックスハイライトエンジンを開発し、それを使ってBlobが適切なスコープを持っているかどうかを判定しています。なお、PrettyLightsはオープンソースプロジェクトになってはいません。
シンタックスハイライトとエンコーディング変換
では、このシンタックスハイライトの可否がなぜ文字化けと関係があるのでしょうか。GitHubのDiffやSyntax Highlighted Diffsに関係する部分のコードを読むと、シンタックスハイライトができる場合とできない場合で異なった動作を行うことがわかります。
プログラミング言語が判定できなかった、TextMateスコープが不明なものだったなどの理由でシンタックスハイライトが行われなかった場合は、Diffの扱いはこれまでと変わりません。ところが、シンタックスハイライトができる場合は、前述したPrettyLightsにデータを渡すため、Blobのエンコーディングの推測とUTF-8への変換を行っています。
もう一度コミットf074f92を見ると、文字化けしている2つのファイルは、シンタックスハイライトされていないことがわかると思います。
一方、example-utf8.srcというファイルを見てください。このファイルが文字化けしていないのは、そもそもエンコーディングがUTF-8だからです。しかし、シンタックスハイライトされていないので、言語の判定には失敗していることがわかります。example-utf8.jsと比較してみてください。
