DVCSの世界にあるのはGitだけではありません。 事実、Git以外にも様々なシステムが存在し、分散バージョン管理を正しく行う方法について、それぞれが独自の見方を持っています。 Gitを除くと、もっとも広く使われているのは Mercurial です。Git と Mercurialは多くの点で似通っています。
良いニュースとして、 Git のクライアントサイドの動作がお好みで、しかし作業中のプロジェクトでは Mercurial でソースを管理しているという場合、 Mercurial でホストされているリポジトリのクライアントに Git を使用するという方法があります。 Git はリモートを通してサーバリポジトリとやりとりしているので、このブリッジがリモートヘルパーとして実装されているのは驚くほどのことでもないと思います。 プロジェクト名は git-remote-hg で、 https://github.com/felipec/git-remote-hg から取得できます。
まず、 git-remote-hg をインストールする必要があります。 ここでは基本的に、そのファイルをどこかパスの通った場所に置く必要があります。以下のような感じです。
$ curl -o ~/bin/git-remote-hg \
https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
…ここでは ~/bin
が $PATH
に含まれていることを仮定しています。
git-remote-hg にはもう一つ依存先があります。 Python の mercurial
ライブラリです。
Python をインストール済みなら、これは次のようにシンプルなコマンドで行えます。
$ pip install mercurial
(Python をインストールしていないなら、まず https://www.python.org/ からPython を入手してください。)
最後に必要なのは Mercurial のクライアントです。 インストール済みでないなら、 https://mercurial-scm.org/ から入手してインストールしてください。
これで準備が整いました。 必要なのはプッシュが可能な Mercurial リポジトリだけです。 幸いなことに、 Mercurial リポジトリならどれでもこの操作が可能です。そのため、 Mercurial の使い方を学ぶときにみんなが使う "hello world" リポジトリを使用することにします。
$ hg clone http://selenic.com/repo/hello /tmp/hello
これで、都合のいい ``サーバサイド'' のリポジトリができたので、以降では典型的なワークフローを見ていきます。 これから見ていくように、 Git と Mercurial はよく似ているため、食い違う箇所はそう多くありません。
Git でいつもやるように、まずクローンをします。
$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program
標準的な git clone
コマンドを使用して Mercurial リポジトリを操作しているのが分かると思います。
これは git-remote-hg が非常に低いレベルで動作しているためです。 git-remote-hg は Git が HTTP/S プロトコルを実装しているのと同じようなメカニズム(リモートヘルパー)を使用しています。
Git と Mercurial は両方とも、すべてのクライアントがリポジトリの歴史の完全なコピーを持つように設計されています。そのためこのコマンドは、プロジェクトのすべての歴史を含む完全なクローンを作成します。また、この処理は非常に高速に行われます。
git logコマンドは2つのコミットを表示しています。最新のコミットは大量の参照から指されています。
実は、これらの中のいくつかは、実際には存在しません。
.git
ディレクトリの中に実際には何が入っているか見てみましょう。
$ tree .git/refs
.git/refs
├── heads
│ └── master
├── hg
│ └── origin
│ ├── bookmarks
│ │ └── master
│ └── branches
│ └── default
├── notes
│ └── hg
├── remotes
│ └── origin
│ └── HEAD
└── tags
9 directories, 5 files
git-remote-hg は、物事をより Git 風にしようとしているわけですが、内部的には、2つの微妙に異なるシステムの間のマッピングを管理しています。
refs/hg
ディレクトリには実際のリモート参照が格納されています。
例えば、 refs/hg/origin/branches/default
は `ac7955c'' で始まるSHA-1( `master
が指しているコミットを表している)を含むGitの参照ファイルです。
そのため、 refs/hg
ディレクトリは refs/remotes/origin
の模造品のようなものとも言えます。ただし、ブックマークとブランチの区別が追加されています。
notes/hg
ファイルは、 git-remote-hg が Git のコミットハッシュと Mercurial のチェンジセットIDを対応付ける際の開始点となります。
ちょっと見てみましょう。
$ cat notes/hg
d4c10386...
$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800
Notes for master
$ git ls-tree 1781c96...
100644 blob ac9117f... 65bb417...
100644 blob 485e178... ac7955c...
$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9
refs/notes/hg
は Git オブジェクトデータベース中にあるツリーを指しており、その内容は他のオブジェクトの名前つきリストになっています。
git ls-tree
はツリー中の要素のモード、タイプ、オブジェクトハッシュ、およびファイル名を出力します。
ツリー中の要素の一つについて掘り下げていくと、その実体は ac9117f'' (
0a04b98'' ( master
が指しているコミットの SHA-1 ハッシュ)という名前の blob で、内容は default
ブランチの先端の Mercurial チェンジセットのID)であることが分かります。
よいニュースとして、これらすべてのことについて、我々が気にする必要はほとんどありません。 典型的なワークフローは、 Git でリモートに対して作業をする場合と大差ありません。
以降の話をする前に、もう一つ注意しておかなければならないことがあります。 ignoreファイルです。
Mercurial と Git はこの点について非常に似通ったメカニズムを使用しています。ですが、おそらく実際に .gitignore
ファイルを Mercurial リポジトリへコミットしたい、ということはないでしょう。
幸いなことに、 Git にはローカルからディスク上のリポジトリへファイルを登録する際に、指定したファイルを無視する方法があります。Mercurial のフォーマットは Git と互換性があるので、単にファイルをコピーするだけで済みます。
$ cp .hgignore .git/info/exclude
.git/info/exclude
ファイルは .gitignore
と同様の働きをしますが、コミットには含まれません。
現在、何らかの作業をやり終え、 master
ブランチにはコミットがいくつか作成されており、それをリモートリポジトリへプッシュできる状態にあるとしましょう。
現時点では、リポジトリは次のような内容になっています。
$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program
master
ブランチは origin/master
よりコミット2つぶん進んでいますが、これら2つのコミットはローカルのマシン上にしかありません。
ここで、誰か他の人が、何か重要な作業をこれと同時に行っていたらどうなるか見てみましょう。
$ git fetch
From hg::/tmp/hello
ac7955c..df85e87 master -> origin/master
ac7955c..df85e87 branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program
--all
フラグを指定したため、 `notes'' 参照が表示されていますが、これは git-remote-hg が内部的に使用しているものなので、無視して構いません。
残りが期待していた内容です。 `origin/master
はコミット1つぶん進んでおり、現在この歴史は枝分かれした状態にあります。
この章で扱っている他のシステムと異なり、 Mercurial にはマージをハンドリングする機能が備わっているので、ややこしいことをする必要は何もありません。
$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
hello.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
* 0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program
完璧です。 テストを実行して、結果はすべて正常でした。これで、成果物をチームの他のメンバーと共有できる状態になりました。
$ git push
To hg::/tmp/hello
df85e87..0c64627 master -> master
これで完了です! Mercurial リポジトリを見てみれば、期待していた内容が分かるはずです。
$ hg log -G --style compact
o 5[tip]:4,2 dc8fa4f932b8 2014-08-14 19:33 -0700 ben
|\ Merge remote-tracking branch 'origin/master'
| |
| o 4 64f27bcefc35 2014-08-14 19:27 -0700 ben
| | Update makefile
| |
| o 3:1 4256fc29598f 2014-08-14 19:27 -0700 ben
| | Goodbye
| |
@ | 2 7db0b4848b3c 2014-08-14 19:30 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard "hello, world" program
番号 2 のチェンジセットは Mercurial が作成したもので、番号 3 および 4 のチェンジセットは Git で作成したコミットを git-remote-hg がプッシュして作成したものです。
Git のブランチは1種類しかありません。コミットに合わせて移動する参照です。 Mercurial では、この種の参照は ``ブックマーク'' と呼ばれており、 Git のブランチとほぼ同じように振る舞います。
Mercurial の言う `ブランチ'' は Git のそれよりもっと重量級の概念です。
ブランチの上でチェンジセットが作成された場合、ブランチは チェンジセットと一緒に 記録されます。つまり、常にリポジトリの歴史に残ります。
`develop
ブランチの上で作成されたコミットの例を次に示します。
$ hg log -l 1
changeset: 6:8f65e5e02793
branch: develop
tag: tip
user: Ben Straub <[email protected]>
date: Thu Aug 14 20:06:38 2014 -0700
summary: More documentation
``branch'' で始まる行に注目してください。 Git はこれを完全に複製することはできません(また、する必要もありません。いずれのタイプのブランチも Git では参照として表現されるため)が、 Mercurial にとってはこの違いが問題となるため、 git-remote-hg はこの違いを理解している必要があります。
Mercurial のブックマークを作成するのは、 Git のブランチを作成するのと同様に簡単です。 Git 側では、
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
* [new branch] featureA -> featureA
これだけです。 Mercurial 側では、これは次のように見えます。
$ hg bookmarks
featureA 5:bd5ac26f11f9
$ hg log --style compact -G
@ 6[tip] 8f65e5e02793 2014-08-14 20:06 -0700 ben
| More documentation
|
o 5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
|\ Merge remote-tracking branch 'origin/master'
| |
| o 4 0434aaa6b91f 2014-08-14 20:01 -0700 ben
| | update makefile
| |
| o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| | goodbye
| |
o | 2 f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard "hello, world" program
リビジョン5に付いている新しいタグ [featureA]
に注目してください。
これらの挙動は Git 側から見ると Git のブランチと非常によく似ていますが、一つ例外があります。 Git の側からブックマークを削除することはできません(これはリモートヘルパーの制限によります)。
`重量級'' の Mercurial のブランチ上で作業をすることもできます。 `branches
名前空間にブランチを追加します。
$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
* [new branch] branches/permanent -> branches/permanent
Mercurial 側ではこれは次のように見えます。
$ hg branches
permanent 7:a4529d07aad4
develop 6:8f65e5e02793
default 5:bd5ac26f11f9 (inactive)
$ hg log -G
o changeset: 7:a4529d07aad4
| branch: permanent
| tag: tip
| parent: 5:bd5ac26f11f9
| user: Ben Straub <[email protected]>
| date: Thu Aug 14 20:21:09 2014 -0700
| summary: A permanent change
|
| @ changeset: 6:8f65e5e02793
|/ branch: develop
| user: Ben Straub <[email protected]>
| date: Thu Aug 14 20:06:38 2014 -0700
| summary: More documentation
|
o changeset: 5:bd5ac26f11f9
|\ bookmark: featureA
| | parent: 4:0434aaa6b91f
| | parent: 2:f098c7f45c4f
| | user: Ben Straub <[email protected]>
| | date: Thu Aug 14 20:02:21 2014 -0700
| | summary: Merge remote-tracking branch 'origin/master'
[...]
``permanent'' という名前のブランチが、 7 とマークされたチェンジセットと一緒に記録されています。
Git 側では、いずれのタイプのブランチで作業をするのも変わりません。普段と同じように、チェックアウト、コミット、フェッチ、マージ、プル、プッシュが行えます。 一つ知っておくべきこととして、 Mercurial は歴史の書き換えをサポートしておらず、追記しか行えません。 対話的リベースと強制プッシュを行うと、 Mercurial リポジトリは次のような内容になります。
$ hg log --style compact -G
o 10[tip] 99611176cbc9 2014-08-14 20:21 -0700 ben
| A permanent change
|
o 9 f23e12f939c3 2014-08-14 20:01 -0700 ben
| Add some documentation
|
o 8:1 c16971d33922 2014-08-14 20:00 -0700 ben
| goodbye
|
| o 7:5 a4529d07aad4 2014-08-14 20:21 -0700 ben
| | A permanent change
| |
| | @ 6 8f65e5e02793 2014-08-14 20:06 -0700 ben
| |/ More documentation
| |
| o 5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
| |\ Merge remote-tracking branch 'origin/master'
| | |
| | o 4 0434aaa6b91f 2014-08-14 20:01 -0700 ben
| | | update makefile
| | |
+---o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| | goodbye
| |
| o 2 f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/ Add some documentation
|
o 1 82e55d328c8c 2005-08-26 01:21 -0700 mpm
| Create a makefile
|
o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm
Create a standard "hello, world" program
チェンジセット 8、 9、 10 が作成され、 permanent
ブランチに紐付けられていますが、古いチェンジセットも残っています。
これは Mercurial を使用している他のチームメンバーを かなり 混乱させるので、できる限り避けましょう。