データ ポイント

Git: Git はデータにすぎない

Julie Lerman

コード サンプルのダウンロード

Julie Lermanデータ列のソース管理、ですって? でも、そのソース管理が 1 つの大きなデータベースにすぎないとしたら、データおたくにとってはたまりません。誤解のないように最初に言っておくと、今月 Git を取り上げるのは、私が Git の専門家だからではなく、それどころか Git と悪戦苦闘したからです。あまり輝かしくない私の GitHub 歴がその証拠です。Git の話題になると、開発者の友人のほとんどが顔色 1 つ変えず話題について行く中、自分が Git を活用できていないことが露呈するのが怖く、話をそらしたものです。

最近、Ruby 開発者である地元の友人の Alan Peabody (github.com/alanpeabody、英語) に私がいかに Git を活用できていないか話したとき、彼はこう言いました。「でもね Julie、Git はデータにすぎないんだよ」。データという言葉に私は反応し、「データですって? 私はデータが大好きなの! もっと教えて!」と彼に頼みました。Peabody は、Git がキーと値のペアで埋め尽くされたデータベースに依存していることを教えてくれました。そして、Git を操作するときは、あらかじめ用意されている UI ツールや "porcelain (磁器)" コマンドを使用するのではなく、低レベルの "plumbing (配管)" コマンドと API について調べて活用することを提案しました。

つまり Git には、データベースと便利な配管があるということです。私は Peabody のアドバイスによって、が然やる気になりました。Git の低レベルでの動作、リポジトリのアクティビティを表すデータの読み取りと書き込みの方法、そして Git におけるさまざまなオブジェクトの種類が相互にどのように関連しているかを調べるにまで至りました。いまだに専門家とは到底言えない状態ですが、Git での自分のアクティビティは前よりずっとコンロトールできているように感じます。さらに重要なのは、Git をいじるのが楽しくなり、何か怖いものが詰まった箱のように恐れる必要がなくなったことです。

同じ悩みを抱えている方は、オンラインで公開されている Scott Chacon の著書『Pro Git』(Apress、2009 年) の「Git の基本」と「Git の内側」の章をぜひお読みください。http://progit-ja.github.io/ からご覧いただけます。

以前確かに Git データベースに触れる機会がありましたが、その学習内容の多さに圧倒されたため、通り過ぎるだけで終わってしまいました。でも今は、Git の学習を少しでも楽しもうと思っています。ここからは、データベースと、データベースが追跡しているコードを簡単に操作して、リポジトリを表すデータベースにその変更がどのような影響を及ぼすか見てみます。

空の新しいリポジトリではなく、使用中の既存のリポジトリから開始することにします。GitHub.com/aspnet/EntityFramework (英語) でホストされている、Entity Framework 7 の非常に初期の作業を使用します。本コラム執筆時点ではこのプロジェクトの変化は非常に激しいので、ここで扱っているファイルは変更されている場合があります。

posh-git モジュールを Windows PowerShell にインストールしてから、PowerShell でコマンドを実行します。posh-git は、より詳細なステータス情報、色分け、およびタブ補完によってコマンドラインのエクスペリエンスを拡張します。セットアップ方法の指示については、コラム付属のダウンロードで確認してください。

リポジトリの取得と Git 資産の確認

まず、既存のリポジトリを複製します。以下のように、既存の github フォルダーから開始し、git clone コマンドを使用します。

D:\User Documents\github> git clone git://github.com/aspnet/EntityFramework

この最初の手順が Git スキルを上げてくれます。Git はリポジトリの名前を使用して開始ディレクトリに新しいフォルダーを作成するので、ここでは D:\User Documents\github に EntityFramework フォルダーが作成されます。複製処理中、EntityFramework フォルダーを作成直後にエクスプローラーで開くと、まず最初に .git サブフォルダーが作成されるのがわかります。これこそがリポジトリで、「Git データベース」と呼ばれるものです。また、Git はこのデータを使用して「作業ディレクトリ」 (EntityFramework フォルダーのその他の部分) にソースを構築します。

