分散バージョン管理システムGitの使い方入門

 バージョン管理システムと言うとSubversionやCVSが有名だが、近年急速にユーザーを増やしているバージョン管理システムに「Git がある。GitはLinuxカーネルの開発リーダーとして知られるLinus Torvalds氏が中心となって、Linuxカーネルの開発に使用する目的で開発した分散型バージョン管理システムである。2005年に開発が開始されて以来さまざまなプロジェクトでの採用が進み、現在ではPerl 5やRuby on Rails、Android、Wine、X.orgなど、有名な大規模プロジェクトで採用されるに至っている。

 本記事では、このGitを使用するのに必要な「分散型バージョン管理システム」の基本的な考え方を紹介するとともに、Gitの導入方法や基本的なGitの使い方について解説する。

分散バージョン管理システムとは?

 GitはLinuxカーネル開発で用いられることを前提に開発されており、次のような特徴を備えている。

  • 開発者各自がローカルにリポジトリを持つ「分散型」アーキテクチャを採用
  • 高速かつ柔軟にブランチの作成やマージが可能
  • Linuxカーネルのような巨大なプロジェクトでも十分な速度で動作
  • 変更履歴とともにそのハッシュも保存され、これを検証することで改竄を検出できる
  • 主要部分はCで書かれており、各種スクリプト言語から利用できるラッパーも用意されている
  • 独自プロトコルのほかWebDAVやrsync、ssh経由でリモートのリポジトリにアクセスできる

 この中でもGitの大きな特徴となっているのが「分散型」という点だ。Gitはプロジェクトに関わる各開発者がそれぞれローカルにリポジトリを持ち、必要に応じてリポジトリ間で変更点をやりとりすることによってソースコードなどの管理を行う(図1)。いっぽう、バージョン管理システムとして有名なSubversionやCVSは、1つのリポジトリにすべての開発者がアクセスする「集中型」と呼ばれるアーキテクチャを採用している(図2)。

図1 各開発者がリポジトリを持つ分散型バージョン管理システム
図1 各開発者がリポジトリを持つ分散型バージョン管理システム


図2 1つのリポジトリにすべての開発者がアクセスする集中型バージョン管理システム
図2 1つのリポジトリにすべての開発者がアクセスする集中型バージョン管理システム

お詫びと訂正

 記事掲載当初、Gitの特徴の1つとして「変更履歴は暗号化されて保存され、改竄ができない」としていましたがこちらは誤りで、実際は変更履歴自体を暗号化して保存しているわけではなく、そのハッシュを保持することで変更履歴の整合性を検出できる、ということでした。また、これに加えてGPG署名を利用することでさらに堅牢にすることも可能です。お詫びして訂正いたします。ご指摘をいただき、ありがとうございました。

 図3は、分散型バージョン管理システムを使う際のワークフロー例だ。分散型バージョン管理システムでは、変更を加えたファイルの情報をまずローカルのリポジトリにコミットしていき、ある程度変更点がまとまったらそれをpushという形でほかのリポジトリに送信する。また、pullという機能を使うことで、ほかのリポジトリで行われた変更点を自分のリポジトリに取ってくることもできる。

図3 分散型バージョン管理システムのワークフロー
図3 分散型バージョン管理システムのワークフロー

 分散型バージョン管理システムには、次のような利点がある。

  • 競合を考えずにコミットが行える
  • オフライン環境でも利用できる
  • 派生物の作成が容易

 分散型バージョン管理システムでは、各開発者がそれぞれ自分専用のリポジトリを持つため、コミット時には自分が編集しているファイルを他者が同時に編集していたという事態(競合と呼ばれる)が発生しない(もちろん、pushやpullを実行する際にはpush/pull対象のリポジトリとで競合を解消する必要はある)。また、ローカルにリポジトリを作成するため、ネットワークが利用できない環境でもコミットを行うことができる。

 そして、「中央リポジトリ」が存在せず、、他の開発者が加えた変更を自由に自分のリポジトリにマージできるため、特定の変更点だけを反映させた派生物を作りやすい。たとえば、分散型バージョン管理システムを利用する場合、特定のリポジトリを「マスターリポジトリ」とし、そこに各開発者がそれぞれのリポジトリで加えた変更点をマージして成果物を作成する、というケースが多いが、このマスターリポジトリをベースに、別の変更を加えて独自の成果物を作成することもできる。また、プロジェクトが複数のグループに分かれている場合などは、それぞれのグループごとに変更点をマージしたリポジトリを用意し、最終的にそこからそれぞれのグループの成果物をマージする、といった階層的なプロジェクト運営を行うこともできる。

