Subversion, Git, Mercurialそれぞれでのcherrypicking

つまみ食いとか青田買いといわれるcherrypickingはある特定のコミットをブランチから抜き出して別のブランチに反映させるというものです。
Subversion, Git, Mercuriaそれぞれのやり方を調べてみました。

まずSubversionいってみましょう。

準備

$ svnadmin create repos
$ svn checkout file:///tmp/repos work
Checked out revision 0.
$ cd work/
$ svn mkdir tags branches trunk
A         tags
A         branches
A         trunk
$ svn commit -m "add initial dir"
Adding         branches
Adding         tags
Adding         trunk

Committed revision 1.

trunkの直下にhelloディレクトリをつくりいくつかコミットしてみます。

$ cd trunk/
$ svn mkdir hello
A         hello
$ svn commit -m "add directory"
Adding         trunk/hello

Committed revision 2.
$ cd hello/
$ echo 1 > a.txt
$ svn add a.txt 
A         a.txt
$ svn commit -m "first commit" a.txt 
Adding         a.txt
Transmitting file data .
Committed revision 3.
$ echo 2 >> a.txt
$ svn commit -m "second commit" a.txt 
Sending        a.txt
Transmitting file data .
Committed revision 4.

次にブランチを作ってワーキングコピーを切り替えます。

$ svn copy file:///tmp/repos/trunk/hello file:///tmp/repos/branches/hello -m "create branch"

Committed revision 5.
$ svn switch file:///tmp/repos/branches/hello
At revision 5.

ブランチで新たにb.txtというファイルを作り変更します。

$ echo 1 > b.txt
$ svn add b.txt 
A         b.txt
$ svn commit -m "first commit for branch" b.txt
Adding         b.txt
Transmitting file data .
Committed revision 6.
$ echo 2 >> b.txt 
$ svn commit -m "second commit for branch" b.txt 
Sending        b.txt
Transmitting file data .
Committed revision 7.

trunkに戻ります。ブランチでつくったb.txtは無いですね。

$ svn switch file:///tmp/repos/trunk/hello
D    b.txt
Updated to revision 7.
$ ls
a.txt

trunkでa.txtに変更を加えます。

$ echo 3 >> a.txt 
$ svn commit -m "third commit for trunk" a.txt 
Sending        a.txt
Transmitting file data .
Committed revision 8.
$ echo 4 >> a.txt 
$ svn commit -m "fourth commit for trunk" a.txt 
Sending        a.txt
Transmitting file data .
Committed revision 9.

ブランチに切り替えます。trunkでa.txtにした変更は無いかわりにb.txtはあります。

$ svn switch file:///tmp/repos/branches/hello
U    a.txt
A    b.txt
Updated to revision 9.
$ ls
a.txt b.txt
$ cat a.txt 
1
2

trunkでa.txtに3を加えた変更であるリビジョン8をつまみ食いします。
svn mergeでつまみ食いして最終的に反映させるにはsvn commitします。
mergeinfoプロパティを見れば何をつまみ食いしたかわかります。

$ svn merge -c 8 file:///tmp/repos/trunk/hello
--- Merging r8 into '.':
U    a.txt
$ svn diff

Property changes on: .
___________________________________________________________________
Added: svn:mergeinfo
   Merged /trunk/hello:r8

Index: a.txt
===================================================================
--- a.txt	(revision 9)
+++ a.txt	(working copy)
@@ -1,2 +1,3 @@
 1
 2
+3
$ svn commit -m "svn merge r8" a.txt 
Sending        a.txt
Transmitting file data .
Committed revision 10.
$ svn mergeinfo file:///tmp/repos/trunk/hello
r8

次にGitいってみましょう。作るファイルはSubversionの時と同じです。まずはmasterブランチでa.txtを作ります。

$ mkdir hello
$ cd hello
$ echo 1 > a.txt
$ git init
$ git add a.txt 
$ git commit -m "first commit" a.txt 
[master (root-commit) 8d42fb0] first commit
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 a.txt
$ echo 2 >> a.txt 
$ git commit -m "second commit" a.txt 
[master baee205] second commit
 1 files changed, 1 insertions(+), 0 deletions(-)

次にhotfixブランチを作ってb.txtというファイルを追加します。

$ git branch hotfix
$ git checkout hotfix
Switched to branch 'hotfix'
$ echo 1 > b.txt
$ git add b.txt 
$ git commit -m "first commit for hotfix branch" b.txt 
[hotfix 9249a7a] first commit for hotfix branch
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 b.txt
$ echo 2 >> b.txt
$ git commit -m "second commit for hotfix branch" b.txt 
[hotfix c106823] second commit for hotfix branch
 1 files changed, 1 insertions(+), 0 deletions(-)
$ git branch
* hotfix
  master

masterブランチに戻ってa.txtに3と4を追加します。3を加えた0804feeをあとでつまみ食いします。

$ git checkout master
Switched to branch 'master'
$ echo 3 >> a.txt
$ git commit -m "third commit for master branch" a.txt 
[master 0804fee] third commit for master branch
 1 files changed, 1 insertions(+), 0 deletions(-)
$ echo 4 >>a.txt 
$ git commit -m "fourth commit for master branch" a.txt 
[master c2869d6] fourth commit for master branch
 1 files changed, 1 insertions(+), 0 deletions(-)
