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 に詳しく書いてあります。非常にわかりやすい説明ですね。。
この記事は
現在のアクセス:425