実践でわかる!ブランチとコンフリクト、リバートを解説します


実践でわかる!ブランチとコンフリクト、リバートを解説します

こんにちは、フロントエンドエンジニアのいなばです。

気付けば、フロントエンド入門Git編の連載も5回目。前回は、ローカルで作業中のコミットを綺麗にまとめたり、間違った操作をしてしまったときにコミットログを修正したりする方法を紹介しました。

今回は、Gitを使う上で重要な「ブランチ」について解説したいと思います。

▼目次

ブランチとは?

ブランチは、コミットの歴史を分岐して記録していくためのものです。

 
01

歴史を分岐させることで、他のメンバーの作業の影響を受けることなく、同じリポジトリ上で複数の開発を同時に進めていくことができます。

 
02

分岐したブランチはマージもしくはリベースというコマンドで、他のブランチに合流させることができます。

これを繰り返すことで開発を進めていきます。作業単位ごとにブランチを作ることで、なにかしらの問題があった場合に原因となる箇所の調査が楽になります。なるべく細かい単位でブランチをこまめに作るようにしましょう。

マージとリベースってなんだっけ?となったら、前々回の記事を読んでおさらいしましょう。

ブランチを作ってみよう

それでは、実際にブランチを作ってみましょう。

自分のいるブランチを確認する

ブランチは、現在自分のいるブランチ(カレントブランチ)から新しく作成されます。ブランチを切る前に、自分がいま、枝分かれをさせたい場所にいるかを確認してみましょう。

なお、git branch と打つと現在のブランチを確認することができます。

$ git branch
* master

* が付いているブランチが、現在いるブランチです。

ブランチを作成する

では、新しいブランチを作成しましょう。

git branch の後に任意のブランチ名を入力してEnterを押すと新しいブランチが作成されます。

$ git branch new-branch
$ git branch
* master
new-branch

new-branchが増えていますね。でも、 * が付いているブランチはまだmasterのままなので、作成したnew-branchブランチへ移動してみましょう。

$ git checkout new-branch
$ git branch
master
* new-branch

移動したあとに再度 git branchを打ってみると、新しく作成したnew-branchブランチへ移動できていることが確認できました。この段階ではmasterブランチもnew-branchブランチも同じコミットを指しています。

 
ブランチとは、とあるコミットに名前をつけたものなんです。

03

new-branchでコミットをすると、履歴はこのようになります。履歴は1本のままなので、masterブランチでコミットした場合と変わりがないように見えますね。

 
04

masterにもコミットが追加されました。それぞれのブランチでコミットがされた時点で枝が分かれます。

 
05

あとは作成したブランチでの作業を進めて枝を成長させていき、作業が完了したらメインのブランチに合流させるのみです。

おまけ

checkoutに -b オプションをつけると、新しいブランチの作成と移動を同時におこなえます。

$ git checkout -b {新しいブランチ名}

コンフリクト

コンフリクトが発生するのは珍しいことではありません。はじめは、どう対処すればいいのかわからず不安になると思いますが、正しい解消法を理解すれば問題ないので、恐れることはありません。

コンフリクトとは?

マージの際にGitは変更箇所を自動的に統合してくれます。しかし、同じファイルの同じ場所への変更が同時にある場合、Gitはどちらを優先すべきか判断できないのでエラーをだします。この状態を「コンフリクトが起きた」と言います。

試しにコンフリクトを起こしてみる

試しに、手元で意図的にコンフリクトを起こしてみましょう。まずは、ブランチを切ります。

$ git checkout -b test-conflict-a

これでtest-conflict-aブランチの、作成と移動を同時におこなうことができました。test.txt を作成して「こんにちは」と入力してみましょう。

+ こんにちは

これを一旦コミットします。

 
次に、意図的にコンフリクトを起こすためにブランチを切ります。

$ git checkout -b test-conflict-b

先ほどのtest.txtを編集して、これをコミットします。

- こんにちは
+ みなさん、こんにちは

 
続いて、元いたtest-conflict-aブランチに戻ります。

$ git checkout test-conflict-a

test.txtを開くと「こんにちは」と書かれていますね。こちらのブランチでも、ファイルを変更してコミットしましょう。

- こんにちは
+ こんにちは、みなさん

これでtest-conflict-aとtest-conflict-bブランチで、同じファイルの同じ行にそれぞれ変更を加えた状態になりました。

 
では、いよいよコンフリクトを起こしてみましょう。test-conflict-aにtest-conflict-bをマージしてみます。

$ git merge test-conflict-b
Auto-merging **/**/test.txt
CONFLICT (content): Merge conflict in **/**/test.txt
Automatic merge failed; fix conflicts and then commit the result.

自動マージに失敗して、コンフリクトを起こすことができたようです。test.txtを開いて、中身を確認してみましょう。

コンフリクトしたファイルの見方

