さて、前回までで、ひととおりブランチの運用とマージができるようになりましたね。だいぶ Git が怖くなくなってきたのではないでしょうか。むしろ「あーこれは便利かもしれない」となってきたのではないですか? それでは今回は過去を改変する機能について、少しだけ見て行きましょう。
さて、準備からはじめましょうか。今回も前回と同じシナリオでやっていきます。my_third_workspace を作って、そこにリポジトリを用意してください、よいですか? そしたら前回と同じくそこに以下のようなファイルを用意しましょう。
- cat_lover_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っており、また人間も
人間で猫に使役されることを至上の喜びと思う傾向にある。
猫の魅力に取り付かれた人間は二度と猫の魅力から離れることが
できないとも言われており、猫ってほんとうにかわいいですね。
もうほんとうに可愛い、猫。
猫。
猫が本当にかわいい。
猫好きじゃない人間とか頭がおかしいとしか思えない。
- cat_hater_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っているが、冗談じゃない。
人間が猫を飼ってやっているのである。そこを勘違いする猫は
本当にダメである。
その点犬は良い。犬は人間のことを「ご主人様」だとおもって
なついてくれる。それならばこそかわいがる気も起きるというものである。
犬は賢い。犬がいい。犬かわいい。散歩に行って息が上がった
あの「ヘッヘッヘッヘッへ」がもう私をおかしくしちゃうほんとにかわいい。
犬大好き。犬最高。
猫好きな人間とか頭がおかしいとしか思えない。
で、これをコミット。コミットメッセージも前回とおなじく「猫好きの話と犬好きの話を作成」です。これで準備は完了です。
さて、今回も前回と同じく「文体統一してくれ」という作業依頼が来たとしましょう。unify_styles ブランチを切って作業しますよ。ちなみに、こういう「特定の目的の作業を行うためのブランチ」のことを、「トピックブランチ」とか「フィーチャーブランチ」と呼ぶことが多いです。あるトピックやある機能に特化したブランチ、くらいの意味ですね。
はい、トピックブランチ(今回で言えば unify_styles)を切って checkout しましたか? もうコマンドはここには書かないので自分でやってみてください。
さて、あなたは作業を進めて行きます。ひとまず、こんな感じで途中まで文体を統一しました。
- cat_lover_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っており、また人間も
人間で猫に使役されることを至上の喜びと思う傾向にある。
猫の魅力に取り付かれた人間は二度と猫の魅力から離れることが
できないとも言われている。
もうほんとうに可愛い、猫。
猫。
猫が本当にかわいい。
猫好きじゃない人間とか頭がおかしいとしか思えない。
- cat_hater_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っているが、冗談じゃない。
人間が猫を飼ってやっているのである。そこを勘違いする猫は
本当にダメである。
その点犬は良い。犬は人間のことを「ご主人様」だとおもって
なついてくれる。それならばこそかわいがる気も起きるというものである。
犬は賢い。やはり犬がよい。犬はかわいい。
散歩に行ったあとに息が上がっている様など、ほんとうに良いものである。
犬大好き。犬最高。
猫好きな人間とか頭がおかしいとしか思えない。
うん、まだ中途半端な状態ですね。
でも、ここでディレクターから「おい頭おかしいってなんだよこれすぐ直して!」と言われてしまいました。 master を checkout してそこから hotfix ブランチを作らないと!
でもちょっと待ってください。あなたの作業ディレクトリには、まだコミットされてない変更があります。しかもこの中途半端な状態の変更をコミットしたくはありません。困りましたね! でも、今はとりあえず気にせず、ひとまずコミットしてしまいましょう。コミットさえしておけばいつでもこの状態に戻れるのですからね!
まだ作業途中なんで、コミットメッセージは「作業途中だがとりあえずコミットしておく」くらいにしておきましょうか。こんなコミットしたら怒られるって? いいですいいです、それはあとでなんとかしましょう。今はとっとと「頭おかしい」を直さないと!
はい、コミットしましたか?
ではグラフを確認。
$ git gprah
* 1321d1b (HEAD, unify_styles) 2013-05-06 Shinpei Maruyama 作業途中だがとりあえずコミットしておく
* 15248b3 (master) 2013-05-06 Shinpei Maruyama 猫好きの話と犬好きの話を作成
はい、いいですね。
じゃあ今度は hotfix をやっちゃいましょう。
まずは master を checkout して文体統一のための作業を行う前の状態を復元して、そこから hotfix ブランチを切って選択しましょう。
やりかたはわかりますね? わからないひとは前回、前々回を復習してください。
はい、ではそこから緊急対応を行います。
「猫好きじゃない人間とか頭がおかしいとしか思えない。」を「猫好きじゃない人間がいるのが不思議。」に書き換え、「猫好きな人間とか頭がおかしいとしか思えない。」を「猫好きな人間がいるのが不思議。」に書き換えます。いいですね。
そしたらコミットしましょう。メッセージは「頭おかしいという表現はまずいので修正」でいいでしょう。
はい、今のグラフを確認しましょう。
$ git graph
* 18fca6b (HEAD, hotfix) 2013-05-06 Shinpei Maruyama 頭おかしいという表現はまずいので修正
| * 1321d1b (unify_styles) 2013-05-06 Shinpei Maruyama 作業途中だがとりあえずコミットしておく
|/
* 15248b3 (master) 2013-05-06 Shinpei Maruyama 猫好きの話と犬好きの話を作成
ふむ、良いですね。ディレクターから OK が出たので、master にマージしてリリースしちゃいましょう。
$ git checkout master
$ git merge hotfix --no-ff
コミットメッセージは「Merge branch 'hotfix'」のままでいいですね。
いらなくなった hotfix ブランチを削除。
$ git branch -d hotfix
グラフを確認します
$ git graph
* 662776b (HEAD, master) 2013-05-06 Shinpei Maruyama Merge branch 'hotfix'
|\
| * 18fca6b 2013-05-06 Shinpei Maruyama 頭おかしいという表現はまずいので修正
|/
| * 1321d1b (unify_styles) 2013-05-06 Shinpei Maruyama 作業途中だがとりあえずコミットしておく
|/
* 15248b3 2013-05-06 Shinpei Maruyama 猫好きの話と犬好きの話を作成
うん、いいですね。
ではここで途中だった作業に戻りましょう。
unify_styles ブランチを checkout して、作業を進めたいところですね。でもちょっとまってください。ここで、仮定の話をしましょう。
今はこの unify_styles ブランチでの作業を始めたのが hotfix が行われる前でしたが、仮に、もしこれが、hotfix が行われたあとに発生したタスクだったら、話はずいぶんシンプルになるのにな、と思いませんか?
言い方を変えましょう。
* 15248b3 2013-05-06 Shinpei Maruyama 猫好きの話と犬好きの話を作成
から分岐している
* 1321d1b (unify_styles) 2013-05-06 Shinpei Maruyama 作業途中だがとりあえずコミットしておく
が、もしも
* 662776b (HEAD, master) 2013-05-06 Shinpei Maruyama Merge branch 'hotfix'
から分岐していてくれれば、なんだかシンプルなのになー、と思いませんか?
じゃあ、そういうふうに歴史を改変しちゃえばいいんです。改変しちゃいましょう。
さあ、改変を行いますよ!
$ git checkout unify_styles
で、unify_styles を選択しましょう。その状態で、
$ git rebase master
としてみましょう。お、なんか出ましたね。
First, rewinding head to replay your work on top of it...
Applying: 作業途中だがとりあえずコミットしておく
だそうです。これなんでしょうね? ひとまず置いておいて、現在のグラフを確認しましょう。
$ git graph
* 03f91be (HEAD, unify_styles) 2013-05-06 Shinpei Maruyama 作業途中だがとりあえずコミットしておく
* 87fe5eb (master) 2013-05-06 Shinpei Maruyama Merge branch 'hotfix'
|\
| * 0bd5673 2013-05-06 Shinpei Maruyama 頭おかしいという表現はまずいので修正
|/
* 3b9ccc5 2013-05-06 Shinpei Maruyama 猫好きの話と犬好きの話を作成
おおお! グラフがシンプルになってる!「作業途中だがとりあえずコミットしておく」の親コミットが、「Merge branch 'hotfix'」になりました! では、現在ファイルの中身がどうなっているかも見てみましょう。
- cat_lover_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っており、また人間も
人間で猫に使役されることを至上の喜びと思う傾向にある。
猫の魅力に取り付かれた人間は二度と猫の魅力から離れることが
できないとも言われている。
もうほんとうに可愛い、猫。
猫。
猫が本当にかわいい。
猫好きじゃない人間がいるのが不思議。
- cat_hater_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っているが、冗談じゃない。
人間が猫を飼ってやっているのである。そこを勘違いする猫は
本当にダメである。
その点犬は良い。犬は人間のことを「ご主人様」だとおもって
なついてくれる。それならばこそかわいがる気も起きるというものである。
犬は賢い。やはり犬がよい。犬はかわいい。
散歩に行ったあとに息が上がっている様など、ほんとうに良いものである。
犬大好き。犬最高。
猫好きな人間がいるのが不思議。
おお、hotfix で行った変更が取り込まれている!!これで、グラフも、コミットに含まれているファイルの内容も、「hotfixが終わったところから編集した」のと同じことになりましたね! ここまでは良いですか?
では、rebase を行ったときに何が起こっていたのかを詳しくみて行きましょう。
rebase したときに出てきた表示を再度見てみましょう。なんて書いてありますか?
First, rewinding head to replay your work on top of it...
Applying: 作業途中だがとりあえずコミットしておく
「まず、head を巻き戻すよ。これは、君がやったことを最初からリプレイするためだよ!」と言ってますね。そして、そのあとに、「適用中:作業途中だがとりあえずコミットしておく」とあります。
つまりこれはどういうことでしょうか?
それを理解する補助線として、まずは「もしもあなたが手動で rebase と同じことをするとしたらどうする?」という話を考えましょう。以下の手順で、同じことができますね。
- unify_styles の各コミットで「どのような変更を行ったのか」を覚えておく
- master (hotfix を merge したあとの状態) を checkoutする
- そこから新しくまた unify_stylesブランチを切る
- 覚えておいた「どのような変更を行ったのか」をイチから適用し直して行く。
じつは、rebase で行われていることもこれとかわりがありません。
まずは、unify_styles を「巻き戻し」して、master からあたらしくブランチを切り直します。そこから、unify_styles の各コミットで行った変更を、master が指しているコミットに対して適用しなおしているわけです。つまり、「適用中:作業途中だがとりあえずコミットしておく」が意味しているのは、そのコミットで行った変更内容を、再度 master から適用しなおしているよ、という意味だったのですね!
ということはですよ?
rebase 前の、「作業途中だがとりあえずコミットしておく」のコミットと、rebase 後の「作業途中だがとりあえずコミットしておく」というのは、似て非なるコミットとなっているはずです。実際、ファイルの内容は hotfix で行った変更を取り込んだものに変化していましたね?そして、rebase 前のコミットオブジェクトの id と rebase 後のコミットオブジェクトの id が変化しています!
なるほどー。
つまり rebase というのは、「過去に戻って別のところから変更をやりなおして、コミットをやり直すことで過去を改変しているもの」なのですね。
でも、ちょっと待ってください。今回はたまたま「変更のやり直し」が機械的に可能だったけれど、もしも Git さんが「やり直し」をしている間に、「機械的に変更の適用」ができない事態に陥ったらどうなるのでしょう? 言い方を変えれば、リベース中にコンフリクトが起こったら? ということです。
そのときは、rebase 作業が途中で止まってしまい、「このコミットやり直してるときにコンフリクトが起こったよ!」と Git さんが教えてくれます。
実際にわたしの手元で意図的にコンフリクトを起こしたときの表示は以下のように表示されました。
First, rewinding head to replay your work on top of it...
Applying: <やりなおしているコミットの名前>
Using index info to reconstruct a base tree...
M <コンフリクトを起こしたファイルの名前>
Falling back to patching base and 3-way merge...
Auto-merging <コンフリクトを起こしたファイルの名前>
CONFLICT (content): Merge conflict in <コンフリクトを起こしたファイルの名前>
Failed to merge in the changes.
Patch failed at <コンフリクトを起こした行> <コンフリクトの内容>
The copy of the patch that failed is found in:
/Users/shinpeim/hoge/.git/rebase-apply/patch
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
ふむふむ、いろいろと情報が出てきて、最後の三行に、
問題を解決したら、"git rebase --continue".をしてね。
この変更をスキップしちゃいたいなら、"git rebase --skip" instead.だよ。
もとのブランチを checkout してリベースをやめてしまいたいなら、"git rebase --abortしてね"
だそうです。コンフリクトを起こしたファイルを開いてみると、mergeでコンフリクトを起こしたときとおなじように、どのようなコンフリクトがおこったかの情報が書かれていますので、手動で修正しましょう。修正したら、マージのときと同じように、 git add
で「修正したよ」と Git に教えてあげましょう。これで問題は解決したので、Git さんの言う通りgit rebase --continue
をすれば、Git さんは rebase の続きを行ってくれます。
さて、とにかく、rebase で歴史を改変して、「hotfix が終わったあとに文体の統一の作業を始めた」世界線に辿りつくことができました。作業の続きを行いましょう。といいたい所なのですが、直前のコミットが「とりあえずのコミット」だったのを覚えていますか? こんな中途半端なコミット、捨て置けないですね。なので、今度は「直前のコミットをやりなおす」という歴史改変方法を伝授しましょう。
これは簡単で、git add
で「やりなおした後の状態のファイル」を stage してから、
$ git commit --amend
することで、直前のコミットをやり直すことができます。やってみましょう。
まずはファイルを編集します。
- cat_lover_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っており、また人間も
人間で猫に使役されることを至上の喜びと思う傾向にある。
猫の魅力に取り付かれた人間は二度と猫の魅力から離れることが
できないとも言われている。
猫というのはかように本当にかわいいものである。
猫好きではない人間が存在するのが、私には不思議に思える。
- cat_hater_said.txt
cat cat_hater_said.txt
猫は動物の一種である。
犬とならび、ペットとして飼われることの多い動物である。
猫は人間のことを下僕かなにかだと思っているが、冗談じゃない。
人間が猫を飼ってやっているのである。そこを勘違いする猫は
本当にダメである。
その点犬は良い。犬は人間のことを「ご主人様」だとおもって
なついてくれる。それならばこそかわいがる気も起きるというものである。
犬は賢い。やはり犬がよい。犬はかわいい。
散歩に行ったあとに息が上がって ヘッヘッヘッヘッ と
なっている様など、ほんとうに良いものである。
猫が好きな人間が存在するというのが、私には不思議に思える。
このように編集しました。さて、ではこの内容で直前のコミットを「やりなおし」しましょう。
$ git add .
$ git commit --amend
です。前回のコミットメッセージ「作業途中だがとりあえずコミットしておく」が表示されたテキストエディタが立ち上がったでしょう。もう作業は終わったので、このメッセージを「文体を統一」に書き換えて、保存して終了しましょう。
はい、コミットができましたね。ではグラフを確認しましょう。
$ git graph
* 519a167 (HEAD, unify_styles) 2013-05-06 Shinpei Maruyama 文体を統一
* 87fe5eb (master) 2013-05-06 Shinpei Maruyama Merge branch 'hotfix'
|\
| * 0bd5673 2013-05-06 Shinpei Maruyama 頭おかしいという表現はまずいので修正
|/
* 3b9ccc5 2013-05-06 Shinpei Maruyama 猫好きの話と犬好きの話を作成
「作業途中だがとりあえずコミットしておく」というコミットが、「文体を統一」でやりなおされているのがわかると思います。
ちょう便利!
さて、「文体の統一」の作業の結果をディレクターにチェックしてもらい、OK が出ました。あとは master に merge してリリースしてしまいましょう。
$ git checkout master
$ git merge --no-ff unify_styles
$ git branch -d unify_styles
はい、最後にグラフを確認しておきましょう。
$ git graph
* fe4090c (HEAD, master) 2013-05-06 Shinpei Maruyama Merge branch 'unify_styles'
|\
| * 519a167 2013-05-06 Shinpei Maruyama 文体を統一
|/
* 87fe5eb 2013-05-06 Shinpei Maruyama Merge branch 'hotfix'
|\
| * 0bd5673 2013-05-06 Shinpei Maruyama 頭おかしいという表現はまずいので修正
|/
* 3b9ccc5 2013-05-06 Shinpei Maruyama 猫好きの話と犬好きの話を作成
なんだかすっきりしたグラフになりました。いいですね!
さて、rebase を使うとグラフや履歴がすっきりするのはわかりました。では、こういうケースではいつでも rebase を使うべきなのでしょうか? これも結構現在でも宗教戦争が活発に行われているトピックなので、あなたの所属する組織やプロジェクトのやり方に合わせるのが良いでしょう。
ちなみに、筆者は最近では rebase のような大掛かりな過去改変は「ダークサイド」であるという見解に与するものです。rebaseすると、Git さんが勝手に過去のコミットを書き換えてしまいます。これによって、「ちゃんと問題なく動いていたコミット」が「動かないコミット」になってしまう可能性がある、というのがその理由です。
もちろん、マージコミットでもそのようなことは起こるのですが、マージコミットの場合はその後のテストでかならずすぐに「動かないコミット」を作ってしまったことを確認できますが、リベースで出来上がったコミットをいちいち再度テストするのは現実的ではないですよね?
それでは今回のまとめです。
- 今いるブランチが「履歴上のどこから分岐したのか」という過去を改変するためには、
git rebase
を使う。 - rebase をすると、分岐元から今までに行ったコミットの変更内容を、新しい分岐元からひとつずつイチから適用しなおしてくれる。
- 直前のコミットという過去を改変するためには、
git commit --amend
を使う
こんなところでしょうか。次回はついに「複数人で一緒に Git を使う」の話に入りますよ!