Gitは ローカルでのコミットやリモートへのプッシュ・プル、ブランチのマージやナントカカントカなどいろいろとややこしい仕組みがあります。とりあえず自分の理解のために、まずはリモートを使わないローカルでの操作を整理してみました。おもにリポジトリの作成からコミット、マージまでを整理しました。
$ mkdir tutorial && cd $_ $ git init Initialized empty Git repository in /Users/masatomix/git/tutorial/.git/
$ echo "master added" >> file1.txt $ cat file1.txt master added $ git add file1.txt $ git commit -m "master commit" [master (root-commit) 25c2961] master commit 1 file changed, 1 insertion(+) create mode 100644 file1.txt
$ echo "2 master added" >> file1.txt $ cat file1.txt master added 2 master addedgit $ git add file1.txt $ git commit -m "2 master commit" [master ad0dac1] 2 master commit 1 file changed, 1 insertion(+)
addしてcommitの繰り返しです。コミットまでできました。
ブランチを作成します
$ git checkout -b dev ←作成と移動。作成だけなら git branch dev でいい Switched to a new branch 'dev' $ git branch * dev [#h881c414] master
ローカルのワーキングツリーがdevになりました。この時点で、masterとdev それぞれはおなじリビジョンにいることになります。
devに存在するファイルを修正して、コミットします。
$ echo "dev modified" >> file1.txt && git add file1.txt && git commit -m "dev commit" [dev 3f7af4f] dev commit 1 file changed, 1 insertion(+)
上記のキャプチャを見て分かるとおり、devだけひとつリビジョンが進んでいますね。
さて、devで修正した内容をmasterにマージします。マージとは、ローカルのワーキングツリー上のブランチに他のブランチの修正を反映させることです。 修正を反映させたいブランチへ移動して、他のブランチを指定してマージのコマンドを実行します。
$ git checkout master Switched to branch 'master' $ git merge dev Updating ad0dac1..3f7af4f Fast-forward file1.txt | 1 + 1 file changed, 1 insertion(+) $ git branch dev * master [#fbf5598a]
今回は masterブランチにdevブランチをマージしました。ただmasterについてはdevを作成後、コミットがされていなかったので、ただ単にmasterブランチのリビジョンがdevのリビジョンに進んだだけのようですね。これを早送りするって意味でFast-forwardなマージというようです。
masterからdevを作成後、たとえばmasterが別のブランチをマージして先に進んでいる場合を考えます。この場合はdevの修正をmasterにマージする際、単純なFast-forwardにすることはできないはずですよね。どうなるかやってみます。
$ git checkout -b dev2 Switched to a new branch 'dev2' $ echo "dev2 added " >> file2.txt && git add file2.txt && git commit -m "dev2 commit" [dev2 134e253] dev2 commit 1 file changed, 1 insertion(+) create mode 100644 file2.txt
$ git checkout master Switched to branch 'master' $ git merge dev2 Updating 3f7af4f..134e253 Fast-forward file2.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 file2.txt
さあdev2を使って、masterのリビジョンを少し進めました。
続いて、devも独自にリビジョンをすこしだけ進めておきます。
$ git checkout dev $ echo "dev added " >> file1.txt && git add file1.txt && git commit -m "2 dev commit" [dev 3f7a1ef] 2 dev commit 1 file changed, 1 insertion(+)
上記キャプチャで分かるとおり、masterとdev2はおなじ場所にいます。dev はmasterの一個前(?)から派生していて、独自の進化をしています。さあ派生したdevの修正をmasterに反映してみます。
$ git checkout master Switched to branch 'master' $ git merge dev Merge made by the 'recursive' strategy. file1.txt | 1 + 1 file changed, 1 insertion(+)
新しいコミットがひとつ実行され、Fast-forwardでないやり方で、ファイルがマージされました*1。
今回はFast-forwardではないのでコミットメッセージを入れるダイアログが出たと思いますが、
$ git merge dev -m "Merge branch dev"
などとすればメッセージは表示されません。。
最後にdevをmasterまで進めて、完了です。
$ git checkout dev $ git merge master Updating 3f7a1ef..56f88d1 Fast-forward file2.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 file2.txt
masterとdevはおなじ状態になりました。
このように、あるブランチの修正をマージする場合、そのまま直列に後ろにつなげばよい場合は、Fast-forwardというやり方でブランチの内容が反映されることが分かりました*2。また自分が別のコミットやマージによって、単純に早送りするだけではダメな場合、他ブランチの修正を反映させたコミットを新たに作成することで、マージ処理が行われることが分かりました。
それらの修正がコンフリクトしない場合、gitが正しくマージ処理をしてくれます。かしこいです。おなじファイルをいじっているなど、単純なマージで廃家内場合は人間が競合を取り除くコミットを手動で行うことで、マージ処理を行います。それについてはこちらに詳しく書いてありますのでそちらをご参照のこと。
別のブランチの修正をマージするにあたって
$ git checkout dev $ git merge master
とやることで、devブランチにmasterブランチの修正を反映させることができるのでした。ちなみにさっきのまでの説明に対して、マージの向きが逆になっちゃってますが、リベースなどのときの実際の開発に近いパタンにあわせて向きを変えました。まあご愛敬ということで。
さてgit には リベースという概念があります。これはマージが「いまのdevの状態に対して、masterのコミットをまとめた内容をコミットすることで反映する」のに対して、リベースは「いまのdevの状態(コミット履歴)を待避しておいて、枝分かれしたところへ戻りそこからmasterの修正内容をdevに反映し、そのあと待避していたコミット履歴を新たにコミットする」機能のようです。
$ git checkout dev $ git rebase master First, rewinding head to replay your work on top of it... Applying: dev commit <- 3fc335e として新たにコミットされてる! Applying: dev commit <- 9210c21 として新たにコミットされてる! Applying: dev commit <- 09a6a5a として新たにコミットされてる!
このように、devのコミットがいったん待避されて*3、masterの修正がdevに反映され*4、んで「あらたな」コミットとして、待避していた内容がコミットされることが分かりました。
画面キャプチャからもわかるとおり、修正履歴が一直線となり見やすくなるにはなるのですが。。なんどもmerge/rebaseするケースを考えると、rebaseは都度の「マージした」という履歴が残りません。
また、ココではリモートへのpushの件は出てきませんが、リモートへpush済みのブランチについて、それより前のリビジョンからrebaseした場合、巻き戻し&再コミットによってリモートとの不整合が発生するのでリモートへのプッシュができなくなります。つまりそんなケースではrebaseはやってはいけないようです。
これらについては、こわくない GitのP.143 に詳しく書いてあります。非常にわかりやすい説明ですね。。
devから派生した dev_#50, dev_#60があって並行で開発してて、dev_#60がさきにdevを書き換えたとします。dev_#50の開発者は、書き換えられたdevの更新分を取り込んだうえで、devへ自分の更新分をマージするわけですが、devの更新分をマージで取り込んだ場合とリベースで取り込んだ場合で、どうなるかって比較をしてみます。
マージ・リベース直前の状態は下記の通り。
ふつうにマージします。
$ git checkout dev_#50 $ git merge dev
マージされた更新履歴を含め、さらにdev側にマージします
$ git checkout dev $ git merge dev_#50
マージなので、まずdev_#50 側では元々の更新履歴(5d630c3, f8407bb, 27c9868) の後ろに「マージされた」というコミット(8c31370)が追加されます。 つづいてdevにそれを取り込む際、fast-forwardマージで更新分が反映されます。
つぎにリベースです。
$ git checkout dev_#50 $ git rebase dev
反映された更新履歴を含め、さらにdev側にマージします
$ git checkout dev $ git merge dev_#50
今回はリベースなので、dev_#50側での更新がいったん待避され、まず devの更新分(8f9278e,de5d47b,ab201b5) が反映されます。そののち、dev_#50更新のコミットが新規で走ります(b3eb943, 5074dc6, 9f3c377)。待避されたコミットとは別のIDのコミットです。つづいてdevにそれを取り込む際、fast-forwardマージで更新分が反映されます。
dev の側から見た時に「dev_#50がdev_#60での更新をこのタイミングで取り込んで、んでこっちにマージしてきた」なんて情報が必要か、そうでないかでマージ・リベースを使い分ければ良さそうです。通常の開発であれば「のこしとけば?」という気がします。ようするにマージなんですかね。たとえばdev側が他人のプロジェクトとかで「dev_#50がどんなタイミングでうちら(dev)の更新取り込んでるか、なんてしらん」って場合はリベースを使うって事ですかね。他のプロジェクトにプルリクエストを送る場合に、先方の更新分でリベースしてからプルリクしなさいっていうのはこういうことっぽいですね。。。ただ、マージ、リベースしないでプルリク送るのはマズイと思いますが、先方の更新分をマージした上でのプルリクなら、リベースでなくてもよいのでは?という気がしました。
ってわけで、上記でどっちでもイイかなと思ったマージとリベースを、さらにもう少し追加で比較しました。どうやら並行開発時の他のブランチの更新分の反映は、リベースの方がよさそうなことが分かったのですが、長くなりそうなので、別ページにまとめました。
この記事は
現在のアクセス:426