ブランチを活用した使い方

プログラムや文書はどんどん加筆修正を加えるものである。 ときには、これから行なう修正が成功に終わるか不安なときもあるし、 またときには、公開したバージョンを温存させつつ開発を進めなければ ならないこともある。

リビジョンが進んで行く流れを分岐させて支流を作り、修正は支流で行なって 本流に影響を及ぼさないようにしたり、成功した修正を本流に戻すといった ことができる。リビジョンの流れの分岐をブランチという。

           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 pullhg 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 addhg 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-1pub-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

衝突(conflict)の対処

違うブランチで同じ箇所を修正した場合、マージで衝突が起こる。 この場合、(半自動化する方法もあるが)衝突を手で修正して解決したことを 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 ]... 異なるブランチのチェンジセットの単体取り込み

Mercurialの自習

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