この操作が完了したら、新しく作成された EntityFramework フォルダーは Visual Studio のソリューション フォルダーとほぼ同じように見えます。1 つだけ違うのは .git フォルダーの有無で、これは複製されたリポジトリの完全なコピー (すべてのブランチを含む) です。ファイル構造については図 1 を参照してください。Git では、ブランチはコミットへの単なるポインターで、コミットは作業ディレクトリのスナップショットへのポインターです。Git リポジトリの "メイン" ブランチは既定で master と呼ばれますが、必ずその名前にしなければならないわけではありません。たとえば EF チームは、既定のブランチが、dev と名前を付けたブランチを指すようにしています。ブランチを使用すると、別のブランチに変更をマージする前にコードの変更やテストを安全に実行できます。

リポジトリの複製によって作成されたソリューション フォルダー (.git フォルダー内にリポジトリそのものも含まれる)
図 1 リポジトリの複製によって作成されたソリューション フォルダー (.git フォルダー内にリポジトリそのものも含まれる)

EntityFramework フォルダーの残りのコンテンツは作業ディレクトリを表します。どこかの時点で、作業ディレクトリで追加、変更、または削除したファイルを追跡するよう Git に指示することになりますが、これを「ステージング」と呼びます。これらのファイルへの変更がステージングされて、コミット可能になります。ステージングされた変更はデータベースに格納され、コミット後もそこに残ります。最終的には、それらの変更をサーバーにプッシュすることになります。今回はデータベースのアクティビティについて詳しく調べることに重点を置くため、プッシュの手順については扱いません。ツリーやフォーク、参照、ヘッダーなど、他にも多くの概念がありますが、今回は取り上げません。

では、このデータベースの内容や場所はどうなっているのでしょう。SQL Server や SQL CE などと同じようなリレーショナル データベースではなく、実際は、Git オブジェクトを表すファイルのコレクションです。どのファイルもハッシュで名前が付けられ、ハッシュされたコンテンツを含みます。データベースは、キーと値のペアのセットであることを思い出してください。ファイル名はデータベース内のオブジェクトを表すキーで、そのコンテンツが値です。これらのオブジェクトのコレクションは、リポジトリを表すデータベースを構成します。すべてのキーと値のペアは .git/objects フォルダーに格納され、それらのマスター リストは index というファイルに格納されます。さまざまなブランチを追跡するオブジェクトもあります。多くのファイルが単に複製されまったく編集されないという状態もあり得ますが、Git はそれらのファイルを指すことができるのでコピーを別に保持しなくてもよいため、Git データベースが膨張せずに済みます。

しかし、最初にすべてのものが .pack ファイルと共に .idx ファイルに圧縮されているので、まだオブジェクト ファイルは見当たりません。これらは pack サブフォルダーに格納されています (図 2 参照)。

すべてが .pack ファイルに圧縮されているので最初はオブジェクトが見当たらない
図 2 すべてが .pack ファイルに圧縮されているので最初はオブジェクトが見当たらない

Git がコードについて把握していること

編集を開始する前に、Git が作業ディレクトリとリポジトリについて何を把握しているのか確認しようと思います。つまり、もう一度 Windows PowerShell を使用し、posh-git を有効にして Git のコマンドラインのエクスペリエンスを拡張します。まず、EntityFramework フォルダーにディレクトリを変更します (古きよき DOS 時代のように、"cd EntityFramework" を実行します)。posh-git は .git サブフォルダーを確認し、Windows PowerShell 環境に出力します。図 3 に Windows PowerShell ウィンドウを示します。タイトルに "posh~git" が含まれ、プロンプトには黄色のかっこで囲まれたステータスが表示されています。これらは posh-git の機能です。現在、作業ディレクトリは dev というブランチ (EF7 リポジトリのメイン ブランチ) を使用していることが示されています。リポジトリを複製したとき、Git はこのブランチを既定の操作として「チェック アウト」しました。これは、作業ディレクトリに Git が格納したバージョンのブランチになります。Git でチェック アウトが意味するところはまさにこれで、指定されたブランチから作業ディレクトリを構築します。エクスプローラーで .git フォルダー以外を削除し、Windows PowerShell で「git checkout dev」と入力するとおもしろいので、ぜひお試しください。ディレクトリのコンテンツが完全に作り直されます。インターネットから切断しておくと、コンテンツがサーバーから転送されるわけではないことを実証できます。

