Subversion ユーザーのための Git: 第 1 回 Git 入門

Subversion バージョン管理システムのユーザーのために Git について明らかにする

DVCS (分散バージョン管理システム) には、集中型の VCS に勝るメリットがいくつもあります。そして DVCS モデルを検討しようとしている Subversion ユーザーにとっては、Git が出発点として最適です。2 回からなる連載の第 1 回である今回は、Subversion を基準に、Git のインストール方法、リモート・リポジトリーのセットアップ方法、そして Git の基本的なコマンドの使い方について説明します。

Teodor Zlatanov, Programmer, Gold Software Systems

photo- teodor zlatanovTeodor Zlatanov は 1999年にボストン大学 (Boston University) でコンピューター工学の修士号を取得しています。彼は 1992年からプログラマーとして働いており、Perl、Java、C、C++ を使ってきています。彼が関心を持っている領域は、オープンソースによるテキスト構文解析、データベース・アーキテクチャー、ユーザー・インターフェース、UNIX システム管理などです。



2009年 8月 04日

無料で入手できるオープンソースの VCS (バージョン管理システム) をあまりよく知らない読者のために説明すると、Subversion は非商用の VCS として標準的なものとなっており、かつて最もよく使われていた CVS (Concurrent Versions System) は Subversion に取って代わられています。CVS は、限られた用途には相変わらず問題なく使用できますが、Subversion には、Web サーバーでわずかなセットアップをすれば、それ以上のことはほとんど必要ない、という魅力があります。ほとんどの場合 Subversion は問題なく動作しますが、この記事で説明するように、Subversion にもいくつかの問題があります。

では、なぜ Subversion では事足りないのでしょう。多くの点で Subversion よりも優れた設計となっている Git (「G」は大文字です。git はコマンドライン・ツールです) は、数ある分散型の VCS の 1 つです。私自身が初めて経験した分散型の VCS は Arch/tla ですが、Mercurial、Bazaar、darcs など、その他にもいくつかの分散型の VCS があります。さまざまな理由から (そのうちの重要なものをこの記事で説明します)、Git はよく使われるようになり、Subversion と Git は、個人用あるいは企業用の VCS として最も優れた 2 つの選択肢と見なされることが多くなっています。

皆さんが Subversion のユーザーである場合には、Git に関心を持つ重要な理由として、次の 2 つが考えられます。

  • 何らかの面で Subversion の制約を受けているため、Git への移行を検討している。
  • Git に関心があり、Git を Subversion と比較したい。

あるいは、3 番目の理由があるかもしれません。Git は比較的ホットな技術であり、自分の履歴書に Git についての経歴を書き加えたい方もいるかもしれません。それが皆さんの主な目標であって欲しいとは思いませんが、Git について学ぶことは開発者にとって最も得るものが大きいことの 1 つなのです。たとえ皆さんが現在 Git を使わないとしても、この分散型 VCS に具現化された概念やワークフローは、IT 業界が今後 10 年間、対象とする範囲や地理的な分散の面で大きな変化を遂げる中で、この業界のほとんどの領域にとって非常に重要な知識となることは確実です。

そしてもうひとつの理由として (皆さんが Linux カーネルの開発者でない場合には魅力的な理由にならないかもしれませんが)、Linux カーネルや他の重要なプロジェクトのいくつかは Git を使って管理されており、皆さんがこれらに貢献したいと思っている場合には Git を十分に理解する必要があります。

この記事は、初級から中級の Subversion ユーザーを対象にしています。この記事を読むためには Subversion に関する初級レベルの知識と、バージョン管理システムについての一般的な知識が必要です。ここで説明する情報は主に UNIX® ライクなシステム (Linux® や Mac OS X) のユーザーを対象しており、一部 Windows® ユーザーも対象にしています。

この連載の第 2 回では、Git の高度な使い方 (分岐のマージ、diff の生成などの一般的な課題) について説明します。

Subversion と Git の基礎

ここから先では「Subversion」を「SVN」と省略し、キーボードの「U、B、E、R、S、I、O」キーの消耗を防ぐことにします。

git-svn

皆さんは git-svn について聞いたことがあるかもしれませんが、git-svn は Subversion のリポジトリーに対して Git を使えるようにするツールです。git-svn は状況によっては有用ですが、半分分散型でありながら集中型の VCS を使用するという方法は、分散型 VCS に切り換えることと同じではありません。

