Git のブランチとは何か、またブランチ機能を利用したワークフロー(ブランチモデル)について、実際の運用手順を説明いたします。
参考: Git を利用した開発環境・テスト環境・本番環境の構成
ブランチとは
ブランチ【Branch】とは、修正を行うソースコードのファイルセットのスナップショットです。
自身のクローン環境内にいくつでも作ることができ、それぞれのブランチでそれぞれ別の修正を施すことができます。
ブランチを切り替えることで、ワーキングディレクトリと呼ばれる作業領域に当該ファイルセットが復元されます。
Git ではこのブランチという機能を活用することでソース管理・バージョン管理を実現しています。
ブランチの運用モデル
ブランチの運用モデルについては、Vincent Driessen 氏が 2010 年に公開した有名なワークフロー図がありますね。
参考: A successful Git branching model
おおむねどのようなプロジェクトにおいてもこのモデルで運用していけば問題ないかと思いますが、ここでは最低限の運用ルールで実践可能なモデルについて説明したいと思います。
実際の運用で利用できる最もミニマムなモデルとしては、以下の3種類のブランチがあれば良いでしょう。
master ブランチgit init
やfirst commit
で作成される初期ブランチですが、運用時には本番リリース用のファイルセットとして利用されます。
最終的なテストが終了した必ず正常に動作するソースコードのみが本ブランチにpush
されるべきであり、push
されたタイミングで本ファイルセットがプロダクション環境(本番環境)に適用されます(適用するようフックをかけます)。
中央リポジトリにてリモートブランチとして公開され、clone
してきたローカル環境ではこれを追跡する同名(名前を変えることもできます)のローカルブランチ(追跡ブランチ)が作成されます。
develop ブランチ
リリース前の検証テスト用のファイルセットです。
複数人のコード修正者から共有されるリモートブランチであり、修正のバッティング(コンフリクト)を解消したり、各々の修正をマージした結果、正しく動作するかどうか検証するために利用されます。
本ブランチにpush
されたタイミングで当該ファイルセットがステージング環境(結合・システムテスト環境)に適用されます(適用するようフックをかけます)。clone
してきたローカル環境ではこれを追跡する同名(名前を変えることもできます)のローカルブランチ(追跡ブランチ)を作成してこれを操作します。
feature ブランチ
コードの修正を行うローカルの作業ブランチです。
コード修正者の開発環境やテスト環境で利用されます。
リモートブランチではないため、コード修正者個人にしか見えません。
ブランチ名は任意ですが、慣例的には、feature/{issue}
、feature/{component}
のように feature/ の後に解決しようとする不具合やコンポーネント名などをつけます。
Git 環境の作成
こちらの記事を参考にしてください。
参考: Git を利用した開発環境・テスト環境・本番環境の構成
develop リモートブランチの作成master
ブランチは Git 環境構築時に自動的に作成されますが、develop
ブランチは自動的には作成されません。
リモートブランチはclone
してきた自身のローカル環境から作成が可能です。
まずはローカル環境のブランチの状況を確認します。
>git branch -v # ローカルブランチの一覧を表示
* master bb58af0 First Commit
>git branch -vv # 追跡先の情報も含めたローカルブランチの一覧を表示
* master bb58af0 [origin/master] First Commit
master
ブランチが存在し、origin/master
というリモートブランチを追跡しています。origin
とはリモートリポジトリ名で、リモートブランチは [remote/branch] という名前で表されます。origin
はデフォルトで名づけられる名前ですが、git remote add
等のコマンドで異なる名前での追跡が可能です。
次にリモートブランチのorigin/develop
を作成します。
>git push origin master:develop # git push origin [local:remote]
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To xx.xx.xx.xx:/path/to/remote.git
* [new branch] master -> develop
>git checkout develop
Branch 'develop' set up to track remote branch 'develop' from 'origin'.
Switched to a new branch 'develop'
1行目
ローカル環境のmaster
ブランチを元にして、リモートのdevelop
ブランチ(origin/develop
)を作成しました。
この段階ではまだローカル環境のdevelop
ブランチ(追跡ブランチ)が作成されていません。
2行目
リモートブランチと同名の追跡ブランチを作成したい場合には、checkout
でブランチを切り替えるだけで作成まで行ってくれます。
すでに他の誰かがリモートブランチを作成済みであれば、このcheckout
だけで追跡ブランチの作成が完了します。
ローカル環境がどのようになっているか確認してみます。
>git branch -vv # 追跡先の情報も含めたローカルブランチの一覧を表示一覧を表示
* develop bb58af0 [origin/develop] First Commit
master bb58af0 [origin/master] First Commit
ローカルブランチにdevelop
が追加され、origin/develop
を追跡していることがわかります。
ここで、中央リポジトリのリモートブランチの状況を確認してみましょう。
>git fetch origin # リモート参照の更新
>git branch -r # リモートブランチの一覧を表示
origin/HEAD -> origin/master
origin/develop
origin/hotfix # ★
origin/master
まずfetch
で中央リポジトリの最新の内容を取得してきます。
(ローカル環境で参照するリモートリポジトリの内容は、リモートサーバと接続しない限り更新されません)-r
オプションでリモートブランチの内容を表示します。
他の誰かが新たにorigin/hotfix
(★)というリモートブランチを作成したようですね。
もしorigin/hotfix
ブランチを追跡したければ、git checkout hotfix
で完了です。
ちなみにorigin/HEAD
はカレントのリモートブランチで、ブランチ名を省略した場合などにこれが指定されます。
参考までに、リモートリポジトリの詳細を知るためのコマンドを紹介します。
>git ls-remote # リモートブランチ一覧
From user@xx.xx.xx.xx:/path/to/remote.git
bb58af04e8501a2e71962478a217d51d19f16d0c HEAD
bb58af04e8501a2e71962478a217d51d19f16d0c refs/heads/develop
bb58af04e8501a2e71962478a217d51d19f16d0c refs/heads/hotfix
bb58af04e8501a2e71962478a217d51d19f16d0c refs/heads/master
>git remote show origin # リモートブランチの詳細および追跡ブランチ
* remote origin
Fetch URL: user@xx.xx.xx.xx:/path/to/remote.git
Push URL: user@xx.xx.xx.xx:/path/to/remote.git
HEAD branch: master
Remote branches:
develop tracked
hotfix tracked
master tracked
Local branches configured for 'git pull':
develop merges with remote develop
master merges with remote master
Local refs configured for 'git push':
develop pushes to develop (up to date)
master pushes to master (up to date)
ソースコードの修正からマスターアップまでの流れ
基本的には以下のような流れで進みます。
① ローカルの develop ブランチを元に作業ブランチを作成
まずは作業ブランチを作成します。
仮に作業ブランチの名前をissue01
とすると、以下のようなコマンドになります。
>git checkout -b issue01 develop # git checkout -b [作成ブランチ名] [派生元ブランチ名]
Switched to a new branch 'issue01'
checkout
の-b
オプションはブランチの新規作成およびそのブランチへの切り替えを同時に行います。
派生元ブランチ名は省略可能ですが、必ず指定するように心がけましょう。
思わぬブランチが派生元となり、トラブルを引き起こす原因となります。
派生元ブランチは最新の状態(手順は③の工程と同じです)にしたローカルのdevelop
ブランチが良いでしょう。
直接リモートのorigin/develop
ブランチから派生させても良いですが、リモートの情報はfetch
しないと最新にならないことに注意をしてください。
なお、作業ブランチをマージせず複数作成する場合は、直前の作業ブランチを派生元にします。
② 修正を commit する
ソースコードを修正し、修正内容を作業ブランチ(issue01
)に反映・保存します。
もちろん単体テストはお済みですよね?
>vim sourcecode.php # ソースコードを修正
>git add -A # 変更内容をインデックスに登録
>git commit -m "issue01 commit" # インデックスの内容をブランチへ保存
[issue01 9741247] issue01 commit
1 file changed, 1 insertion(+)
・・・
git add
の-A
オプションは変更があったすべてのファイルをインデックスに登録します。-A
オプションがない場合には、新規に追加したファイルや削除したファイルが登録されません。git add .
でも-A
と同じ働きをしますが、こちらはカレントディレクトリ配下に対してのみということで、どこのディレクトリで作業しているか注意が必要なので、-A
オプションの方が無難ですね。git commit
の-m
オプションでコミットに対するメッセージを入力できます。
③ ローカルの develop ブランチの内容を最新にするorigin/develop
からリモートの内容をpull
し、ローカルのdevelop
ブランチを最新の状態にします。
>git checkout develop # developブランチに切り替え
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
>git pull origin develop
From xx.xx.xx.xx:/path/to/remote
* branch develop -> FETCH_HEAD
Updating 7207908..33a6cce
Fast-forward
test.php | 1 +
1 file changed, 1 insertion(+)
・・・
※マージの際にコンフリクトが起きる可能性があります。
Git 2.27.0 からgit pull
をすると以下のようなメッセージが出力されるようになりました。
warning: Pulling without specifying how to reconcile divergent branches is
discouraged. You can squelch this message by running one of the following
commands sometime before your next pull:
git config pull.rebase false # merge (the default strategy)
git config pull.rebase true # rebase
git config pull.ff only # fast-forward only
You can replace "git config" with "git config --global" to set a default
preference for all repositories. You can also pass --rebase, --no-rebase,
or --ff-only on the command line to override the configured default per
invocation.
git pull
の振る舞いに関して警告を出してくれているそうです。--rebase
オプションについて知識のある方はご自身にとって適切な方を指定してください。
本サイトでは--rebase
は推奨しないので、デフォルトの振る舞いである false を設定します。
設定を行うとこのメッセージは出なくなります。
>git config pull.rebase false
④ 作業ブランチの内容をローカルの develop ブランチにマージするissue01
ブランチの修正をローカルのdevelop
ブランチにマージします。
>git merge --no-ff issue01 # issue01の変更内容をdevelopにマージ
# メッセージを記入するvimが立ち上がるので記入して保存
git merge
の--no-ff
オプションは、ファストフォワード【Fast Forward】させないためのオプションです。
ファストフォワードとは、マージ元のコミット履歴(ブランチツリー)をマージ先のコミット履歴と(可能であれば)融合してしまうというものです。
ファストフォワードが起きるパターンとしては、例えば、develop
ブランチの履歴が A←B←C で、feature
ブランチがCから派生して D←E←F となっている場合、
develop A ← B ← C
↑
feature D ← E ← F
↓ git merge feature (into develop) ↓
develop,feature A ← B ← C ← D ← E ← F
feature
ブランチの最終コミットF はdevelop
ブランチにとっても最終形であるので、Git は自身の処理をシンプルにするため双方の履歴をつなぎ合わせ、HEAD ポインタをコミットF まで持っていきます。--no-ff
オプションを利用すると、このデフォルトの動きを抑止します。
develop A ← B ← C
↑
feature D ← E ← F
↓ git merge --no-ff feature (into develop) ↓
develop A ← B ← C ←---- G
↑ ↓
feature D ← E ← F
feature
ブランチの履歴がしっかりと残り、git merge
のためのコミットG が作られました。
このコミットG をマージコミットといいます。
マージコミットがあることで、マージの取り消しやfeature
ブランチでの一連の編集内容を見ることが容易になるというメリットがあります。
本サイトでは、マージの際の--no-ff
オプションを推奨します。
※マージの際にコンフリクトが起きる可能性があります。
⑤ develop ブランチの内容をリモートの origin/develop ブランチへ push する
マージした内容を、origin/develop
へpush
します。
この作業により自身の修正を他者へ公開することになりますので、Slack などに通知することで他の作業者に周知し、更新を各々のローカル環境にマージしてもらうようにしておくと良いでしょう。
フックを利用していれば、この作業にて修正内容がステージング環境に適用されます。
>git push origin develop
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 3 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 462 bytes | 462.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
To xx.xx.xx.xx:/path/to/remote.git
9e12c4e..6432796 develop -> develop
⑥ develop ブランチの内容をローカルの master ブランチへマージする
前提として、⑤でpush
した修正内容はステージング環境で十分にテストされ、問題ないことを確認する必要があります。
問題ないようでしたら、develop
ブランチの内容をローカルのmaster
ブランチへマージします。
ここでも、--no-ff
オプションをつけましょう。
>git checkout master # developブランチに切り替え
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
>git merge --no-ff develop
# メッセージを記入するvimが立ち上がるので記入して保存
⑦ master ブランチの内容をリモートの origin/master ブランチへ push する
いよいよマスターアップです。
フックを利用していれば、この作業にて修正内容が本番環境に適用されます。
非常に重要な作業ですので、基本的にはマスターアップ用のクローン環境を1つ用意し、専任者が当該環境にて作業をするように運用ルールを規定すると良いでしょう。
>git push origin master
Counting objects: 1, done.
Writing objects: 100% (1/1), 251 bytes | 251.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To xx.xx.xx.xx:/path/to/remote.git
bb58af0..95c2f91 master -> master
ローカルの develop ブランチを起点とするmaster
ブランチとはすなわち本番環境ですので、常に正常動作する状態に保たれていなければなりません。
したがってその1つ手前のdevelop
ブランチですべての問題を事前に解決しておく必要があります。develop
ブランチはリモートブランチであり複数人の作業者に共有されているので、他者の修正をここで初めてマージするわけですが、このマージ作業は様々な問題を引き起こします。
例えば同じファイルを修正したことによって発生するコンフリクト状態や、他者の修正と自身の修正が組み合わさることによって生じる不具合などが起こる場合があります。
これらの問題への対処方法としては、なるべく早めに他者の修正を自身の環境に取り込んでおくことが重要になってきます。
マージ対象の変化が大きければ大きいほどコンフリクトのリスクは高まりますし、自身の修正への影響も大きくなります。
互いの修正部分が複雑に入り組んだ状態になってしまうと、最悪自分の修正コードはもう一度作り直しになってしまう可能性もあることでしょう。master
から作業ブランチを作るよりも、他者の修正がすでに入ったdevelop
から作業ブランチを作ることで問題の早期発見につながり、修正リスクを軽減できると言えるかと思います。
Vincent Driessen 氏のワークフロー図でも作業ブランチはまずdevelop
から派生しています。
以上のことからローカルのdevelop
ブランチは常に最新の状態に保っておくことも Git の運用にあたっては重要となります。
リモートのdevelop
ブランチが更新されたら Slack などに通知することで他の作業者に周知し、通知を受け取ったらすばやく更新を自身のローカル環境にマージしてください。
下図のようにブランチ間の関係を図で表してみると、ローカルのdevelop
ブランチが起点となっている様子がわかりますね。
ブランチツリーの確認
最後にブランチツリーを確認してみましょう。git log
コマンドの--graph
オプションでツリーを見ることができます。--format
でツリーの形が見やすいよう出力内容を調整しています。
# [修正者名] [コミットID] [ブランチ名] [コミットメッセージ1行目]
>git log --graph --all --format="%an %h %d %s"
* John Doe 062e67a (HEAD -> issue04) issue04 commit
* John Doe 04aeba8 (develop) Merge branch 'issue03' into develop
|\
| * John Doe 522ba8d (issue03) issue03
|/
* John Doe 5adf32e (issue02) issue02 commit
* John Doe 0601cf9 Merge branch 'issue01' into develop
|\
| * John Doe d3b712a (issue01) issue01
|/
* John Doe bb58af0 (origin/master, origin/develop, origin/HEAD, master) First Commit
これくらいのツリーであればなんとか見えなくもないですが、さらに複雑なツリーになるとgit log
コマンドではさすがに視認しづらくなってきます。
このようなときは、GitKraken や SourceTree といった無償の Git クライアントツールを利用すると良いでしょう。
下図は SourceTree でログを見た場合の画面です。
参考: GitKraken
参考: SourceTree
参考: Git を利用した開発環境・テスト環境・本番環境の構成
参考: Git コンフリクトの解消
参考: Git のフックを利用したデプロイの方法
参考: git push を Chatwork や Slack へ通知する方法
コメント