Friday, August 7, 2009

Cutting a git commit off a branch, and pasting it on another

git のトピックブランチでの作業中にバグ修正をコミットしたんですけど、そいつをリリースしようとしたときに、まだリリースできない変更を含むトピックブランチにコミットしてたことに気づくことがあります。
Subversion ならトピックブランチのコミット済みの履歴をもとに戻すことできないので、そのコミットのリビジョンだけを trunk にマージしますね。
Git でも同様にその変更をだけをマージすれば簡単なんですけど、 git の rebase とかでトピックブランチへのコミットを無かった事にして、 master に移動できるのではないかと考え頑張ってみました。

作業前の状態はこんな感じです。 i18n ブランチの b1ad369 が移動したい commit です。

$ git log --oneline -5 i18n
431120e localize filters#new in Japaense
b1ad369 handling EOFError in Feed.open()
4d4dac5 Japanese localizing top page
c04524f I18n support
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

"git rebase --onto newbase upstream [branch]" の構文に当てはめると、

$ git rebase --onto 4d4dac5 b1ad369 i18n

これで、バグ修正のコミットは i18n ブランチから削除されました。
$ git log --oneline -4 i18n
51a74d5 localize filters#new in Japaense
4d4dac5 Japanese localizing top page
c04524f I18n support
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

ここで、注意するべきは b1ad369 のあとの commit のハッシュが 431120e から 51a74d5 に変更されていることです。歴史を変えたのでタイムパラドックスが発生、同じように見える現在が微妙に変わる感じですかね。また、 b1ad369 は i18n ブランチからは削除されましたが、 commit は存在しています。

$ git log --oneline -4 b1ad369
b1ad369 handling EOFError in Feed.open()
4d4dac5 Japanese localizing top page
c04524f I18n support
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work

ちなみに、このケースでは master ブランチには i18n が分岐してからは何もコミットされていないので、

$ git log --oneline -1 master
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

i18n ブランチの履歴にも登場した 2688536 が master HEAD になります。
ここから commit b1ad369 をマージするためにはこの無名ブランチから b1ad369 と 2688536 の間2つ commit を削除しなければならないかと思ったのですが、 cherry-pick (つまみ食い) という便利なコマンドで任意の commit の変更だけマージできます。

$ git checkout master
$ git cherry-pick b1ad369

これで期待通りの結果になりました。

$ git log --oneline -2 master
92244c5 handling EOFError in Feed.open()
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.


2008-8-8: 更新


コメントの指摘により cherry-pick を使うように変更しましたので不要になった部分は以下 display:none してます。

b1ad369 を無名ブランチの HEAD として考えて、 master HEAD との間にある2つの commit を rebase で削除してから master にマージすれば期待している結果になるはずです。(ここで早まってマージするとその要らない2つの commit もマージされてしまいます。)

$ git rebase --onto 2688536 4d4dac5 b1ad369
$ git log --oneline -2
0960a76 handling EOFError in Feed.open()
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

ここでも、過去を変更した以降の commit は別の hash になるのでそれを指定して master にマージ

$ git checkout master
$ git merge 0960a76
$ git log --oneline -2 master
0960a76 handling EOFError in Feed.open()
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

結果は期待通りですが、正直、二度とこんなことしたく無い感じですね。一発でできるコマンドありそう。

2 comments:

sumikawa said...

はじめまして。
$ git checkout master
$ git cherry-pick 0960a76
でいいんじゃないでしょうか。

hiroshi saito said...

sumikawaさん、ご指摘ありがとうございます。
cherry-pick 、確かに。
要約するとこれだけで済みますね。

$ git rebase --onto 4d4dac5 b1ad369 i18n
$ git checkout master
$ git cherry-pick b1ad369

あとで本文も修正します。