コンフリクトを解決するための方法を見ていきましょう。

-こんにちは、みなさん
+<<<<<<< HEAD
+こんにちは、みなさん
+=======
+みなさん、こんにちは
+>>>>>>> test-conflict-b

先ほどのtest.txtの中身を確認すると、このようになっているかと思います。

<<<<<<< HEAD
自分の作業での変更 (ソースツリーではこちら側と呼ばれる)
=======
マージをしようとしたブランチでの変更 (ソースツリーではあちら側と呼ばれる)
>>>>>>> [commitのID]

コンフリクトが発生した箇所ごとに <<<<<<< HEAD といった記述が自動で追加されます。======= より上が自分が加えた変更で、下がマージをしようとしたブランチでの変更になります。

コンフリクトを解決しよう

今回は、適当にコンフリクトを起こしてみた例なので、どちらかの変更を生かすのが正しいというのがありませんが、実際は最終的に望ましい状態に修正してコミットすることで、コンフリクトを解決します。

今回は「こんにちは、みなさん」の変更が最終的に望ましい形だったと仮定して、必要な行以外は全て削除したのちにコミットします。

- こんにちは、みなさん
- <<<<<<< HEAD
+ こんにちは、みなさん
- =======
- みなさん、こんにちは
- >>>>>>> test-conflict-b

これで、コンフリクトを解消できました。

リバート

ブランチをマージした後で気付かないうちにバグを潜ませてしまうなど、意図しない影響を与えてしまっていたことに気付くことがあります。そんなときは、問題があったそのマージコミットをリバートすることで、マージする前のコードに戻すことができます。

リバートとは?

リバートは主に、リモートにコミットした内容を打ち消したい場合に使います。ただし、git resetとは違い、コミット自体をなかったことにするわけではありません。git revertは、修正内容を打ち消すコミットを新しく作ることで、コードを変更前の状態にします。

試しにリバートを使ってみる

早速リバートを試してみましょう。

- こんにちは、みなさん
+ こんにちは、みなさん
+
+ revertを試してみる

先ほどのtest.txtに1行追加してコミットしてみました。

 
revertコマンドの後に、戻したい先ほどのコミットIDを指定します。コマンドを実行するとエディタが立ち上がります。

$ git revert [コミットID]

ここでは、revertで新しく作られたコミットのメッセージを編集することができますが、今回はそのまま保存して終了します。(vimの場合は :wqを入力後Enterで終了)

これで、先ほどの変更を打ち消すコミットがされました。新しく作られたコミットの変更内容を見てみると、先ほどの変更を打ち消す変更が新しくコミットされていることが確認できます。

- こんにちは、みなさん
-
- revertを試してみる
+ こんにちは、みなさん

これで、変更した内容を戻せました。

マージコミットをリバートする

先ほどの例は1つのコミットをリバートする場合でした。最後に、マージコミットのリバートを実際にやってみましょう。

まずは、新しくブランチを切ります。

$ git checkout -b test-conflict-c

 
先ほどのようにtest2.txtを作成、編集して、これをコミットします。

+ マージコミットのrevert

 
つづいて、元いたtest-conflict-aブランチに戻ります。

$ git checkout test-conflict-a

 
先ほどのtest-conflict-cをマージして……(今回は強制的にマージコミットを作りたいので--no-ffオプションをつけます)

$ git merge test-conflict-c --no-ff

 
先ほどと同様にrevertコマンドを叩いてみると……

$ git revert [コミットID]
error: Commit [コミットID] is a merge but no -m option was given.
fatal: revert failed

エラーが出て、マージができませんでした。

マージコミットをリバートする場合、マージコミットは親コミットを複数持っているので、どのブランチを残すのかを-mオプションで指定しなければなりません。git revert -mで残したい親番号を指定することで、マージコミットの取り消しが可能となります。

$ git revert -m 1 [コミットID]

通常-mオプションで指定する番号はマージされるブランチの親番号は1、マージするブランチの親番号は2となるようです。この場合はマージされるブランチを残したいので、 -m 1 と指定することでtest-conflict-cの変更分をリバートすることができました。

おわりに

Gitを理解するための一番の近道は、個人でどうでもいいリポジトリを作り、ローカルでさまざまなコマンドを実行してみて、何度もやらかすことです。(昔先輩エンジニアに教わりました。実案件でやらかすと普通に怒られます)

次回は、ついにGit連載の最終回。ブランチの運用ルールについて解説をしますので、来週の公開を楽しみにしていてください。


この記事を書いた人

いなば
いなば フロントエンドエンジニア 2014年入社
フロントエンドエンジニアのいなばです。
LIGではAngularJSを使ったWebアプリケーションの開発をしています。
ゴリゴリ動くサイトよりSPAが得意です。
好きなものはカフェインとカプサイシン。
趣味はランニングと一眼レフです。