分散型バージョン管理システムを集中型バージョン管理システムのように使う

 分散型バージョン管理システムでは複数のリポジトリが存在するために一見難しく見えるが、次のようなルールで運用することで、集中型バージョン管理システムと同じように利用することもできる(図4)。

  • すべての変更点を集積するマスターリポジトリを用意する
  • 各開発者は、ファイルに変更を加える前にマスターリポジトリから変更点をpullする(集中型バージョン管理システムのアップデートに相当)
  • ローカルリポジトリに変更点をコミットした後、必ずマスターリポジトリへ変更点をpushする(集中型バージョン管理システムのコミットに相当)
  • それぞれのリポジトリはマスターリポジトリ以外に対してpush/pullを行わない
図4 個人リポジトリ間のpush/pullを考慮しなければ、分散型バージョン管理システムは集中型バージョン管理システムとほぼ同様に運用できる
図4 個人リポジトリ間のpush/pullを考慮しなければ、分散型バージョン管理システムは集中型バージョン管理システムとほぼ同様に運用できる

 分散型バージョン管理システムに不慣れな場合、まずは上記のような集中型のルールでプロジェクトを運営し、必要に応じてリポジトリの分岐や個人リポジトリ間のpush/pullを行うなど、分散型のメリットを活用できる体制に移行していくとよいだろう。なお、集中型の運用をする場合でも、Gitを使えば「競合を考えずにコミットが行える」、「オフライン環境でも利用できる」というメリットが得られる。

Gitのインストール

 各種Linuxディストリビューションや各種BSDでGitを利用するなら、多くの場合システム標準のパッケージマネージャでGitをインストールできる。また、最新版のRPM/DebパッケージやMac OS X向けのインストーラ、ソースコードについてはGitのダウンロードページに入手先へのリンクが掲載されている。

 いっぽう、WindowsでGitを使いたい場合はCygwinを利用するか、もしくはMinGW/MSysを使用したバイナリを配布しているmsysGitを利用する。msysGitを利用するとGitの利用に必要なbashやSSH、mvやrm、lsといった各種UNIX互換コマンド、Perlなどをまとめて導入できるほか、簡単なGUIツールも付属している。ただし、GitとSubversionを連携させる「git-svn」などは含まれない。Cygwinを利用している場合やそのほかのUNIX/Linux由来アプリケーション、git-svnなどを利用したい場合はCygwinを、Gitのみを利用したい場合はmsysGitを利用するとよいだろう。なお、msysGitはデフォルトでは「C:\Program Files\Git\」以下に各種ツールをインストールするので、Cygwinと共存させることも可能だ(図5)。

図5 msysGitのインストール先
図5 msysGitのインストール先

Gitを使った基本的なプロジェクト管理:ローカルリポジトリの作成

 Gitを使ったソースコード管理は、まずローカルリポジトリを作成することから始まる。ローカルリポジトリを作成するには2つの方法がある。新規にプロジェクトを立ち上げる場合や、別のバージョン管理システムからGitに切り替える場合など、すでにローカルにGitで管理したいファイルがある場合は、ファイルが格納されているディレクトリで「git init」コマンドを実行すればよい。たとえば、「/home/test/test」ディレクトリをローカルリポジトリにする場合、下記のように実行する。

$ cd /home/test/test
$ git init
Initialized empty Git repository in /home/test/test/.git/

 いっぽう、Gitでファイル管理を行っているプロジェクトに参加する場合や、Gitで管理されているソースコードを入手したいという場合は「git clone <リポジトリ>」コマンドを利用する。たとえば、「git.sourceforge.jp/gitroot/test/test.git」というリポジトリからファイルをダウンロードしてローカルリポジトリとする場合、次のように実行する。

