CVS/SVN世代のためのGit入門

バージョン管理システムとしてGitがすっかり一般的になりましたが、他に慣れ親しんだシステムがある場合、なかなか移行できないものです。
自分自身、CVSやSVNに慣れ親しんでいて、何かバージョン管理したいときにはついつい手元のCVSサーバに入れてしまい、なかなかGitを使う機会がありませんでした。また、CVS/SVN世代は、たいていの場合に管理系業務も担当しているため、なかなか新しい開発ツールを学習する時間が確保できません。
そういう「オールドタイプ」向けに、ざっとGitを理解するための情報をまとめておきます。

Gitの特徴

Gitの特徴は検索すれば色々と出てきますが、CVS/SVNとの対比で重要なのは下記です。

  • 分散レポジトリであり、サーバがダウンしていても、ファイルの履歴や差分を利用することができる。
  • レポジトリに保存しているのは差分ではなく、各バージョンのファイル全体である(実装は多少異なるようですが、とりあえずの理解としてはこれで十分)。

この2つ目の特徴のおかげで、さらに下記のような特徴があります。

  • ブランチ作成やブランチ間移動がとても速い。
  • バイナリファイルでも、特に意識せずにレポジトリに入れられる。
  • ファイル名の変更やディレクトリ移動も、すべてレポジトリに記録される。

もう1つ、GitHubという言葉もよく出てきますが、これはGitレポジトリのホスティングサービスの1つであり、Gitとは異なるものです。この2つを混同すると、JavaとJavaScriptを間違えたときと同じように、IT理解度を疑われることがあるので、注意が必要です。
なお、Gitレポジトリのホスティングサービスとしては、BitBucketなどGitHub以外にもいろいろあります。

cloneから始めるGit入門

オールドタイプが初めてGitを使うのは、既存のソースがGit管理されているときです。自分で作ったものをバージョン管理したいときには、使い慣れているCVS/SVNサーバを使ってしまうからです。
自分の場合、Mapnikというオープンソースの地図画像生成ライブラリを使っていて、内部処理を確認するためにソースを見たくなったのがGitを使うきっかけでした。他には、社内の既存プロジェクトでトラブルがあって、解析に駆り出されたなども、ありそうなきっかけです。
ここでは、GitHub上にあるMapnikのレポジトリを例として、具体的な操作を紹介します。

1. まず、Gitのコマンドラインを準備します。Gitの公式サイトで、各種OS用のバイナリが配布されています(https://git-scm.com/downloads)。Windows用バイナリもあり、Git Bashという名前で、Gitのコマンド群とWindows用のコンソール画面がセットになって配布されています。
2. GitHub上にある、Mapnikのレポジトリ(https://github.com/mapnik/mapnik)のclone用URLを確認します(レポジトリのトップページの右上の方にある。画像参照)。なお、とりあえずソースを読むだけなのでhttpsプロトコルを使っていますが、自分の編集をpushしたい場合はSSH(gitプロトコル)の方が便利です。

3. Git Bashを起動し、適当な空きディレクトリに移動して、下記コマンドを実行します。

$ git clone https://github.com/mapnik/mapnik.git
Cloning into 'mapnik'...
remote: Counting objects: 109686, done.
remote: Compressing objects: 100% (17/17), done.
Receiving objects:  12% (13868/109686), 3.64 MiB | ***.** KiB/s

4. しばらく待つと、直下にmapnikというディレクトリが生成されています。mapnikに移動して、下記のようなコマンドを試してみると、ちゃんとソースコードが取得できていることが確認できます。この状態を厳密に表現すると、レポジトリを複製して、最新バージョンをワーキングツリーにチェックアウトした状態です。

$ cd mapnik
$ git status
$ git log
$ git tag

過去のバージョンを見てみる

git tagで出てくるタグのうち、過去の適当なものを見てみましょう。とりあえずv3.0.2を見てみることにします。まず、現状のソースとの差分を見るには、下記のコマンドです(タグ名のあとに、ピリオド2つ追加されていることに注意)。

$ git diff v3.0.2..

ワーキングツリー全体をv3.0.2に置き換えるには下記のコマンドです。

$ git checkout v3.0.2

Detached Headというモードになりますが、ソースを眺めるだけなら気にする必要はありません。checkoutコマンドが比較的あっさりと完了するので、本当に置き換わっているのか疑問に思うかもしれませんが、CHANGELOG.mdというファイルを見れば、ちゃんとv3.0.2になっていることが確認できると思います。

元の状態に戻るには、下記のコマンドです。

$ git checkout master

レポジトリについて

さて、diffやcheckoutができることからレポジトリが存在していることは分かりますが、Git Bashをインストールしただけで、特にサーバを立ち上げた覚えはありません。レポジトリはどこにあるのでしょうか?
答えは、ワーキングツリー直下にある .git というディレクトリです。ここに、過去のコミット内容やタグなどがすべて保存されています。ここの容量をduコマンドで見てみると、結構な大きさになっていることが分かります。

$ du .git
35      .git/hooks
1       .git/info
1       .git/logs/refs/heads
1       .git/logs/refs/remotes/origin
1       .git/logs/refs/remotes
2       .git/logs/refs
6       .git/logs
0       .git/objects/info
163512  .git/objects/pack
163512  .git/objects
1       .git/refs/heads
1       .git/refs/remotes/origin
1       .git/refs/remotes
0       .git/refs/tags
2       .git/refs
163711  .git

Gitでは、原則として、1レポジトリ・1ワーキングツリーになっています。過去のソースと、現在のソースを別々の場所にcheckoutして比較したいという場合には、レポジトリごとcloneしてcheckoutすれば実現できます。この際に、clone元のレポジトリとしてローカルのレポジトリを使うことができます。

$ cd ..
$ git clone ./mapnik mapnik2
$ cd mapnik2
$ git checkout v2.2.0

なお、Git 2.6以降ではgit worktreeというコマンドが追加されていて、こちらを使って似たようなことが実現できるようです。

その他の情報源

この先は、他にいくらでも良い情報があるので、そちらを参照してください。CVS/SVN世代向けな情報としては、以下のようなものがあります。

オブジェクトモデルというのは、「Git オブジェクト」を単位としたデータ構造のことです。どのようにデータを記録してあるかが、 Git の最も魅力的な部分です。Git の内部を見ていくと、実にシンプルな仕組みで動いていることに驚きます。
一方、Git コマンドには現実の複雑さをそのまま反映しているような部分もあって、学んでいてもあまり楽しくありません。
(中略)
基本的な使い方がある程度わかったら、内部のオブジェクトモデルから入門し直すことをおすすめします。オブジェクトモデルがわかると、自然な流れでコマンドを理解できるようになります。

Gitを実際に開発に使うようになると、「ちょっとした追加機能をmasterにcommitしちゃったけど、やっぱりbranchにしたい」みたいなことが起こります。そういうときに、内部構造を理解していた方が使いやすくなります。