Top / GitHub / 基本

Gitは ローカルでのコミットやリモートへのプッシュ・プル、ブランチのマージやナントカカントカなどいろいろとややこしい仕組みがあります。とりあえず自分の理解のために、まずはリモートを使わないローカルでの操作を整理してみました。おもにリポジトリの作成からコミット、マージまでを整理しました。

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 それぞれはおなじリビジョンにいることになります。

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(+)
02.png

上記のキャプチャを見て分かるとおり、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なマージというようです。

03.png

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のリビジョンを少し進めました。

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(+)
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(+)
06.png

新しいコミットがひとつ実行され、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はおなじ状態になりました。

07.png

まとめ

このように、あるブランチの修正をマージする場合、そのまま直列に後ろにつなげばよい場合は、Fast-forwardというやり方でブランチの内容が反映されることが分かりました*2。また自分が別のコミットやマージによって、単純に早送りするだけではダメな場合、他ブランチの修正を反映させたコミットを新たに作成することで、マージ処理が行われることが分かりました。

それらの修正がコンフリクトしない場合、gitが正しくマージ処理をしてくれます。かしこいです。おなじファイルをいじっているなど、単純なマージではいかない場合は人間が競合を取り除くコミットを手動で行うことで、マージ処理を行います。それについてはこちらに詳しく書いてありますのでそちらをご参照のこと。

6. マージでの衝突を解決する

さいごに、リベースってなんだ

別のブランチの修正をマージするにあたって

$ git checkout dev
$ git merge  master

とやることで、devブランチにmasterブランチの修正を反映させることができるのでした。ちなみにさっきのまでの説明に対して、マージの向きが逆になっちゃってますが、リベースなどのときの実際の開発に近いパタンにあわせて向きを変えました。まあご愛敬ということで。

08.png

さて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 として新たにコミットされてる!
09.png

このように、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の更新分をマージで取り込んだ場合とリベースで取り込んだ場合で、どうなるかって比較をしてみます。

マージ・リベース直前の状態は下記の通り。

before.png

マージ

ふつうにマージします。

$ git checkout dev_#50
$ git merge dev

マージされた更新履歴を含め、さらにdev側にマージします

$ git checkout dev
$ git merge dev_#50
merge.png

マージなので、まずdev_#50 側では元々の更新履歴(5d630c3, f8407bb, 27c9868) の後ろに「マージされた」というコミット(8c31370)が追加されます。 つづいてdevにそれを取り込む際、fast-forwardマージで更新分が反映されます。

リベース

つぎにリベースです。

$ git checkout dev_#50
$ git rebase dev

反映された更新履歴を含め、さらにdev側にマージします

$ git checkout dev
$ git merge dev_#50
rebase.png

今回はリベースなので、dev_#50側での更新がいったん待避され、まず devの更新分(8f9278e,de5d47b,ab201b5) が反映されます。そののち、dev_#50更新のコミットが新規で走ります(b3eb943, 5074dc6, 9f3c377)。待避されたコミットとは別のIDのコミットです。つづいてdevにそれを取り込む際、fast-forwardマージで更新分が反映されます。

比較のまとめ

dev の側から見た時に「dev_#50がdev_#60での更新をこのタイミングで取り込んで、んでこっちにマージしてきた」なんて情報が必要か、そうでないかでマージ・リベースを使い分ければ良さそうです。通常の開発であれば「のこしとけば?」という気がします。ようするにマージなんですかね。たとえばdev側が他人のプロジェクトとかで「dev_#50がどんなタイミングでうちら(dev)の更新取り込んでるか、なんてしらん」って場合はリベースを使うって事ですかね。他のプロジェクトにプルリクエストを送る場合に、先方の更新分でリベースしてからプルリクしなさいっていうのはこういうことっぽいですね。。。ただ、マージ、リベースしないでプルリク送るのはマズイと思いますが、先方の更新分をマージした上でのプルリクなら、リベースでなくてもよいのでは?という気がしました。

追加で。

ってわけで、上記でどっちでもイイかなと思ったマージとリベースを、さらにもう少し追加で比較しました。どうやら並行開発時の他のブランチの更新分の反映は、リベースの方がよさそうなことが分かったのですが、長くなりそうなので、別ページにまとめました。

GitHub/マージとリベース

関連リンク


この記事は

選択肢 投票
おもしろかった 0  
そうでもない 0  

Top / GitHub / 基本

現在のアクセス:849


*1 今回devでのコミットは一回でしたが、複数回の場合も、それらをまとめた一つのコミットが実行されます
*2 というか自分がただ進むだけということでしょうか
*3 rewinding。巻き戻し
*4 そして、これはきっとFast-forward

添付ファイル: filerebase.png 180件 [詳細] filemerge.png 172件 [詳細] filebefore.png 163件 [詳細] file09.png 251件 [詳細] file08.png 265件 [詳細] file07.png 162件 [詳細] file06.png 251件 [詳細] file03.png 203件 [詳細] file05.png 225件 [詳細] file04.png 254件 [詳細] file02.png 234件 [詳細] file01.png 266件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-02-02 (木) 18:18:05 (963d)