$ git clone git://git.sourceforge.jp/gitroot/test/test.git
Initialized empty Git repository in /home/test/.git
remote: Counting objects: 857, done.
remote: Compressing objects: 100% (494/494), done.
remote: Total 857 (delta 339), reused 845 (delta 333)
Receiving objects: 100% (857/857), 8.63 MiB | 1089 KiB/s, done.
Resolving deltas: 100% (339/339), done.
Checking out files: 100% (1120/1120), done.

変更のコミット

 管理しているファイルに変更を加えた後、コミットを行うことで変更点がGitに記録される。コミットを行うには、「git commit」コマンドを利用する。なお、CVSやSubverseionなどの場合、コミット対象を省略すると変更したファイルが自動的にコミット対象となったが、Gitの場合はコミットするファイルを「git add」コマンドで明示的に指定する必要がある。「git status」コマンドを実行することで修正されたファイルを確認できるので、修正されたファイルのうちコミットしたいファイルを確認し、「git add」でコミット対象に追加するとよいだろう。

 たとえば、Gitでバージョン管理を行っているファイルに変更を加えた状態で「git status」コマンドを実行すると、下記のように「Changed but notupdated:」以下に変更が加えられたファイルが表示される。

$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified:   HBPreferencesTransFormer.h
#       modified:   HBPreferencesTransFormer.m
#
no changes added to commit (use "git add" and/or "git commit -a")

 この例の場合、「HBPreferencesTransFormer.h」および「HBPreferencesTransFormer.m」が変更されたファイルだ。そこで、「git add」コマンドでこのファイルをコミット対象に追加する。

$ git add HBPreferencesTransFormer.*
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   HBPreferencesTransFormer.h
#       modified:   HBPreferencesTransFormer.m

 「git add」コマンドでは変更されたファイルだけでなく、新たに作成したファイルも追加できる。一通りコミットしたいファイルを指定したら、最後に「git commit」コマンドでコミットを実行する。

$ git commit
Created commit a3ab45b: Add ValueTransformer method.
 2 files changed, 31 insertions(+), 3 deletions(-)

 なお、「git commit -a」のように「-a」オプション付きでコミットを実行することで、変更されたファイルを自動的にコミット対象に加えてコミットを実行することもできる。

ローカルリポジトリに加えた変更をリモートリポジトリに送信する

 ローカルリポジトリに加えた変更をリモートリポジトリに送信するには、「git push <リポジトリ名>」コマンドを利用する。ローカルリポジトリでの作業が一段落し、加えた変更点をマスターリポジトリ(たとえばSourceForge.JPのGitリポジトリなど)に送信する場合などに、このコマンドを利用する。

 たとえば、ユーザー名「hoge」で「git.sourceforge.jp:/gitroot/test/test.git」というリポジトリに変更点を送信したい場合、次のように実行する。

$ git push [email protected]:/gitroot/test/test.git
Counting objects: 9, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 841 bytes, done.
Total 5 (delta 3), reused 0 (delta 0)
To [email protected]:/gitroot/test/test
   cf6c47c..a3ab45b  master -> master

 なお、自分が行った変更と競合するような変更がすでに行われていた場合、次のようなメッセージが表示されてpushに失敗する。

$ git push [email protected]:/gitroot/test/test.git
To [email protected]:/gitroot/test/test.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to '[email protected]:/gitroot/hoge/hoge'

 この場合、競合を解消してから再度pushを実行する必要がある。もっとも簡単なのは、後述する「git pull」コマンドを使用して競合部分を取得し、ローカルリポジトリを修正する方法だ。

SourceForge.JPでGitリポジトリを利用する場合の注意点

 SourceForge.JPでは、各プロジェクトが利用できるGitリポジトリが用意されている。リポジトリの作成は、各プロジェクトの「ソースコード」タブ内にある「Gitリポジトリ管理」で行える。

 なお、リポジトリ作成後の初回のpush時は、下記のように明示的にブランチを指定する必要があるので注意してほしい(この例では「master」がブランチ名)。