$ git log --pretty=oneline --graph --abbrev-commit
* c2869d6 fourth commit for master branch
* 0804fee third commit for master branch
* baee205 second commit
* 8d42fb0 first commit

hotfixブランチに戻ります。

$ git checkout hotfix
Switched to branch 'hotfix'
$ ls
a.txt b.txt
$ cat a.txt 
1
2
$ git log --pretty=oneline --graph --abbrev-commit
* c106823 second commit for hotfix branch
* 9249a7a first commit for hotfix branch
* baee205 second commit
* 8d42fb0 first commit

masterブランチでa.txtに3を加えた0804feeをつまみ食いします。
git cherry-pickでつまみ食いします。Subversionとは異なりコミットする必要はありません。

$ git cherry-pick 0804fee
Finished one cherry-pick.
[hotfix 8e21ed9] third commit for master branch
 1 files changed, 1 insertions(+), 0 deletions(-)
$ cat a.txt 
1
2
3
$ git diff
$ git log --pretty=oneline --graph --abbrev-commit
* 8e21ed9 third commit for master branch
* c106823 second commit for hotfix branch
* 9249a7a first commit for hotfix branch
* baee205 second commit
* 8d42fb0 first commit

最後にMercurial行ってみます。

事前準備として~/.hgrcに以下のように記述します。hg transplantがつまみ食いに相当するコマンドなのですがこれを有効にするためにtransplantと記述します。graphlogの部分はhg glogというコミットグラフを有効にするためで無くてもつまみ食いできます。

[extensions]
transplant =
hgext.graphlog =

次にdefaultブランチでa.txtを用意しておきます。

$ mkdir hello
$ cd hello/
$ echo 1 > a.txt
$ hg init
$ hg add a.txt 
$ hg commit -m "first commit" a.txt 
$ echo 2 >> a.txt
$ hg commit -m "second commit" a.txt 

hotfixブランチでb.txtを作ります。

$ hg branch hotfix
marked working directory as branch hotfix
$ echo 1 > b.txt
$ hg add b.txt 
$ hg commit -m "first commit for hotfix branch" b.txt 
$ echo 2 >>b.txt 
 hg commit -m "second commit for hotfix branch" b.txt 

defaultブランチに戻ってa.txtに変更を加えます。

$ hg update default
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ echo 3 >>a.txt 
$ hg commit -m "third commit for default branch" a.txt 
$ echo 4 >> a.txt 
$ hg commit -m "fourth commit for default branch" a.txt 

hotfixブランチに移動してつまみ食いします。
移動する際に-Cつけないとabort: update spans branches, use 'hg merge' or 'hg update -C' to lose changesと言われます。
つまみ食いはhg transplant -b ブランチ名 -m リビジョン です。

$ hg update -C hotfix
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cat a.txt 
1
2
$ hg transplant -b default -m 4
applying b92e71274b5f
4:b92e71274b5f merged at 712a433bd6b0
$ cat a.txt 
1
2
3
$ hg diff
$ hg glog
@    changeset:   6:712a433bd6b0
|\   branch:      hotfix
| |  tag:         tip
| |  parent:      3:8461d663b5dc
| |  parent:      4:b92e71274b5f
| |  user:        wyukawa@ubuntu-vbox
| |  date:        Thu Mar 25 21:38:44 2010 +0900
| |  summary:     third commit for default branch
| |
| | o  changeset:   5:6cc982156ae2
| |/   user:        wyukawa@ubuntu-vbox
| |    date:        Thu Mar 25 21:42:43 2010 +0900
| |    summary:     fourth commit for default branch
| |
| o  changeset:   4:b92e71274b5f
| |  parent:      1:93e446343985
| |  user:        wyukawa@ubuntu-vbox
| |  date:        Thu Mar 25 21:38:44 2010 +0900
| |  summary:     third commit for default branch
| |
o |  changeset:   3:8461d663b5dc
| |  branch:      hotfix
| |  user:        wyukawa@ubuntu-vbox
| |  date:        Thu Mar 25 21:37:52 2010 +0900
| |  summary:     second commit for hotfix branch
| |
o |  changeset:   2:1801deebe08e
|/   branch:      hotfix
|    user:        wyukawa@ubuntu-vbox
|    date:        Thu Mar 25 21:37:34 2010 +0900
|    summary:     first commit for hotfix branch
|
o  changeset:   1:93e446343985
|  user:        wyukawa@ubuntu-vbox
|  date:        Thu Mar 25 21:36:45 2010 +0900
|  summary:     second commit
|
o  changeset:   0:e4da2e60f814
   user:        wyukawa@ubuntu-vbox
   date:        Thu Mar 25 21:36:12 2010 +0900
   summary:     first commit

まとめ

どれが楽かは何とも言えない気もしますが、Gitはつまみ食いするリビジョンにハッシュ値を指定する一方、MercurialSubversionは連番の数字ですね。
Mercurialはマージしなくても全ブランチを含めたコミットグラフが見れるのがいいかも。Subversionもそんなに面倒じゃないかも。

コマンドの対応関係はだいたい以下のようです。

ブランチの切り替え svn switch git checkout hg update
つまみ食い svn merge git cherry-pick hg transplant

いじょ。