プログラムや文書はどんどん加筆修正を加えるものである。 ときには、これから行なう修正が成功に終わるか不安なときもあるし、 またときには、公開したバージョンを温存させつつ開発を進めなければ ならないこともある。
リビジョンが進んで行く流れを分岐させて支流を作り、修正は支流で行なって 本流に影響を及ぼさないようにしたり、成功した修正を本流に戻すといった ことができる。リビジョンの流れの分岐をブランチという。
ver1.0 ver2.0
開発開始 ----+-----------+--------[A]------------ - - - → 3.0?
\ \ v2.1 v2.2
\ +--------+------+ - -→ ver2.x - - →
\ ver1.1 v1.2
+-----+------+ - - - - → ver1.x - - →
サポートが必要な製品は、最新版だけを開発維持するだけでは不十分で、 過去に出したバージョンに対する微修正(マイナーバージョンアップ)も 継続して行なう必要がある。また、複数のブランチでの修正がまったく 独立したものというわけではなく、重大な不具合修正など すべてのブランチで共通の修正(たとえば上図中の[A])があれば、 それは他のブランチにも同じ修正を適用する必要がある。
rev.1 rev.2 rev.4 マージ rev.6
開発開始 -----+--------+-------+-------------------+---
\ /
\ rev.3 rev.5 /
+--------+------+-----+
実験用ブランチ →成功
rev.1 rev.2
開発開始 -----+--------+-------------------------------
\
\ rev.3 rev.4
+--------+------+---→ ×破棄
実験用ブランチ →失敗
個人で開発を進める場合でも、大がかりな変更を行なうときに、 一時的に別の流れを作り、本流に影響が出ないように実験的開発を 進めた上で、成功したら本流に取り込み、失敗したら破棄するという 切り分けができれば安全かつ効率的に開発を進められる。
Mercurialではリポジトリそのものを分岐させるブランチと、 1つのリポジトリの中で論理的に別の流れを作るブランチ(名前つきブランチ)の 2種類のブランチ運用が選択できる。
hg init
を行なったディレクトリ(とその下位のディレクトリ)は
1つの完結したリポジトリとなる。リポジトリそのもののクローンを作り、
あらゆる修正を元のリポジトリとは別の場所で行なうことができる。
リポジトリのクローンは hg clone
で作成する。
hg clone クローン元リポジトリ [ クローン先ディレクトリ ]
クローン元リポジトリ にはリポジトリのURLを指定する。
指定できるURLの書式一覧は hg help urls
で得られる。
hg help urls
URL Paths
Valid URLs are of the form:
local/filesystem/path[#revision]
file://local/filesystem/path[#revision]
http://[user[:pass]@]host[:port]/[path][#revision]
https://[user[:pass]@]host[:port]/[path][#revision]
ssh://[user[:pass]@]host[:port]/[path][#revision]
(以下省略)
実験用に作成した リポジトリのクローンを作成してみる。
pwd /home/taro/hgdir cd .. hg clone hgdir clonedir
複製した2つのリポジトリは互いに独立した修正を行なえ、 なおかつその修正を push/pull でやりとりすることができる。
cd clonedir ls calendar vi calendar (いろいろ編集) hg ci -m iroiro-1
クローン先である clonedir
は、そのまま親と独立した
更新をして行ってもよいし、更新を親に還元することもできる。戻すには
hg push
を行なう。pushする前に、hg outgoing
で内容を確認するのが望ましい。
(clonedir内で) hg outgoing (こちらにしか存在しないチェンジセット一覧を表示) hg outgoing -p (パッチも含めて表示) hg push (送り込む)
親側に移動して更新を取り込む。リポジトリをワーキングコピーに
反映させるには hg update
を用いる。
pushd ../hgdir cat calendar (古いままであることを確認) hg update cat calendar (更新されているか確認)
今度は親側で修正して、それを子側でpullしてみる。
(hgdirで) vi calendar (なにか修正) hg ci -m nanika-1 pushd (clonedirに戻る) hg incoming -p (何が入って来るか確認) hg pull (リポジトリに取り込む) hg update (リポジトリをワーキングディレクトリに反映)
なお、hg pull -u
とすると、hg pull
と
hg update
をまとめて行なう。
単一リポジトリ内で論理的な流れを作り、そこで改変を進めることができる。
この流れを名前つきブランチという。名前つきブランチは
hg branch
で作ることができる。
hg branch jikken marked working directory as branch jikken echo 'これは実験' >> calendar echo 'ファイルも足しちゃえ' > newfile hg add (新規ファイル追加) hg ci -m jikken-1 (コミット)
ここで本流に戻る。現在存在するブランチを hg branches
で確認する。
hg branches
jikken 3:77c081fc8cd7
default 2:3a8c8b4f7e5f (inactive)
`default' は本流のブランチである。名前つきブランチを切り替えるには
hg update
(省略形 up) を使う。
hg up -C default ls hg up -C jikken ls
支流ブランチでの変更を本流に還元するには hg merge
する。
hg up -C default (まずdefaultに戻る) hg merge jikken (マージ) hg ci -m merged (マージしたら必ずコミット)
実際に起こりそうな場面を想定して、実験用ブランチを作って
開発を進めてみる。ここでは ~/hgdir
で
開発を進めるものとして、ブランチ作成にクローンリポジトリと
名前つきブランチを使うものとして実例を示す。
+-----[pub-1ブランチ]---→ v1.0公開版のサポート / 開始 -----+---+----[defaultブランチ]--------→ 本流 v1.0 \ +------[func-1ブランチ]------→ 実験的開発
まず、ファイルを少し増やしておく。
cat <<EOF > index.html <html> <head> <title>My Project!</title> </head> <body> <h1>マイプロジェクト</h1> <p>chmod +x したら ./myproj.rb で起動してね。</p> </body> </html> EOF cat <<EOF >myproj.rb #!/usr/bin/env ruby # coding: euc-jp srand judge = 0 hand = %w,グー チョキ パー, while judge == 0 print "グーは1、チョキは2、パーは3、どれ? :" human = gets.to_i-1 com = rand(3) printf("ぽんっ キミ %s : %s わし\n", hand[human], hand[com]) judge = (3+human-com) % 3 if judge == 2 puts "キミの勝ちだ、めで" elsif judge == 1 puts "キミの負けだ、けけ" else puts "あいこでしょっ" end end EOF
ファイルを追加したので登録し、コミットする。
hg add
と hg ci
に分けてもよいが、
hg ci
に -A
オプションを指定すると自動的に
add も行なうのでこれを利用する。
hg ci -A -m 'HTMLとプログラムを新規追加'
adding index.html
adding myproj.rb
hg manifest
で管理下にあるファイル一覧を確認。
hg manifest
.hgtags
calendar
index.html
myproj.rb
機能1を追加するための実験用ブランチを func-1、 公開版での重要修正用ブランチを pub-1 とする。
クローンリポジトリの場合
hg clone
でリポジトリのクローンを作成する。
hgdir
はあとで使うので、本流用ブランチとして
main
を作り、そこから分岐させて func-1
と pub-1
を作る。
cd .. hg clone hgdir main hg clone main func-1 hg clone main pub-1
名前つきブランチの場合
作業中リポジトリのディレクトリ内で行なう。
hg branch func-1 hg tag branch-func-1-start hg branch pub-1 hg tag branch-pub-1-start hg branches (確認) pub-1 9:c85c1938901e func-1 8:49b970a47e23 (inactive) default 7:7d26a03ddb3d (inactive)
名前つきブランチは hg branch
で名前を付けてから
最初になんらかの修正をしたときに実際に流れが始まるので、
慣習的にはそれと分かるタグを作成することで開始アクションとする。
ここでは、
それぞれ進めるものとして個別の具体作業を追跡してみる。
func-1 では myproj.rb
に対して以下の修正を行う。
com = rand(3) ↓ com = rand(4) % 3 # グーの出る確率を上げる
pub-1 では index.html
に対して以下の修正を行う。
<p>chmod +x したら ./myproj.rb で起動してね。</p> ↓ <p>chmod +x myproj.rb したら ./myproj.rb で起動してね。</p>
さらにもうひとつ、本流(default)では myproj.rb
に対して以下の修正を行う。
com = rand(3) ↓ com = rand(3) # 0、1、2のいずれかが入る
見て分かるように、myproj.rb
の同じ箇所を2つのブランチ
(func-1とdefault)で別々に修正している。あとで問題(衝突)が起こるので
少し注意して読み進めよう。以下、3つのブランチでそれぞれの修正を行なう。
クローンリポジトリの場合
func-1、pub-1、本流と順に切り替えて作業を行なう。
cd func-1 vi myproj.rb (修正) hg ci -m "グーを出やすく" (以上3ステップは、Emacs で func-1/myproj.rb を編集し C-x v v してもよい. 以下同様) cd ../pub-1 vi index.html hg ci -m "chmodを詳しく" cd ../main vi myproj.rb hg ci -m "コメント文追加"
名前つきブランチの場合
ディレクトリはそのままでブランチを切り替えつつ作業する。
hg up
によるブランチ切り替えはコマンドラインで
の作業が必要。
hg up -C func-1 vi myproj.rb (編集) hg ci -m "グーを出やすく" (編集とコミットは、Emacs でもよい. 以下同様) hg up -C pub-1 vi index.html hg ci -m "chmodを詳しく" hg up -C default vi myproj.rb hg ci -m "コメント文追加"
ここでブランチの様子を確認する。hg log
で確認できるがこれだけでは今一つぴんと来ないので、
支流に分かれていることを確認しやすいよう graphlog モジュールを組み込む。
~/.hgrc
を以下のようにする(既に何かあるなら追加)。
[extensions] hgext.graphlog=
サブコマンド glog が使えるようになる。
hg up -C default hg glog | less hg up -C func-1 hg glog | less
@ が今いる論理的な位置である。
将来的に継続したい修正は本流に取り込んでいく。 ここでは、pub-1 ブランチの成果を永続的なものとして本流に取り込む。
クローンリポジトリの場合
異なるリポジトリ間では、チェンジセットを push/pull でやりとりし、 それを update でワーキングコピーに反映させる。
cd ../pub-1 hg paths (親リポジトリの確認) hg outgoing (送出の事前確認、hg outでも可) hg push abort: push creates new remote heads! (did you forget to merge? use push -f to force)
本流に別の更新があるときはpushできない。 先に本流の更新を取り込んでマージする。
hg in (入って来る修正の確認) hg pull -u pulling from /home/taro/main searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) not updating, since new heads added (run 'hg heads' to see heads, 'hg merge' to merge) (指示に従いマージする) hg merge 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) (コミットしてから再度pushする) hg ci -m 'マージ完了' hg outgoing hg push pushiing to /home/taro/main searching for changes adding changesets adding manifests adding file changes added 2 changesets with 1 changes to 1 files cd ../main hg up 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
例にあるように、2つのリポジトリが別々の修正を持つときは、 先に pull して手元で修正をマージしてから push する。
修正が反映されたことが確認でき、なおかつ pub-1 ブランチが もはや不要ということなら pub-1 リポジトリは消してよい(継続使用してももちろん構わない)。
rm -rf ../pub-1
名前つきブランチの場合
チェンジセットを取り込みたいブランチ(本流)に切り替えてから、
欲しいチェンジセットがあるブランチを hg merge
で指定する。
hg up -C default hg branch (確認) default hg merge pub-1 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit)
出力にあるようにマージしたらコミットすることが必要。
hg status M .hgtags M index.html (Mは修正されているという記号) hg ci -m "マージ完了" hg glog | less
違うブランチで同じ箇所を修正した場合、マージで衝突が起こる。
この場合、(半自動化する方法もあるが)衝突を手で修正して解決したことを
hg resolve -m
で伝える。
クローンリポジトリの場合
cd ../func-1 hg in (pub-1での更新も含まれる) hg pull -u pulling from /home/taro/main searching for changes adding changesets adding manifests adding file changes added 3 changesets with 2 changes to 2 files (+1 heads) not updating, since new heads added (run 'hg heads' to see heads, 'hg merge' to merge) hg heads changeset: 12:00376c08766d tag: tip parent: 11:75634e218318 parent: 10:1375a3b94644 user: taro@example.com date: Sat Oct 31 12:14:52 2009 +0900 summary: マージ完了 changeset: 9:7cc38a486511 user: taro@example.com date: Fri Oct 30 15:43:35 2009 +0900 summary: グーを出やすく
名前つきブランチの場合
名前つきブランチは default ブランチから func-1 ブランチの チェンジセットを取り込む。
hg up -C default hg merge merging myproj.rb merge: warning: conflicts during merge merging myproj.rb failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg up --clean' to abandon (確認後マージする) hg merge merging myproj.rb merge: warning: conflicts during merge merging myproj.rb failed! 1 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg up --clean' to abandon
いずれも衝突が起きる。
myproj.rb
を見ると衝突のあった箇所が
<<<<<<< ======= >>>>>>>
で挟まれて2つのバージョンが残されている。適宜編集する。
vi myproj.rb
(編集、保存、ESC ZZで終了)
たとえば以下のように修正する(該当行の前後のみ示す)。
human = gets.to_i-1
com = rand(4) # グーの出る確率を上げる (0,1,2,3)
printf("ぽんっ キミ %s : %s わし\n", hand[human], hand[com])
問題なければ hg resolv -m
で解決したことを伝え、コミットする。
hg resolve -l U myproj.rb (Uは未解決の印) hg resolve -m myproj.rb hg resolve -l R myproj.rb (Rは解決の印) hg ci -m merged hg stat ? myprj.rb.orig (未登録ファイル. 不要) rm myproj.rb.orig hg stat
マージが終わったら後始末をする。
クローンリポジトリの場合
マージ完了した、すべての更新情報を含むリポジトリが func-1 なので、本流(main)に戻すか、そのままfunc-1を 本流として使うか決定する。たいていは戻すことになる。
hg push cd ../main hg update 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
名前つきブランチの場合
名前つきブランチをすべてマージするとすべての名前つきブランチが
1つに収束する。hg glog
で視覚的に理解できる。
hg glog|less
@ changeset: 15:574663f12e3d
|\ tag: tip
| | parent: 14:a687bd1a8d02
| | parent: 11:d26bd03e040e
| | user: yuuji@gentei.org
| | date: Tue Nov 03 17:10:48 2009 +0900
| | summary: merged
| |
| o changeset: 14:a687bd1a8d02
| |\ parent: 13:1375a3b94644
| | | parent: 12:303807ffadc0
| | | user: yuuji@gentei.org
| | | date: Sat Oct 31 10:31:01 2009 +0900
| | | summary: マージ完了
| | |
| | o changeset: 13:1375a3b94644
| | | parent: 8:a1377c9363f3
| | | user: yuuji@gentei.org
| | | date: Fri Oct 30 15:44:55 2009 +0900
| | | summary: コメント文追加
:
:
:
これまでの操作は、名前つきブランチの方が手順が少なくて楽に感じるが、 別ブランチでの修正が完全に要らなくなったときのブランチ破棄は クローンリポジトリのほうが圧倒的に簡単である。
名前つきブランチの場合、行なった修正で本流ブランチに 取り込まず破棄したいものがあったとしても名前つきブランチ自体は 破棄せず永続させる。たとえ無効に終わった修正作業でも、 それを履歴として残すことが重要であるからである。しかし 実際には、ある名前つきブランチを履歴ごと削除することも可能で、 それを以下に示す。ただしリポジトリはどこかにクローンが作られ、 のちにマージされる可能性もある。マージのときに一度記録した履歴が 消えていると不都合が生じるのでブランチの履歴ごと削除は そのような心配がまったくないと言える状況だけで使用する。
リポジトリのディレクトリを消すだけでよい。
cd .. rm -rf func-1
基本的には名前つきブランチは抹消しない。したがって、ブランチ作成時に 今後に作るブランチ名と衝突しないよう工夫した名前にする。 そうした上で名前つきブランチを無効化するには、 「ブランチのクローズ」または「ブランチそのものの刈り取り」を行なう。
ブランチのクローズは hg ci --close-branch
で行なう。
hg up -C func-1 hg ci -m close --close-branch
以後、hg branches
による一覧表示が抑制される。
実際にはそのブランチをさらに使い続けることもできる。
名前つきブランチを抹消するには hg strip
を用いる。
消したい名前つきブランチの最も根っこに相当するリビジョンを指定すると、
それ以降のチェンジセットをすべて破棄する。
+---[2]---[3]--→ br-1 ブランチ / [0]---[1]---[4]--[5]--→ default ブランチ \ +----[6]----[7]---→ br-2 ブランチ
たとえば、名前つきブランチの生成過程が上図のようになっているときに、 br-1 ブランチをすべて抹消するのであれば以下のようにする。
hg strip 2
これで default、br-2 ブランチのみが残り以下のようになる。
[0]---[1]---[2]--[3]--→ default ブランチ \ +----[4]----[5]---→ br-2 ブランチ
「マージ」は、ある別のブランチでの修正を現在のブランチに取り込み 一本化する作業である。
-----+----[A]-[B]----------+---------→(修正A,B,C,Dをすべて含む) \ /マージ +----[C]---[D]----+ hoge ブランチ
マージは異なるブランチにあるすべての修正を取り込むことで、 流れを一本化する。そうではなく、異なるブランチの一部の修正のみを 取り込んで、ブランチは独立させたままにすることもできる。
----+----[A]-[B]-----------------→ default (修正A,B,Dを含む) \ ↑(Dのみ移植) +----[C]---[D]-------------→ hoge ブランチ
これを行なうのが hg transplant
で、移植したい
チェンジセットIDと、(必要なら)コピー元ブランチ(orリポジトリ)を指定する。
たとえば上図で、[D]
の修正がチェンジセット 99 ならば、
hg up -C default hg transplant 99
とする。異なるリポジトリにあるチェンジセットを移植することもでき、
その場合 -s
オプションでコピー元となるリポジトリを
同時指定する。
(./main と ./branch があり、mainのチェンジセット:55 を branch に 取り込む場合) cd branch hg -R ../main log | less (logを確認) hg trans -s ../main 55
移植したいチェンジセットは範囲指定することもでき、たとえば 55:60 とすると55〜60が取り込まれる。コロンの前後いずれかを省略すると それ以前すべて、それ以後すべて、の指定となる。
hg transplant
は移植完了したチェンジセットIDを
記憶しているので、間違えて同じチェンジセット(範囲)を繰り返し指定しても
重複適用されることはない。
hg branch ブランチ | ブランチ作成 |
hg branch | 現行ブランチの表示 |
hg branches | ブランチ一覧の表示 |
hg glog | グラフつきログ |
hg manifest | 管理下ファイル一覧 |
hg clone src [ dest ] |
クローン作成 |
hg incoming [ 上流リポジトリ ] |
入って来るチェンジセット確認 |
hg pull [ 上流リポジトリ ] |
チェンジセットを引っ張る |
hg outgoing [ 上流リポジトリ ] |
出て行くチェンジセット確認 |
hg push [ 上流リポジトリ ] |
チェンジセットを送り込む |
hg paths | リモートパス表示 |
hg merge | マージ作業開始 |
hg resolve -l | 未解決衝突の表示 |
hg resolve -m | 衝突解決を知らせる |
hg transplant [ REV ]... |
異なるブランチのチェンジセットの単体取り込み |
hgのサブコマンドの使い方は hg help
で調べられる。
hg help
Mercurial Distributed SCM
list of commands:
add add the specified files on the next commit
addremove add all new files, delete all missing files
annotate show changeset information by line for each file
:
:
各サブコマンド自体の説明は hg help
に続けて
サブコマンドを指定する。
hg help commit
hg commit [OPTION]... [FILE]...
aliases: ci
commit the specified files or all outstanding changes
:
:
各サブコマンドにはエイリアスがあり、たとえば commit は ci で代用できる。また、サブコマンドは最後まで完全に入れなくともよく、 他のサブコマンドと区別できる部分まで入れればよい。
hg ma
(hg manifestと解釈される)
さらに学習するなら、 Mercurialの使い方のチュートリアルなどから始めるとよい。
yuuji@koeki-u.ac.jp