$ git push [email protected]:/gitroot/test/test.git master
Enter passphrase for key '/home/hoge/.ssh/id_dsa':
Counting objects: 847, done.
Compressing objects: 100% (817/817), done.
Writing objects: 100% (847/847), 8.64 MiB | 332 KiB/s, done.
Total 847 (delta 333), reused 0 (delta 0)
To [email protected]:/gitroot/test/test.git
 * [new branch]      master -> master

リモートリポジトリから変更点を取得する

 リモートリポジトリに加えられた変更点を取得し、それらをローカルリポジトリに適用するには「git pull」コマンドを利用する。次の実行例は、ユーザー名「hoge」で「git.sourceforge.jp:/gitroot/test/test.git」というリポジトリから変更点を取得する例だ。

$ git pull [email protected]:/gitroot/test/test.git
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 3), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
From [email protected]:/gitroot/test/test.git
 * branch            HEAD       -> FETCH_HEAD
Updating fd311c5..cf6c47c
Fast forward
 macosx/HBPreferencesTransFormer.h |   16 ++++++++++++++++
 macosx/HBPreferencesTransFormer.m |   14 ++++++++++++++
 2 files changed, 30 insertions(+), 0 deletions(-)
 create mode 100644 macosx/HBPreferencesTransFormer.h
 create mode 100644 macosx/HBPreferencesTransFormer.m

 このとき、もし競合が発生した場合は次のようにその旨が表示される。

$ git pull [email protected]:/gitroot/test/test
Auto-merged markup.pl
CONFLICT (content): Merge conflict in markup.pl
Automatic merge failed; fix conflicts and then commit the result.

 このとき、競合が発生したファイル(上記の例ではmarkup.pl)の競合が発生した位置には次のようなマーカーが挿入され、どのように競合が起こっているかが分かるようになっている。

sub ulist {
    if( $l =~ m/^☆(リスト.*)$/ ) {
      $cap = $1;
    }

<<<<<<< HEAD:markup.pl
    print "<ul>\n";
    while( $l =~ m/^・/ ) {
      $l =~ s/^・(.*)$/<li>$1<\/li>/;
      print "foo:$l\n";
      $l = <>;
=======
    print "<p><b>$cap</b></p>\n";
    print list_start( $cap );
    while( $l = <> ) {
>>>>>>> e74597cbfdb9995e540ca9e8c8a6e79705e2889c:markup.pl
      chomp $l;
      $l =~ s/&/&amp;/g;
      $l =~ s/</&lt;/g;
      $l =~ s/>/&gt;/g;
}

 この中で、「<<<<<<< HEAD:markup.pl」から「=======」までがマスターリポジトリ内のソースコード、「=======」から「>>>>>>> e74597cbfdb9995e540ca9e8c8a6e79705e2889c:markup.pl」までがローカルリポジトリ内にあったソースコードだ。競合部分を確認・編集し、競合を解消して「git commit」でコミットを実行すれば、「git push」でpushが行えるようになる。

ヘルプの使い方

 最後に、オンラインマニュアルの使い方について紹介しておこう。Gitのオンラインマニュアルは、UNIXの伝統に従いmanページで提供されており、Gitの概要については「man git」とmanコマンドを実行することで確認できる。

$ man git

NAME
       git - the stupid content tracker

SYNOPSIS
       git [--version] [--exec-path[=GIT_EXEC_PATH]]
           [-p|--paginate|--no-pager]
           [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
           [--help] COMMAND [ARGS]
  :
  :

 また、「git init」や「git commit」といったサブコマンドの使い方は、たとえば「man git-init」や「man git-commit」のように、ハイフンでサブコマンドを繋いでmanコマンドを実行することで閲覧できる。

GIT-INIT(1)                             Git Manual                            GIT-INIT(1)

NAME
       git-init - Create an empty git repository or reinitialize an existing one

SYNOPSIS
       git init [-q | --quiet] [--bare] [--template=<template_directory>]
       [--shared[=<permissions>]]
  :
  :

 そのほか、「man gittutorial」でチュートリアルを読むこともできる。いまのところマニュアルは英語でしか提供されていないが、これに目を通しておけば基本的なGitの使い方をマスターできるだろう。