では、SVN は何に対して有効なのでしょう。その答えを既にご存知の読者もいるかもしれませんが、VCS はファイルを扱うのではなく、変更を扱うものです。SVN は中央のサーバー上で実行され、変更を SVN のデータ・リポジトリーに追加し、また変更ごとにスナップショットを提供することができます。このスナップショットにはリビジョン番号が付けられています。リビジョン番号は SVN にとって、そして SVN を使用する人にとって、非常に重要です。私が変更した後で他の人が変更した場合には、その人のリビジョン番号の方が必ず大きいことが保証されています。

Git の目標も SVN に似ており、変更を追跡することですが、Git には中央のサーバーはありません。この違いは重要です。SVN は集中型ですが、Git は分散型です。そのため、Git では変更ごとに増加するリビジョン番号というものはありません。「最新のリビジョン」というものはないからです。Git にも一意のリビジョン ID がありますが、これらのリビジョン ID 自体は SVN のリビジョン番号ほど有用なものではありません。

Git の場合、重要なアクションはもはやコミットではありません。マージです。誰でもリポジトリーを複製することができ、そのクローンにコミットすることができます。リポジトリーの所有者は、変更をマージして元の 1 つのリポジトリーに戻すことができます。あるいは、開発者が変更をプッシュしてリポジトリーに戻すこともできます。ここでは後者の、権限付きプッシュ・モデルのみを説明します。


SVN でディレクトリーを保持する

では、一般的で簡単な例から始めることにし、ディレクトリーの内容を SVN で追跡しましょう。そのためには SVN サーバーが必要であり、そして当然ですが、ファイルの入ったディレクトリーと、その SVN サーバーでのアカウントを、少なくとも 1 つのパスでのコミット権限付きで持っている必要があります。まず、ディレクトリーの追加とコミットから始めましょう。

リスト 1. SVN でディレクトリーを設定する
% svn co http://svnserver/...some path here.../top
% cd top
% cp -r ~/my_directory .
% svn add my_directory
% svn commit -m 'added directory'

これによって何ができるのでしょう。こうすることによって、このディレクトリーにコミットされたすべてのファイルの最新バージョンを得ることができます。またファイルの削除や、ファイルのリネーム、新しいファイルやディレクトリーの作成、既存のファイルへの変更のコミット等々を行うこともできます。

リスト 2. SVN での基本的なファイル操作
# get latest
% svn up
# what's the status?
% svn st
# delete files
% svn delete
# rename files (really a delete + add that keeps history)
% svn rename
# make directory
% svn mkdir
# add file
% svn add
# commit changes (everything above, plus any content changes)
% svn commit

これらのコマンドについて、ここでは詳細に検証しませんが、これらのコマンドを必ず頭に入れておく必要があります。これらのコマンドに関するヘルプが必要な場合には、svn help COMMAND と入力すると、Subversion が基本的なヘルプを表示してくれます。詳細についてはマニュアルを参照してください。


Git でディレクトリーを保持する

SVN の例で行ったことと同じことをしてみましょう。先ほどと同様、既にデータの入ったディレクトリーがあるものとします。

リモート・サーバーとして、ここでは無料の github.com サービスを使いますが、もちろんお望みであれば独自のサーバーをセットアップすることができます。GitHub を使うと、リモートの Git リポジトリーを容易に操作することができます。この記事の執筆時点で、無料のアカウントは 300 MB のデータに制限されており、またリポジトリーを公開しなければなりません。私は「tzz」というユーザーとしてサインアップし、「datatest」という公開リポジトリーを作成しました。このリポジトリーを誰もが自由に使うことができます。私はリポジトリーへのアクセスに自分の SSH 公開鍵を使うことにしましたが、SSH 公開鍵を用意していない方はリポジトリーへのアクセス用に生成する必要があります。また、Gitorious サーバーや repo.or.cz を試すこともできます。git.or.cz のウィキには、Git を使ったホスティング・サービスの長いリストがあります。

GitHub の良い点の 1 つは、親切なことです。GitHub は、Git のセットアップに必要なコマンドは何かを正確に教えてくれる上、リポジトリーを初期化してくれます。ここではこれらを順次説明します。

まず、Git をインストールし (インストール方法はプラットフォームごとに異なります)、Git を初期化する必要があります。Git のダウンロード・ページ (「参考文献」を参照) には、プラットフォームに応じていくつかのオプションが一覧表示されています。(Mac OS X の場合、私は port install git-core コマンドを使いましたが、最初に MacPorts をセットアップする必要があります。また、スタンドアロンの MacOS X 用 Git インストーラーもあり、Git のダウンロード・ページからリンクが張られています。ほとんどの人の場合、後者の方がうまくいくはずです。)

