💼

Git でトランクベース開発するときの手癖

2022/08/06に公開約2,800字

これは何?

タイトル通り。

主にプロマネ向けと思われる、トランクベースのメリットを説明する文書は web 検索で割とヒットする。
けれども、Git を使う作業者目線で、日々の作業を step by step で解説しているものが見当たらなかった。

以下、筆者の手癖であり my best であって、better は何処かにあるかもしれない。

初期設定

clone までは済ませてあるものとする。

git config pull.rebase=true
git config pull.ff=only

pull 時にマージコミットが生成されるのを防ぐ。トランクベース開発時では必須の設定。

ルーティン

作業ブランチ作成

以下、main ブランチがトランクであると仮定する。

git fetch --all --prune
git checkout -b {作業ブランチ名} origin/main

最初の fetch で origin/main ブランチがリモートと同期される。また、レビュー後のマージが済んでリモートで消されたブランチが掃除 (prune) される。

トランクベースが正常に機能している場合、ローカルの main ブランチはリモート側のキャッシュブランチである origin/main ブランチよりも常に古くなる。origin/main を使ったほうが手数が減る。(ローカルの main ブランチは git branch -D main などして削除するのも一考の余地あり)

作業ブランチ内作業

作業する。コード書くなり、config 書くなり。

git add {変更したファイル}
git commit -m '適当なコメント' -s

発展: commit --amend -C HEAD の利用

2 回目以降の commitcommit --amend -C HEAD とする手もある。

メリットは下記の通り。

  • コミットが常に一つになるので、レビュー依頼を出しやすい
  • 手前のコミットメッセージが再利用されるので、毎回コミットメッセージを考えなくて良い

デメリットは下記の通り。

  • 作業ブランチ内の改変履歴を消すに等しいので、作業途中で過去に巻き戻せなくなる。

作業規模や試行錯誤となる可能性など天秤にかけて、判断したほうが良い。

trunk への rebase

git fetch --all --prune
git rebase origin/main

定期的な fetch - rebase 実行でトランクに追随したほうが安全。(数時間〜1日毎)
特に rebase merge しか許さない設定がなされた GitHub や GitLab を使っている場合は必須。

fetch については既に説明したとおり。
rebase は、下記の作業を裏でやってくれる。

  1. 作業ブランチの分岐元を最新のトランクに移動し
  2. 作業ブランチにしか無いコミットを cherry-pick で積む
  3. (ただし次に述べるように、衝突回避はやってくれない。 git merge を使える環境を羨み嘆き悲しんでも、逃げる道はない。)

衝突解消

rebase では、しばしば自分の変更とチームメイトの変更とが衝突(conflict)する。

衝突時のコードを修正フローは下記のようになる。

git status #状況確認
{編集}
git add {編集したファイル}
git rebase --continue

衝突の範囲が思ったより大きく複雑な場合、 rebase を取り消して深呼吸してみるのも悪くない。

git rebase --abort

行おうと思っていた修正の全てが既にトランクに取り込まれていた場合には、skip で切り抜けることもできる。

git rebase --skip

skip で切り抜けないと、差分がないコミットが作られる。大抵の場合、意味がないので --skip を使うことになるはず。

作業ブランチ上の複数コミットを単一コミットに整理

git rebase -i origin/main

エディタが立ち上がるので、いい感じにまとめる。主に使うのは squash fixup 辺り。
Gerrit を使っている場合は、 Change-Id を不用意に削除しないよう注意。

push とレビュー依頼

GitHub や GitLab なら

git push -fu origin {作業ブランチ名}

したあとに、プルリクエストやマージリクエストを出す。

push で -f オプションを付けるのは不穏に感じるかもしれないが「レビュー対象は 1 commit」というルールを設定した上で force push を許すほうが見通しが良くなりがち。
(その際には、事故防止のため、トランクとなるブランチには force push 禁止の設定を行ったほうがよい)

Gerrit なら

git push -u origin HEAD:refs/for/master

注意点

単一のコミットでは、単一の課題のみを、解決する

「いろいろやりました」厳禁。

単一のコミットが、単一の課題のみを解決していることが明らかなら、特定コミットででグレードした時は、そのコミットに対する revert コミットを投げ込めば解決する(可能性が高い)。
複数課題を解決している場合、切り分けた新規コミットで対処する必要があり、複雑になる。複雑さは、新たな別のデグレードの引き金となるかもしれない。

作業ブランチを放置しない

放置すればするほど、トランクとの乖離が大きくなり、rebase に失敗する可能性が高くなる。conflict 解消は、生産性に寄与しない。

tips

git-gone

上流にマージされ、不要となったローカルブランチが、手元に残り続ける。
これらを一括で消してくれる git-gone というシェルスクリプトがある。
何種類か亜種があるようなので、気に入ったものを使うとよい。

https://gist.github.com/search?l=Shell&q=git-gone

Discussion

ログインするとコメントできます