posh-git が有効になっている Windows PowerShell
図 3 posh-git が有効になっている Windows PowerShell

Git が作業ディレクトリについて何を把握しているのか知るには、git "status" コマンドを使用します。すべての git コマンドは git から始まるので、以下のように入力します。

git status

これにより、以下のようなメッセージが表示されます。

On branch dev
nothing to commit, working directory clean

まさにそのとおりです。クリーンな状態からスタートしています。

作業ディレクトリのファイル編集に対する Git の反応

ここで、ファイルを変更した結果としてどのようにステータスが変わるか確認してみます。

個人的にお気に入りの EF クラスの 1 つ、DbContext.cs を編集することにします (src\EntityFramework にあります)。Visual Studio は使用しません。同じ手順を実行される場合は、Notepad++ やお気に入りのテキスト エディターをお使いください。

一番上に以下のようにコメントを追加しました。

// Julie was here

これを保存します。

git status をもう一度実行すると、出力はさらに興味深い内容になっています (図 4 参照)。

作業ディレクトリでファイルを変更した後のステータス (赤いフォントは作業ディレクトリのステータスを表す)
図 4 作業ディレクトリでファイルを変更した後のステータス (赤いフォントは作業ディレクトリのステータスを表す)

Git はファイルが変更されたけれどもステージングされていないと見なします (そのことが、残念ながら読みづらい赤字で示されています)。つまり、Git はまだファイルを追跡していませんが、作業ディレクトリのファイルをそのデータベースと比較し、判断を下しています。また、dev のプロンプト ステータスに "+0 ~1 -0" が追加されていることにも注目してください。赤字で表示されているステータスは作業ディレクトリを反映していて、新しいファイルが 0 個、変更されたファイルが 1 個、そして削除されたファイルがないことが示されています。

データベースについてはどうでしょう。エクスプローラーを見ると、タイムスタンプやサイズから判断して、index ファイルが変更されていないことがわかります。同じく、objects フォルダーでも何も変更されていません。Git データベースは作業ディレクトリの変更については認識していません。

Git へのファイル追跡の指示

Git は指示されない限りファイルを追跡しません。代表的な磁器コマンドを使って Git に追跡対象ファイルを追加するよう指示すると、どのファイルを追跡するかを Git が判別します。ただし、それぞれの手順を明確にするため、Git で開発者が使用する一般的なコマンドの代わりに、ここでは配管コマンドを使用します。以下にコマンドを示します。入力するファイル パスは大文字と小文字が区別されることに注意してください。

git update-index --add src\EntityFramework\DbContext.cs

これにより、プロンプトのステータスが変わります。前と同じように "+0 ~1 -0" が表示されますが、赤ではなく緑で表示されます。つまり、index のステータスであることが示されます。Git は今、0 個の新しいオブジェクト、1 個の変更されたオブジェクト、0 個の削除されたオブジェクトを追跡しています。いずれもコミットが可能です。

Git の index ファイルと objects フォルダーについてはどうでしょう。

.git ディレクトリにある index ファイルのタイムスタンプは変わっています。このファイルには、DbContext.cs クラスを表す object ファイルが変更されたという注意書きが含まれるようになりました。これにより、1 つの変更されたファイルが追跡されているというステータスが表示されるようになります。Entity Framework のコードを記述している方は、どこかで聞いたような話だと思いませんか。そう、index は、エンティティの変更を追跡する EF DbContext と似ています。DbContext はオブジェクト インスタンスが追加、変更、または削除された時点を把握していますが、index ファイルも同様です。