Git をインストールできたら、以下のコマンドを使って基本的なセットアップを行います (当然ですが、皆さん自身のユーザー名と E メール・アドレスを使う必要があります)。

リスト 3. Git の基本的なセットアップ
% git config --global user.name "Ted Zlatanov"
% git config --global user.email "tzz@bu.edu"

この段階で既に、SVN との違いがわかるかもしれません。SVN では、ユーザー ID はサーバー・サイドにあり、サーバーがユーザー ID を決めました。Git では、もしユーザーが望むのであれば、「The Wonderful Monkey Of Wittgenstein (ウィトゲンシュタインの素晴らしい猿)」という ID を使うこともできます (私はその誘惑に耐えました)。

次に、データ・ファイルをセットアップし、それらのファイルを使ってリポジトリーを初期化します。(GitHub は便利なことに、SVN の公開リポジトリーからインポートすることもできます。)

リスト 4. ディレクトリーのセットアップと最初のコミット
# grab some files
% cp -rp ~/.gdbinit gdbinit
% mkdir fortunes
% cp -rp ~/.fortunes.db fortunes/data.txt
# initialize
% git init
# "Initialized empty Git repository in /Users/tzz/datatest/.git/"
# add the file and the directory
% git add gdbinit fortunes
% git commit -m 'initializing'
#[master (root-commit) b238ddc] initializing
# 2 files changed, 2371 insertions(+), 0 deletions(-)
# create mode 100644 fortunes/data.txt
# create mode 100644 gdbinit

上記に示すように、Git ではファイルのモードについての情報をコミットの出力に含めています。100644 は、これらのファイルのパーミッション・ビットを 8 進で表現しています。これを気にする必要はありませんが、2371 insertions には戸惑います。変更したのは 2 つのファイルのみではなかったのでしょうか。実は、この 2371 という数字は挿入された行数を表しています。0 deletions が示すように、もちろん何も削除していません。

新しい変更を GitHub サーバーにプッシュする場合にはどうするのでしょう。ドキュメントには、「origin」というリモート・サーバー (サーバーの名前はどんな名前でも構いません) を追加する方法が説明されています。また、いずれかの Git コマンド (例えば git remote など) について詳しく学びたい場合には、git remote --help または git help remote と入力します。これはコマンドライン・ツールでは典型的な方法であり、SVN の場合も非常によく似ています。

リスト 5. 変更をリモートにプッシュする
# remember the remote repository is called "datatest"?
% git remote add origin git@github.com:tzz/datatest.git
# push the changes
% git push origin master
#Warning: Permanently added 'github.com,65.74.177.129' (RSA) to the list of known hosts.
#Counting objects: 5, done.
#Delta compression using 2 threads.
#Compressing objects: 100% (4/4), done.
#Writing objects: 100% (5/5), 29.88 KiB, done.
#Total 5 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# * [new branch]      master -> master

上記リスト 5 では OpenSSH の警告が出ていますが、これは github.com が以前は既知のホストでなかったためであり、何も心配する必要はありません。

Git のメッセージは、言うなれば、徹底しています。理解しやすい SVN のメッセージとは異なり、Git は mentat によって mentat のために作成されています (訳注: mentat とは Frank Herbert が描いた Dune という世界の人間コンピューターのこと)。もし皆さんが、Frank Herbert が描いた Dune という世界から来た人であり、人間コンピューターとして訓練されている人 (つまり mentat) であれば、皆さんはおそらく (単に自分がその能力を持っているという理由で) 独自のバージョンの Git を既に作成してあるはずです。それ以外の私達にとっては、デルタ圧縮 (Delta compression) や、デルタ圧縮で使われているスレッド数 (threads) などは、あまり重要ではなく、単に頭痛の原因になるだけです。

ここでは SSH でプッシュを行いましたが、他のプロトコル (HTTP、HTTPS、rsync など) やファイルを使うこともできます。git push --help と入力して表示される内容を参照してください。

SVN と Git との間の最も重要で決定的な違いが、ここにあります。SVN のコミットは「これを中央のサーバーにプッシュしなさい」という意味です。SVN ではコミットするまで、変更は宙に浮いています。一方で Git にはローカル・リポジトリーがあり、リモート・サイドで何が起こっていようが、コミットはローカルで行われます。そして Git でプッシュをすると、実質的にローカルのリポジトリーの状態をリモート・サーバーに反映し、同期させることになるのです。

それでは最後に、何が起こったのか、Git のログを見てみましょう。

リスト 6. Git のログ
% git log
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing

