RICORA< image/svg+xml > image/svg+xml >
RICORA Programming Team

Gitのつかいかた branch, merge

Git / GitHubの使い方 ブランチについて

Branchについて

Branch(以下ブランチ)とは枝のことですが、gitにおける役割は木の枝ではなく支流のようなものです。 完全に以前の状況をリセットした状態から始めるのではなく、ある程度引き継いだ状態からファイルを追加したり、ソフトウェアのバージョン管理をするときに有用です。 青◯学院とかのキラキラJDがInstagramにのっけて承認欲求とともに貪るのはbrunch。別物です。

ブランチを使う理由

ブランチを使う最大の理由は、作業中の変更を本流から分離できることです。 mainで直接作業すると、未完成の変更や検証不足の変更が即座に共有されてしまいます。

開発では次のように役割を分けると安全です。

  • main: いつでも使える状態を保つ
  • 作業ブランチ: 機能追加や修正を進める

この分離があることで、レビューと検証を挟んだ上で安全に統合できます。

ブランチ を切る / 移動する

まずブランチを切ってみましょう。 ブランチはコミットしたファイルが対象なので、とりあえず練習用のディレクトリを作り、以下のような感じで最初のファイル(ここではa.txt)を作ってからそれをコミットします。

git init
touch a.txt
git add a.txt
git commit -m "a.txt"

コミットする理由は、はじめにコミットがないと以降の操作ができないからです。

では、ブランチを切ります。コマンドは次のとおりです。

git checkout -b (new branch name)

もしくは、次のコマンドでも可能です。

git switch -c (new branch name)

です。(new branch name)には好きな名前を入れてください。

なぜ同じことをするためのコマンドが複数あるかは、次の記事を読んでください。

また、ブランチは慣習により切るという表現をします。

さて、これで新しいブランチに移ることができました。では、ブランチの一覧を見てそれを確認しましょう。

git branch -l

を打つと、次のように表示されます。

  main
* (new branch name)

*(asterisk)は現在のブランチを示す記号です。 これでブランチを新しく作り、移動できました。簡単ですね。

では、また元のブランチに戻りましょう。そのためのコマンドはこちらです。

git checkout main

もしくは、こちらです。

git switch main

を打ちます。そこで再度現在のブランチ一覧を確認します。

> git branch -l

* main
  (new branch name)

こんな感じ。きちんと戻れましたね。

ファイルが…ない

さて、次にさっき自分が切ったブランチに戻ってファイルを作成しましょう。ついでに確認もしておくとすると、そのためのコマンドは次のとおりです。

git switch (new branch name) / git checkout (new branch name)
git branch -l

でしたね。

では、ここでファイルを1つ作ります。UNIX系では次のコマンドを使います(lol.txtは任意のファイル名でかまいません)。

touch lol.txt

touchは本来ファイル生成コマンドではないのですが、それはさておき。

これでまたmainに戻ります。さて、lsをしてみてください。

> ls

ファイルがありません。さっき作ったはずなのに、なぜでしょうか。ではまた自分が作ったブランチに戻ります。

> ls
  lol.txt

ありました。どういうことなのでしょうか。

HEADとBranch

ここで少しgitの概念について話しましょう。もしそういうものが必要なければ、この節を飛ばして次の節に移っていただいて結構です。

では、はじめましょう。先述したとおり、gitは現在のディレクトリ内でのバージョン管理をしてくれるシステムでした。 しかし、その詳細については立ち入ることなく素通りしていました。ブランチの挙動を理解するための導きの糸として、少しその設計に立ち入りましょう。

まず、次のコマンドを打ってみましょう。

git log

すると、次のようなログが出力されます。

commit ...
Author: ...
Date:   ...

  something

形式はlessと同じなので、長い場合は終了キーを押してログ出力プログラムを終了します。

さて、ログ出力の一番上にはcommit ... (HEAD -> [ブランチ name])という文字列があるはずです。このHEADとは何なのでしょうか。 実際にディレクトリをいじりながら探りましょう。まずは新しいディレクトリを作成します。そこで次を実行します。

git init
touch A.txt
git add A.txt
git commit -m "initial commit"

そこでブランチを新しく作成しましょう。このページの中に書いてあるように、次を実行します。

git switch -c editA

続いて次を実行します。

echo "hi, git! I'm here" > A.txt
git add A.txt
git commit -m "wrote to A.txt"

ここまで行った後で、2つのブランチ、mainとeditAでのgit logの出力を比べてみましょう。

mainでは次のように表示されます。

