タイトル適当。
まともに書いてブロッグにアップしたかっt
必要なもの
- gitに対する愛
- 開発プロセスに対する愛(わぁいレビュー あかりレビュー大好き)
前提状況
申し訳ないけど「gerrit の機能が一通り使える」ことが前提のフロー。githubは読み替えて。gerrit 自体の使い方も、正直よい日本語の記事が無いので時間があれば書くかもしれない。
で、以下のような機能を作る
- 1) Foo 機能の処理モジュール(Service)
- 2) Foo 機能のHTTPサーバ側エンドポイント(Controller)
- 3) Foo 機能のブラウザ側の表示(View)
- 4) Foo 機能のブラウザ側からのサーバ側への連携(Client-side Cooperation)
2 は 1 に、 4 は 1-2 と 3 に依存している。とりあえず上から作っていく。この時点では 2 までできたよ、とする。後、トピックごとに(タグだと後述するように不都合なんで)ブランチを切り直し(features/N)、開発のてっぺんは topic-head とでもしておこう。
---(*)---(1)---(2)...(3)...(4) [topic-head]
git rebase -i
まず、(1)のみレビューが帰ってくる。(1)に含まれる3つのコミットのうち「1つめ」にコメントがついたので直したい。
git checkout features/1 git rebase -i HEAD~3
pick abcd12 Implement Foo model pick ef3456 Implement Foo service pick 7890ab Add some validations ....
上記の p(ick)
の個所を変更する。
e abcd12 Implement Foo model pick ef3456 Implement FooService pick 7890ab Add some validations #... # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. #
e は edit 。他にもよく使うのは、 s(quash) と f(ixup) か。まあ書いてあるね。なお、コミットの行を消せばそのコミットは無かったことになる(取り扱い注意、このスタイルで開発していると git reflog が溜まりすぎてうっかり消すと探せない)し、並び替えたいときはそのままカットペーストする。一種のコミット操作DSLか。
で、リベースが始まり、「e」のコミットの直後の辺で止まる。
# On branch ~~~(実際の文言はなんかもっと違う) nothing to commit (working directory clean)
git diff --stat HEAD~1..HEAD
ってコマンドを発行すると、その直前のコミットで何がどう変わったかが分かるので、ぼくはエイリアスに登録している。で、直したいファイルをまずは直し、 commit --amend で例の直前のコミットに溶かし込む。
git add . git commit --amend
メッセージは変えても変えなくても良い。 # gerrit であれば、このとき「Change-ID」を変えてはならない。
そうしてリベースを続ける。
git rebase --continue
最終的に「過去が変更された」 (1) を実装した新しいブランチ (1') が生まれるので、 gerrit なり何なりでさっきと同じトピックにプッシュする。
- rebase するので、各コミットのハッシュ値が変わる(tagとかは、消える)
- ハッシュ値が変わるので、既存の先々のコミット(すでにできてる2)につながらない「新しいブランチ」が生えている。
そして、無事コミットがレビューを通ってマージされました。良かった。承認されてうれしい!
git checkout master && git pull --rebase ---(*)---(1') [master] | +----(1)---(2)---(3)...(4) [topic-head]
あっ!大変だ!
- 今開発のブランチ topic-head と全然関係ないところに master が伸びていった。 (2) 以降の今後やいかに?
- さらに (3) までできてしまった!
git rebase master
慌てず騒がず、 (2) の位置に立ち戻り、
git checkout topick-head git rebase master ---(*)---(1') [master] | | | +----(2)---(3) [topic-head] | +----(1)---(_2)--(_3)... [abandon]
これで、無事 (2) は (1') の続きから開発されていることにできる。実際、(1')の変更も無事反映された状態になる。
git rebase master 、日本語に訳すと「現在のブランチの根っこを master のてっぺんにする」ぐらいの意味である。
時々 merge のようにコンフリクトするかもしれない。そのときは、 git status
とか git diff
とかでどうなってるか確認して修正できる。特に git diff
はコンフリクト専用の表示になるのでえらく便利。
git add . git rebase --continue
原則、「コンフリクトは、コミットしてはいけない」。そのままコンティニューだ。
ちなみに、
---(*)---(1) | +----(2) | +----(3)---(6) | +----(4) | +----(5)
みたいな開発をしている際でも、 git rebase master
で(forkやmergeが)多い日も安心です。
git rebase --onto
ところで、レビューに出したものの、「来週レビューすっから」と言われているが炎上しているので仕方なく、週末に家で(3)どころか(4)まで作ってしまったとしよう(事実とは無関係なたとえです)。
翌週無事マージされました。
---(*)---(1') [master] | +----(1)---(2)---(3)---(4) [topic-head]
色々あって、以下のようになりました。
---(*)---(1')--(2')--(3') [master] | | | | | +----(3w) | | | +----(2x)--(3x)--(4x) | | | | +----(2y) (3y)--(4y) [topic-head ...?] | +----(_1)--(_2)--(_3)--(_4)
色々あったんです。「仕様変更!そういうのもあるのか!」みたいな。
この状態で、素直に git rebase master topic-head
するのは無限コンフリクトでダルい。というか、(4y)のコミットだけ奇麗に欲しいんです。どうすればいいの。
(4x)にあたるコミットを一つ一つ cherry-pick するのも手ではあるが、 SHA1 をコピペ間違えたりしそうで危ない。あなたと、 rebase --onto
。
git rebase --onto master features/3y features/4y
git rebase --onto master features/3y features/4y
とは、日本語にすると「features/4y(省略するとカレントブランチ)なんですけど、今 features/3y の根っこのところから、 master のてっぺんに挿し木しましょう」ぐらいの意味なんじゃないかな。
で、こうすると、若干のコンフリクト修正の後(多くとも (4y) 相当のコミット回数分しかないですから)、
---(*)---(1')--(2')--(3')--(4y') [master] | | | ... どうでも良いブランチ
本質的な「(4)をどうするか?」に集中できる。この状態なら、一回ぐらいレビューさし戻っても心が折れないはず!
とはいえ、 git は頭が良いのでたいていは rebase master で事足りるはずだし、さらに言えばなるべく --onto 使わないように無理に仕事を進めまくるべきではない。ちゃんとレビューでフィードバックをもらいましょう。
まとめ
git rebase
を効率的に使えば、レビューを回しながら時間を有効活用して開発が進められる。コミットを無駄にしない!- とはいえ、やり過ぎ注意。
言っていないこと
- 前提として、「1つ1つのコミットを意味のある、奇麗なものに」を徹底していないと戦えない
- とても参考になるウェッブサイトがあるので読んでおきましょう → https://github.com/rails/rails/commits/master
git diff
とか活用すればコンフリクト修正のある程度の助けになるが、割とセンスや慣れ、あと人のコミットを読む力が要る- reflog ちょう溜まる。
git gc
しましょう。