さらに、オブジェクトについても確認できます。.git/objects に移動すると新しいフォルダーがあるのがわかります。Visual Studio で編集した場合、たとえばプロジェクト ファイルが変わったら新しいフォルダーの数は多くなりますが、Notepad++ で編集したので、ae という名前の新しいフォルダーが 1 つだけ増えていました。このフォルダーの中には、ハッシュを含む名前の付いたファイルがあります (図 5 参照)。

objects フォルダー内にある、Git によって追跡されているオブジェクト
図 5 objects フォルダー内にある、Git によって追跡されているオブジェクト

このオブジェクトはデータベースの一部で、.pack ファイルでキャッシュされる DbContext.cs ファイルを表すオブジェクトを上書きします。この新しいオブジェクト ファイルには、変更した点と共に、DbContext.cs のコンテンツのハッシュが含まれます。

自分では内容を読み取ることができませんが、Git にならそれが可能です。以下のように、cat-file コマンドを使用して、コンテンツ全体を表示するよう Git に指示します。

git cat-file ­-p ­aeb6db24b9de85b7b7cb833379387f1754caa146

-p パラメーターは、テキストをわかりやすく一覧表示するよう要求します。パラメーターの後には一覧表示するオブジェクトの名前が続き、フォルダー名 (ae) とファイル名の組み合わせになります。Git はこのオブジェクトの名前を簡単に表現することができます。

変更が強調表示されるビューもあります。以下のコマンドにより、このブランチ (dev という名前であることを思い出してください) で変更されたものを表示するよう Git に指示することができます。

git diff dev

この出力を図 6 に示します (わかりやすくするために番号を振ってあります)。

git diff コマンドの結果
図 6 git diff コマンドの結果

このテキストは、情報を区別しやすいように色の付いたフォントで書式設定されています。8 行目の赤色のダッシュは、[blank] 行を削除したことを示しています。9 行目は、新しい行であることを示すためプラス記号で始まっていて、緑色で表示されています。index にさらにオブジェクトを追加していたら、変更されたファイルも新しいファイルも、同様にここに一覧表示されていたはずです。

index ファイルはバイナリですが、調べることはできます。git ls-files コマンドは、index ファイルのリポジトリ オブジェクトによって表されるファイルをすべて一覧表示する際に便利です。ところが、ls-files はキャッシュされたインデックスと作業ディレクトリをまとめて一覧にし、ソリューション内のすべてのファイルを表示するので、目的のファイルを探すために内容を掘り下げなくてはなりません。git コマンドの中に、フィルターに使用する grep があります。このコマンドを ls-files と組み合わせれば、ファイルをフィルターすることができます。以下のように、grep (大文字と小文字が区別されます) を追加し、ls-files の -s (ステージ) パラメーターを使用して ls-files にオブジェクト名を表示するよう指定します。

git ls-files -s |grep DbContext.cs

ステージ パラメーターを含めると、以下のように、出力にファイルの種類が示されます (100664 は、実行可能ではない、グループで書き込めるファイルであることを示します)。また、関連付けられているオブジェクトのハッシュ ファイル名も表示されます。

100644 aeb6db24b9de85b7b7cb833379387f1754caa146 0
src/EntityFramework/DbContext.cs

ls-files について語るべきことは他にもたくさんありますが、現在個人的に関心を持っている部分 (Git がオブジェクト ファイルを作業ディレクトリの DbContext.cs ファイルにマップする方法がわかるということ) を紹介しました。

コミットによる Git データベースへの影響

次に、オブジェクトを、ステージング状態からコミット状態にプッシュした場合の影響について少し見ていきましょう。このコミットは自分のコンピューターでのみ行われることを思い出して下さい。サーバーの元のリポジトリには直接コミットされません。git commit コマンドでは、-m パラメーターを使用してコミット メッセージを追加する必要があります。すべてのステージングされた変更をコミットすることに留意してください。以下にコマンドを示します。

git commit -m "Edited DbContext.cs"

Git は、コミットの内容とコミットされたものについての情報を含む、新しく生成された、オブジェクトの Git データベース名 (2291c49) を返します。また、ファイルへの動作 (何かを追加したこと) を示すコミット メッセージが表示されます。出力を以下に示します。

