- 追加された行はこの色です。
- 削除された行はこの色です。
#topicpath
----
#contents
Gitは ローカルでのコミットやリモートへのプッシュ・プル、ブランチのマージやナントカカントカなどいろいろとややこしい仕組みがあります。とりあえず自分の理解のために、まずはリモートを使わないローカルでの操作を整理してみました。おもにリポジトリの作成からコミット、マージまでを整理しました。
** Gitを始める [#qac54eb8]
$ 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の繰り返しです。コミットまでできました。
** ブランチを作成する [#s0954705]
ブランチを作成します
$ git checkout -b dev ←作成と移動。作成だけなら git branch dev でいい
Switched to a new branch 'dev'
$ git branch
* dev [#h881c414]
master
ローカルのワーキングツリーがdevになりました。この時点で、masterとdev それぞれはおなじリビジョンにいることになります。
#ref(01.png)
devに存在するファイルを修正して、コミットします。
$ echo "dev modified" >> file1.txt && git add file1.txt && git commit -m "dev commit"
[dev 3f7af4f] dev commit
1 file changed, 1 insertion(+)
#ref(02.png)
上記のキャプチャを見て分かるとおり、devだけひとつリビジョンが進んでいますね。
** ブランチのマージ [#c7877d94]
さて、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なマージというようです。
#ref(03.png)
** Fast-forwardでないマージ [#nc334ab4]
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のリビジョンを少し進めました。
#ref(04.png)
続いて、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(+)
#ref(05.png)
上記キャプチャで分かるとおり、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(+)
#ref(06.png)
新しいコミットがひとつ実行され、Fast-forwardでないやり方で、ファイルがマージされました((今回devでのコミットは一回でしたが、複数回の場合も、それらをまとめた一つのコミットが実行されます))。
今回は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はおなじ状態になりました。
#ref(07.png)
**まとめ [#g81261ad]
このように、あるブランチの修正をマージする場合、そのまま直列に後ろにつなげばよい場合は、Fast-forwardというやり方でブランチの内容が反映されることが分かりました((というか自分がただ進むだけということでしょうか))。また自分が別のコミットやマージによって、単純に早送りするだけではダメな場合、他ブランチの修正を反映させたコミットを新たに作成することで、マージ処理が行われることが分かりました。
それらの修正がコンフリクトしない場合、gitが正しくマージ処理をしてくれます。かしこいです。おなじファイルをいじっているなど、単純なマージで廃家内場合は人間が競合を取り除くコミットを手動で行うことで、マージ処理を行います。それについてはこちらに詳しく書いてありますのでそちらをご参照のこと。
[[6. マージでの衝突を解決する>http://www.backlog.jp/git-guide/stepup/stepup2_7.html]]
** さいごに、リベースってなんだ [#zb00c773]
別のブランチの修正をマージするにあたって
$ git checkout dev
$ git merge master
とやることで、devブランチにmasterブランチの修正を反映させることができるのでした。便宜上、マージがさっきの逆のパタンになっているのはご愛敬。。
とやることで、devブランチにmasterブランチの修正を反映させることができるのでした。ちなみにさっきのまでの説明に対して、マージの向きが逆になっちゃってますが、リベースなどのときの実際の開発に近いパタンにあわせて向きを変えました。まあご愛敬ということで。
#ref(08.png)
さてgit には リベースという概念があります。これはマージが「いまのdevの状態に対して、masterのコミットをまとめた内容をコミットすることで反映する」のに対して「いまのdevの状態(コミット履歴)を待避しておいて、枝分かれしたところへ戻りそこからmasterの修正内容をdevに反映し、そのあと待避していたコミット履歴を新たにコミットする」機能のようです。
さて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 として新たにコミットされてる!
#ref(09.png)
このように、devのコミットがいったん待避されて((rewinding。巻き戻し))、masterの修正がdevに反映され((そして、これはきっとFast-forward))、んで「あらたな」コミットとして、待避していた内容がコミットされることが分かりました。
画面キャプチャからもわかるとおり、修正履歴が一直線となり見やすくなるにはなるのですが。。なんどもmerge/rebaseするケースを考えると、rebaseは都度の「マージした」という履歴が残りません。
また、ココではリモートへのpushの件は出てきませんが、リモートへpush済みのブランチについて、それより前のリビジョンからrebaseした場合、巻き戻し&再コミットによってリモートとの不整合が発生するのでリモートへのプッシュができなくなります。つまりそんなケースではrebaseはやってはいけないようです。
これらについては、[[こわくない Git>http://www.slideshare.net/kotas/git-15276118]]のP.143 に詳しく書いてあります。非常にわかりやすい説明ですね。。
** マージとリベースの結果比較。 [#t89651a2]
devから派生した dev_#50, dev_#60があって並行で開発してて、dev_#60がさきにdevを書き換えたとします。dev_#50の開発者は、書き換えられたdevの更新分を取り込んだうえで、devへ自分の更新分をマージするわけですが、devの更新分をマージで取り込んだ場合とリベースで取り込んだ場合で、どうなるかって比較をしてみます。
マージ・リベース直前の状態は下記の通り。
#ref(before.png)
***マージ [#o7781060]
ふつうにマージします。
$ git checkout dev_#50
$ git merge dev
マージされた更新履歴を含め、さらにdev側にマージします
$ git checkout dev
$ git merge dev_#50
#ref(merge.png)
マージなので、まずdev_#50 側では元々の更新履歴(5d630c3, f8407bb, 27c9868) の後ろに「マージされた」というコミット(8c31370)が追加されます。
つづいてdevにそれを取り込む際、fast-forwardマージで更新分が反映されます。
***リベース [#p4951555]
つぎにリベースです。
$ git checkout dev_#50
$ git rebase dev
反映された更新履歴を含め、さらにdev側にマージします
$ git checkout dev
$ git merge dev_#50
#ref(rebase.png)
今回はリベースなので、dev_#50側での更新がいったん待避され、まず devの更新分(8f9278e,de5d47b,ab201b5) が反映されます。そののち、dev_#50更新のコミットが新規で走ります(b3eb943, 5074dc6, 9f3c377)。待避されたコミットとは別のIDのコミットです。つづいてdevにそれを取り込む際、fast-forwardマージで更新分が反映されます。
***比較のまとめ [#f7f1656e]
dev の側から見た時に「dev_#50がdev_#60での更新をこのタイミングで取り込んで、んでこっちにマージしてきた」なんて情報が必要か、そうでないかでマージ・リベースを使い分ければ良さそうです。通常の開発であれば「のこしとけば?」という気がします。ようするにマージなんですかね。たとえばdev側が他人のプロジェクトとかで「dev_#50がどんなタイミングでうちら(dev)の更新取り込んでるか、なんてしらん」って場合はリベースを使うって事ですかね。他のプロジェクトにプルリクエストを送る場合に、先方の更新分でリベースしてからプルリクしなさいっていうのはこういうことっぽいですね。。。ただ、マージ、リベースしないでプルリク送るのはマズイと思いますが、先方の更新分をマージした上でのプルリクなら、リベースでなくてもよいのでは?という気がします。識者のかたどうなんでしょう。。。
**関連リンク [#z8b7d3a5]
-[[Git初心者に捧ぐ!Gitの「これなんで?」を解説します。 | KRAY Inc>http://kray.jp/blog/git-why-explanation/]]
-[[サルでもわかるGit入門 〜バージョン管理を使いこなそう〜>http://www.backlog.jp/git-guide/stepup/stepup2_1.html]]
-[[Git の疑問。トピックブランチで作業中に、master ブランチで重要な変更が加えられた。どうすればよい?>http://blog.inouetakuya.info/entry/20130602/1370173582]] ココのSlideShare めちゃくちゃわかりやすい。必読。
-[[GitHubへpull requestする際のベストプラクティス - hnwの日記>http://d.hatena.ne.jp/hnw/20110528]]
----
この記事は
#vote(おもしろかった,そうでもない)
#comment
#topicpath
SIZE(10){現在のアクセス:&counter;}