commit ... (HEAD -> main)

editAでは次のように表示されます。

commit ...  (HEAD -> editA)
Author: ...
Date: ...

  something

commit ... (main)

確かにさっきA.txtを編集したはずなのにmainでは表示されていませんね。

なぜそのようなことが起きるかを図解したものが以下になります。ブランチはある特定のコミットを指すポインタです。ポインタに馴染みがない人は、コミットの上に看板を立てているようなものだと解釈してください。

ここでHEADは、現在いるブランチの先頭のコミットを指すのです。先程のコマンドを例に取ります。はじめにコミットしたときは次の状態です。

commit "initial commit"
          ^
          |
   ブランチ main <= HEAD

まだeditAはありません。そこでgit switch -c editAを打ったとき、HEADはeditAの先頭のコミットを指すようになります。しかし、editAの先頭はmainのものと同一なので、実質的にHEADが指す位置は変わりません。

commit "initial commit"
          ^
          |
   ブランチ main, editA <= HEAD

その次にコミットをするとどうなるでしょうか。上の例に従えば、editAの先頭のコミットはgit commit "wrote to A.txt"を指すことになります。そこで、HEADもeditAの先頭を指すことになります。

                  -----> commit "wrote to A.txt"
                  |          ^
                  |          |
                  |      ブランチ editA <= HEAD
                  |
commit "initial commit"
          ^
          |
   ブランチ main

ここでgit switch mainをすると、HEADの位置はmainブランチの先頭のコミットへ移動します。

                  -----> commit "wrote to A.txt"
                  |          ^
                  |          |
                  |      ブランチ editA
                  |
commit "initial commit"
          ^
          |
   ブランチ main <= HEAD

ここで、例えばtouch b.txtなどを打って新規にファイルを作成し、それをコミットするとどうなるでしょうか。mainはeditAと異なる方向へ1コミット分進みます。

                  -----> commit "wrote to A.txt"
                  |          ^
                  |          |
                  |      ブランチ editA
                  |
commit "initial commit" --> commit "create B.txt"
                                     ^
                                     |
                            ブランチ main <= HEAD

すると、コミットの履歴が分岐するのです。それはさておき、これでおわかりでしょうか。HEADが指し示す先の移動によって、私たちは見せかけのブランチ移動をしているのです。 また、ブランチがなぜブランチと呼ばれるかも理解できるはずです。ブランチという名は、本流から切り離された1つの枝に由来します。

さて、ここからなぜブランチの切り替えでファイルがなくなるかを考えてみましょう。答えはシンプルで、HEADが移動することによって、ファイルがない状態のコミットが最近のコミットになるからです。

では、これらのブランチを統合するようなこと、つまりmergeについて見ていきましょう。

Git / GitHub の使い方 - マージする

ブランチの使いどころとしては、次が挙げられます。

  • 新規機能開発・機能追加
  • バグ修正などのパッチ当て作業

いずれもどこかのタイミングで開発の本流であるmainブランチにそのコミットを反映させる必要があるものです。

そこで、ブランチにはマージという機能があります。mergeというのは混ぜる、という意味の英単語です。その名の通り、2つのブランチを合流させるためのものです。

コマンドは簡単です。

git merge (マージしたいブランチ名)

で終わります。

しかし、マージが可能である場面とそうでない場面があります。その中で、よくある状況を少し考えてみましょう。

A. Fast-Forwardが可能である状態。

マージするブランチがもう1つのブランチのHEADの先にコミットしている場合、マージするには他のブランチのコミットを単純に引っ張ってくればいいので簡単にマージできます。

B. 直接マージできない状態。

マージするブランチともう1つのブランチでコミットログに差があり、単純に融合できない場合もあります。 そのような場合は、git側でエラーを吐くこともあります。エラーログを読み、コミットログも確認してブランチの履歴がスパゲッティ化しないようにしましょう。

詳しくはman git-mergeもしくはman 1 git-mergeを確認したうえで、次の資料を読んでください。

ただし、マージする機会はそれほど多くありません。必要になったら都度調べながら進めれば十分です。

実際の開発では、安定したブランチ(例: maindevelop)から作業用ブランチを切り、そのブランチ上で変更を加え、レビューなどを経て元のブランチへマージする流れが一般的です。どのような運用ルールにするかはチームごとに異なるため、自分たちのプロジェクトに合ったブランチ戦略を検討してみてください。

最後のリモート・プロジェクトへの貢献編を読み、ブランチを用いた開発手法を学びましょう。