ログの中にはコミットしかありません (SVN のリビジョン番号とは異なり、ランダムに見える長いコミット ID があることに注意してください)。git push による同期を示すものは何もありません。


Git を使った共同作業

ここまでの段階では、SVN の置き換えとして Git を使ってきました。もちろん、面白くするためには複数のユーザーとチェンジセットを含める必要があります。そこで、リポジトリーを別のマシンにチェックアウトします (この場合は Ubuntu GNU/Linux を実行しているので、git ではなく git-core をインストールする必要があります)。

リスト 7. 別の Git ID をセットアップし、リポジトリーをチェックアウトする
% git config --global user.name "The Other Ted"
% git config --global user.email "tzz@bu.edu"
% git clone git@github.com:tzz/datatest.git
#Initialized empty Git repository in /home/tzz/datatest/.git/
#Warning: Permanently added 'github.com,65.74.177.129' (RSA) to the list of known hosts.
#remote: Counting objects: 5, done.
#remote: Compressing objects: 100% (4/4), done.
#Indexing 5 objects...
#remote: Total 5 (delta 0), reused 0 (delta 0)
# 100% (5/5) done
% ls datatest
#fortunes  gdbinit
% ls -a datatest/.git
# .  ..  branches  config  description  HEAD  hooks  index  info  logs  objects  refs
% ls -a datatest/.git/hooks
# .  ..  applypatch-msg  commit-msg  post-commit  post-receive post-update
#  pre-applypatch  pre-commit  pre-rebase  update

この場合も、このマシンから SSH を使って GitHub とやり取りしたことはない、という OpenSSH の警告があることに注意してください。git clone コマンドは SVN のチェックアウトと似ていますが、1 つに合成された内容 (特定のリビジョンまたは最新リビジョン時点でのスナップショット) を取得するのではなく、リポジトリー全体を取得しています

ここでは、本当にすべてを取得することを示すために、datatest/.git ディレクトリーとその下の hooks サブディレクトリーの内容を表示させています。Git は SVN とは異なり、デフォルトでは何も秘密にはしません。SVN ではデフォルトでリポジトリーをプライベートに保持し、スナップショットへのアクセスのみを許可します。

ちなみに、Git リポジトリーに対して (すべてのコミットの時点で、あるいは別の時点で) 何らかのルールを強制したい場合には、フックでそれらを指定します。フックは SVN のフックとまったく同様で、シェル・スクリプトであり、「成功した場合はゼロを返し、失敗した場合はそれ以外を返す」という UNIX の標準的な慣習に従います。ここではフックについての詳細には触れませんが、チームで Git を使用したいと考えている場合には、必ずフックの資料を読む必要があります。

さてここで、「The Other Ted (もう 1 人の Ted)」が浮かれ騒ぎ、マスター・ブランチ (大まかに言えば SVN の TRUNK と同じもの) に新しいファイルを追加しようとしており、また gdbinit ファイルを少し変更した新しいブランチを作ろうとしています。

リスト 8. ファイルを追加し、新しいブランチを作成する
# get a file to add...
% cp ~/bin/encode.pl .
% git add encode.pl
% git commit -m 'adding encode.pl'
#Created commit 6750342: adding encode.pl
# 1 files changed, 1 insertions(+), 0 deletions(-)
# create mode 100644 encode.pl
% git log
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing
% git branch empty-gdbinit
% git branch
#  empty-gdbinit
#* master
% git checkout empty-gdbinit
#Switched to branch "empty-gdbinit"
% git branch
#* empty-gdbinit
#  master
% git add gdbinit
% git commit -m 'empty gdbinit'
#Created commit 5512d0a: empty gdbinit
# 1 files changed, 0 insertions(+), 1005 deletions(-)
% git push
#updating 'refs/heads/master'
#  from b238ddca99ee582e1a184658405e2a825f0815da
#  to   675034202629e5497ed10b319a9ba42fc72b33e9
#Generating pack...
#Done counting 4 objects.
#Result has 3 objects.
#Deltifying 3 objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 0), reused 0 (delta 0)

これは長い例ですが、これを見て皆さんが居眠りしなかったことを祈ります。もし居眠りをした場合には、Git リポジトリーがチェンジセットのワルツを永遠に繰り返しながら同期している様子を夢に見たことを願います (心配しなくても、いずれ皆さんはそうした夢を見るはずです)。

まず、ファイルを追加し (encode.pl を追加した 1 行のみ)、このファイルをコミットしています。コミットした後も、GitHub にあるリモート・リポジトリーには私が変更を行ったことがまったくわかりません。次に、empty-gdbinit という新しいブランチを作成し、そのブランチに切り換えています (これは git checkout -b empty-gdbinit と入力して切り換えることもできます)。このブランチで gdbinit ファイルを空にし、その変更をコミットしています。最後に、リモート・サーバーにプッシュしています。

