Git
研修

初めてGitを使う人向けに情報をまとめてみた(ちょっと応用編)

以前投稿した記事の続きです。
前回は、「Gitのリポジトリ構造」と、「よく使う基本的なGitコマンド」について説明しました。
今回は、ちょっと応用ということで、チーム開発をしていると必須になる、
「ブランチ戦略」と「merge,rebaseコマンド」に焦点を当てて説明します。

ブランチ戦略

ブランチ戦略とは、Gitを活用する大きな特徴である「並行開発」をより効率的に行うために、各ブランチをどのように管理し、どのブランチに統合させるのかといった明示的な運用ルールのことです。

ブランチとは

ひとつのソフトウェアやプロダクトから分岐させて、複数のメンバーが同時に機能追加や、バグ修正を行うなどします。
また、分岐したブランチは他のブランチの影響を受けないため、同じリポジトリ中で複数の変更を同時に進めていくことができます。

(参考)git flow

Gitを活用したブランチ運用戦略のモデルケースの1つです。(git flowとは)
その他にもgithub flowという、git flowを簡略化したモデルケースもあります。
それらの違いについては、以下のQiitaの記事が簡潔にまとまってたので、参考にしてみてください。1

統合ブランチとは

リリース版が何時でも作成可能なようしておくためのブランチです。
また、トピックブランチの分岐元としても使用するため、常に安定した状態を保つ必要があるため、Jenkins等のCIツールを使用した自動ビルドやテストで、品質担保すると良いでしょう。
何らかの変更や機能追加を行う場合は、この統合ブランチからトピックブランチを作成して作業を行います。
(Subversionで言う、trunkと概念は同じです。Gitではmasterを指すことが多いです。)

トピックブランチとは

機能追加やバグ修正といったある課題や特定の目的実現に関する作業を行うために作成するブランチです。
複数の課題に関する作業を同時に行う時は、その数だけトピックブランチが作成されます。
トピックブランチは統合ブランチから分岐する形で作成され、作業が完了したら統合ブランチに取り込まれます。

image.png

ブランチの切り替えとブランチの統合

ブランチ戦略を実施するにあたって、具体的にどのようにブランチを新規作成し、作業が終わったブランチをどのように統合ブランチに取りこむかを説明します。

ブランチの切り替え

Gitでは、下記のコマンド実行することで、ブランチの新規作成・切り替えを行います。

  • git branch(ブランチの作成)
    • トピックブランチを作成する。
    • git branch <branchname>:新規ブランチを作成
    • git checkout -b <branchname>:新規ブランチを作成し、そのブランチに切り替える
  • git checkout(ブランチの切り替え)
    • 作業するブランチを切り替える。
    • git checkout <branchname>:指定したブランチにローカルブランチを切り替える

ブランチの統合(rebase)

統合ブランチにトピックブランチを取りこむ方法は、rebasemergeの二つがあります。
まずはrebaseについて説明します。

rebaseとは、その名の通り、「baseを再定義」します。
大まかな流れを説明していきます。

image.png

現在の情報を整理すると以下になります。

【masterブランチ】
・リリースなどに活用されているブランチ。
・他からの変更も取りこんでおり、現在の最新バージョンは「D」(バージョン名は便宜上です)。

【developブランチ】
・masterブランチのバージョン「B」から分岐したbugを修正するためのブランチ
・developブランチには、masterブランチのバージョン「B」をベースに、X,Yという2つのバグ修正コミットを実施した。

この状態でbugfixブランチで、
rebaseコマンドgit rebase <repository>/<branch name(今回はmaster)>
を実行し、「masterブランチの最新コミットでbaseを再定義」すると以下のようになります。

image.png

developブランチの起点となるmasterブランチのバージョンが「B」から「D」に変更されます。
その際に、これまでのbugfixで変更していたコミット「X,Y」は「X',Y'」になります。
これは、rebaseを実行することで、bugfixでコミットされていた内容はそのままに、「baseを付け替えたことによって、X,Yは別コミットと認識され、コミットハッシュ値が変更」されるためです。

rebaseした時点では、bugfixブランチ内にはmasterブランチの最新の情報+bugfixの修正が取りこまれている状態なので、「X',Y'の変更をmasterブランチにmergeして取りこむ」必要があります。
しかし、mergeに際して、bugfixのリモートリポジトリにrebaseしてC,Dのコミットを取りこんだ結果をpushする必要がありますが、通常通りにpushした場合は以下のようなエラーが発生します。

$ git push origin develop
To <git repository informarion>
! [rejected] develop -> develop (non-fast-forward)
error: failed to push some refs to '<git repository informarion>'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

これはnon-fast-forward(大まかに言うと、ローカルのコミット情報がリモートリポジトリの最新コミットに対して単純に付け足せる状態になっていない)のためエラーが発生しています。
原因は、rebaseしたことにより、X,Yのコミットが無くなり、X',Y'というGitが認識していないコミットが生成されたため、単純に取りこめなくなったためです。
対処するには、
git push -f origin developのように
-fオプション(forceプッシュ)をつけて強制的に上書きするしかありません。

無事mergeが完了したら、以下のようなツリーになります。

image.png

ブランチの統合(merge)

rebaseが、平たく言うと統合ブランチの先頭にトピックブランチの情報を付け加えることだとすると、mergeは統合ブランチとトピックブランチの両方の変更を取りこんだマージコミットを作成し、統合ブランチの先頭がそのコミットに移動します。

image.png

上記のようなツリー構造をしている場合に、mergeコマンドを実行すると、以下のような構造になります。

image.png

rebaseとmergeの違い

rebaseとmergeはどちらもブランチを統合させる動きをしますが、「履歴管理」で差異があります。
・rebase:履歴は単純になるが、元のコミットから変更内容が変更される。
そのため、元のコミットを動かない状態にしてしまうことがある。
・merge:変更内容の履歴はそのまま残るが、履歴が複雑になる。

どちらを採用するかは、開発の運用次第にはなりますが、使い分ける場合は以下のように分類すると良いと思います。

・トピックブランチを主語として、
統合ブランチの最新のコードを取りこむ場合:rebase
(GitHubやGitLabを使っている場合は、rebaseしたトピックブランチを統合ブランチに対してPull/Merge Requestを出すことになるイメージです)
・統合ブランチを主語として、
特定のトピックブランチの変更を統合ブランチに取り込む場合:merge
(素のGitを使う場合は統合ブランチからトピックブランチのコミットを取る動きになるため、こちらを使う機会が多いと思います)