Git入門:バージョン管理のきほん
必要なコミットだけ取り込む(cherry-pick)
この回でやること
ブランチ全体ではなく、特定のコミット 1 つだけを別のブランチへ持ってくる cherry-pick を学びます。コピーであってコミットの移動ではないという仕組みと、使ってよい場面を見極めましょう。
- 読む 約 8 分
ブランチごと入れると、未完成のものまで入る
feature-search で検索機能を開発中で、まだ完成していません。ところがその作業の途中に、ついでに直したバグ修正のコミットが 1 つ入っています。そのバグは本番で起きているので今すぐ main に入れたい。でも検索機能は未完成なので、ブランチごとマージするわけにはいきません。
このとき使うのが git cherry-pick です。ブランチから欲しいコミットだけを摘み取ってきます。
ターミナル
$ git log --oneline --all --graph
* 7d2e918 (feature-search) 検索フォームの見た目を整える
* c4a1b03 日付のゼロ埋めが抜けていたのを修正
* 8e5f7a2 検索 API の下書き
* 3b9c0d1 (HEAD -> main) ユーザー一覧のページング欲しいのは c4a1b03 だけです。取り込みたいブランチに立って、ハッシュを指定します。
ターミナル
$ git switch main
$ git cherry-pick -x c4a1b03
[main 5f8a2c1] 日付のゼロ埋めが抜けていたのを修正-x を付けると、できたコミットのメッセージの末尾に (cherry picked from commit c4a1b03...) が自動で追記されます。あとから「この修正はどこから持ってきたのか」を追えるので、チームで使うときは付ける習慣にしておくと後が楽です。
移動ではなくコピーなので、同じ変更が 2 か所に残る
いま起きたことをよく見てください。feature-search の c4a1b03 は消えていません。そして main 側には 5f8a2c1 という別のハッシュを持つ新しいコミットができました。
cherry-pick はコミットを引っ越しさせるのではなく、そのコミットが持つ変更内容を取り出して、いまいるブランチの先端に新しいコミットとして積み直す操作です。ハッシュが変わるのは、Git のコミットが変更内容だけでなく「どのコミットの次に来るか」も含めて計算されるためです。作成者は元の人のまま引き継がれ、そのコミットを実際に作った人だけが自分になるので、誰が書いたコードかという情報は消えません。
同じ変更が 2 つのブランチに別々のコミットとして残ることになります。リリース済みの安定版に修正だけを届けたい場面では、これが意図した状態です。逆に、develop から main へ 20 個のコミットを 1 つずつ cherry-pick する、といった同期の道具としての使い方はしないでください。 どちらが本物か分からない履歴ができ、あとでマージしたときにコンフリクトが噴出します。日常の同期はマージで行い、cherry-pick は緊急の修正取り込みのような例外に限ります。
単独で成立しないコミットは、静かに壊れる
いちばん怖い失敗です。修正コミット c4a1b03 が、その 1 つ前の 8e5f7a2 で追加された関数を呼んでいたとします。c4a1b03 だけを cherry-pick すると、存在しない関数を呼ぶコードが main に入ります。
cherry-pick 自体は成功し、コンフリクトも起きません。 壊れるのは実行したときです。取り込む前に git show c4a1b03 で中身を読み、そのコミットが単独で成立するかを必ず確かめてください。
取り込み先のコードが元のブランチと違っていれば、当然ぶつかります。マーカーの直し方は通常のコンフリクトと同じですが、完了のコマンドが違います。
ターミナル
$ git add src/utils/date.js
$ git cherry-pick --continue途中で分からなくなったら、迷わず git cherry-pick --abort を打ってください。始める前の状態に完全に戻ります。中途半端に解決したまま放置するほうが、はるかに厄介な状態を作ります。
- cherry-pick はコミットの移動ではなくコピー。元は残り、取り込み先には別ハッシュの新しいコミットができる
- 単独で成立しないコミットを取り込むと、コンフリクトなしで壊れたコードが入る。
git showで先に読む - 日常の同期はマージで行う。cherry-pick は緊急の修正取り込みなど例外的な場面に限る