マスター・ブランチに切り換えると、ログの中には空のgdbinit が見あたりません。つまり各ブランチには独自のログがあります。これは妥当なことです。

リスト 9. ブランチ間のログを調べる
# we are still in the empty-gdbinit branch
% git log
#commit 5512d0a4327416c499dcb5f72c3f4f6a257d209f
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    empty gdbinit
#
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing
% git checkout master
#Switched to branch "master"
% git log
#commit 675034202629e5497ed10b319a9ba42fc72b33e9
#Author: The Other Ted <tzz@bu.edu>
#Date:   ...commit date here...
#
#    adding encode.pl
#
#commit b238ddca99ee582e1a184658405e2a825f0815da
#Author: Ted Zlatanov <tzz@lifelogs.com>
#Date:   ...commit date here...
#
#    initializing

プッシュを行うと、Git は、(GitHub のサーバー上にある)「あの、encode.pl という新しいファイルを見なさい」という指示を出します。

すると GitHub の Web インターフェースは encode.pl を表示します。しかし GitHub には、まだブランチは 1 つしかありません。なぜ empty-gdbinit ブランチは同期されなかったのでしょう。その理由は、Git のデフォルトでは、ブランチとそれらのブランチの変更がプッシュされることを想定していないためです。そこで、すべてをプッシュする必要があります。

リスト 10. すべてをプッシュする
% git push -a
#updating 'refs/heads/empty-gdbinit'
#  from 0000000000000000000000000000000000000000
#  to   5512d0a4327416c499dcb5f72c3f4f6a257d209f
#updating 'refs/remotes/origin/HEAD'
#  from 0000000000000000000000000000000000000000
#  to   b238ddca99ee582e1a184658405e2a825f0815da
#updating 'refs/remotes/origin/master'
#  from 0000000000000000000000000000000000000000
#  to   b238ddca99ee582e1a184658405e2a825f0815da
#Generating pack...
#Done counting 5 objects.
#Result has 3 objects.
#Deltifying 3 objects...
# 100% (3/3) done
#Writing 3 objects...
# 100% (3/3) done
#Total 3 (delta 1), reused 0 (delta 0)

この場合も、mentat インターフェースが大活躍しています。しかし、これを見れば必要なことを判断することができます。私達は mentat ではないかもしれませんが、少なくとも 0000000000000000000000000000000000000000 が何らかの特別な開始タグだと判断する一般的な感覚は持ち合わせています。また、リスト 9 のログから、5512d0a4327416c499dcb5f72c3f4f6a257d209f というタグが empty-gdbinit ブランチでの最後の (そして唯一の) コミットであることがわかります。それ以外の部分は大部分のユーザーにとってアラム語のように見えるかもしれませんが、これらは重要ではありません。そして GitHub は新しいブランチと、そのブランチへの変更を示しています。

ファイルの管理には git mvgit rm を使うことができます (それぞれファイルのリネームと削除を行います)。


まとめ

この記事では、Git の基本的な概念と、Git を使って単純なディレクトリーの内容をバージョン管理の下に置く方法を説明し、その中で Git と Subversion の比較を行いました。また簡単な例を使ってブランチについて説明しました。

第 2 回では、マージ、diff の生成、その他のいくつかの Git コマンドについて説明します。皆さんには、Git のマニュアルが非常にわかりやすいのでそれを読むこと、あるいは少なくとも Git のチュートリアルをやってみることを強く推奨します。どれも Git のホームページからアクセスすることができますので、少し時間をかけて調べてみてください (下記の「参考文献」にリンクがあります)。SVN ユーザーの観点から見ると、それ以上のことは必要ありません。

その一方、Git は非常にリッチな DVCS です。Git の機能についてさらに学ぶことで、そうした機能を使って VCS のワークフローを単純化し、改善できるはずです。そしてさらに、Git リポジトリーの夢を 1、2 回見られるかもしれません。

参考文献

学ぶために

製品や技術を入手するために

議論するために

  • My developerWorks community に参加してください。個人プロファイルとカスタムのホームページを利用することで、皆さんの関心事項に合わせて developerWorks を調整することができ、また他の developerWorks ユーザーとやり取りすることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。プロフィールで選択した情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source, Web development
ArticleID=426830
ArticleTitle=Subversion ユーザーのための Git: 第 1 回 Git 入門
publish-date=08042009