[dev 2291c49] Edited DbContext.cs
  1 file changed, 1 insertion(+)
  D:\User Documents\github\ 
    entityframework [dev]>

プロンプトは簡潔であることに注目してください。ステータスは今、最後のコミットから行われたすべてのことを反映しています。つまり何もありません。クリーンな状態に戻りました。

コミットの結果としてデータベースには何が起こったのでしょう。タイムスタンプが変わっていないので index ファイルは更新されていないことがわかります。

しかし、objects ディレクトリには 4 つの新しいフォルダーがあり、それぞれに固有のハッシュ オブジェクトが含まれています。それらのファイルに含まれているものを確認するために cat-file を使用します。1 つ目のファイルは、ls-files の実行結果と同様、src\EntityFramework フォルダーにおけるすべてのファイルとフォルダー、およびそれらのオブジェクト名の一覧です。2 つ目は src フォルダーのすべてのオブジェクトの一覧、つまりサブフォルダーの一覧です。これらはちなみに、Git 用語では「フォルダー」ではなく「ツリー」と呼びます。3 つ目のオブジェクトには、ルート フォルダーである EntityFramework 内のすべてのファイルとフォルダーの一覧が含まれています。4 つ目のオブジェクトには、サーバーと同期するために必要なデータが含まれています。具体的に言うと、コミッター、作者の ID (私の電子メール アドレス)、「ツリー」のオブジェクト 1 つ、および「親」のオブジェクト 1 つです。興味深いことに、この 4 つ目のオブジェクトは、コミットへの応答でリレーされたものと同じオブジェクトになります。このオブジェクトは 22 というフォルダーにあり、ファイル名は 91c49 から始まります。

これらのオブジェクトはすべて、サーバー リポジトリにプッシュするときに使用されます。

Git を我が物にできたか

低レベルで物事を探索したことで、ある程度 Git を自分のものにできたと思います。個人的には、原因と結果を確認するのも、配管について掘り下げるのも大好きです。私は Git を恐れる気持ちが一切なくなりました。また、このように Git と楽しく触れ合うことで、コマンド言語について学習したり、低レベルで Git を操作することの美しさを理解したりできたのも思いがけない喜びでした。これほどたくさん Windows PowerShell を使ったのもおそらく初めてでした。

私には、Git で何が起こっているのか混乱したときに何度も Skype で助けてくれた Cori Drew など、Git の熱狂的なファンである友人が何人かいますが、それよりずっと多いのは、Git をまるで空気のような存在として扱っている友人たちです。彼らは Git のしくみを私に教えようとしてくれましたが、最初はそれに挫折してしまいました。彼らのおかげで、Git について学習し、そこから成果を得て、さらに学習すべきことについて知る気になりました。でも、データに感じている魅力や、物事への働きかけとその結果を知ることの魅力こそが、私をここまで駆り立ててくれました。ですので、Alan Peabody の刺激的な提案には心から感謝しています。

追記: 今回のコラムを書き終えてから 1 週間後、個人的な 2 つのプロジェクトに Git を使用し、ブランチやマージをプロのように使いこなせたことを報告します。Git に思い切って飛び込んだ結果、すばらしい成功を収めることができました。皆さんも同様にうまくいくことをお祈りしています。


Julie Lermanは、バーモント ヒルズ在住の Microsoft MVP、.NET の指導者、およびコンサルタントです。世界中のユーザー グループやカンファレンスで、データ アクセスなどの .NET トピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com/blog (英語) で、彼女は O'Reilly Media から出版されている『Programming Entity Framework』(2010 年) および『Code First』版 (2011 年)、『DbContext』版 (2012 年) を執筆しています。Twitter (twitter.com/julielerman、英語) で彼女をフォローし、juliel.me/PS-Videos (英語) で Pluralsight のコースをご覧ください。

この記事のレビューに協力してくれた技術スタッフの Cori Drew (coridrew@gmail.com) と Alan Peabody (gapeabody@gmail.com) に心より感謝いたします。