git cherry-pickとは何か・特定のコミットだけをつまみ食いする
前記事で触れたとおり、意図の分かるコミットを積み重ねていくと、後から「あのコミットだけを別のブランチにも入れたい」という場面が出てきます。ブランチ全体を持ってくるほどではなく、特定の1つのコミットだけをつまみ食いしたい。そんなときに使うのがgit cherry-pickです。
特定の1コミットの変更内容だけを、別のブランチに移植する
別ブランチの変更を取り込みたいなら、ブランチ全体をマージするかrebaseするしかないと思うかもしれません。ですがcherry-pickを使えば、そのブランチに含まれる無関係なコミットは巻き込まず、必要な1コミットの変更内容だけを選んで今のブランチに適用できます。
# hotfixブランチで直したバグ修正コミットを、developにも個別に取り込む git switch develop git cherry-pick a1b2c3d # 複数のコミットをまとめて指定することもできる git cherry-pick a1b2c3d e4f5g6h
ここで移植された変更は、新しいコミットとして今のブランチに追加されます。元のコミットと変更内容は同じでも、親コミットやタイムスタンプが異なるため、コミットハッシュはまったくの別物として計算されます。つまりcherry-pick後のコミットは、見た目の内容こそ同じでも「元のコミットのコピー」ではなく「別人格の新しいコミット」として扱われるということです。
ビュッフェ形式の食事に例えると、cherry-pickは「大皿に並んだ料理の中から、食べたい一品だけを自分の皿に取り分ける」動作です。大皿(元のブランチ)に並んでいた他の料理(他のコミット)はそのまま残り、自分の皿に取り分けた一品だけが今の作業に加わります。ただし、取り分けた瞬間にそれは大皿の上の料理とは別の、自分の皿の上の一品になります。同じレシピで作られていても、大皿の分と自分の皿の分は別々に存在している、という点が次の話につながります。
後でマージすると「同じ内容の別コミット」が2つ並んでしまう
cherry-pickした後、いずれ元のブランチをマージすれば自然に統合されて問題ないだろうと考えるかもしれません。ですがcherry-pickされたコミットは新しいハッシュを持つ別コミットとして扱われるため、後になって元のブランチをマージすると、Gitからは「内容は似ているが別々の変更」に見え、同じ修正が2つのコミットとして両方とも履歴に残ってしまいます。
# hotfixのバグ修正をdevelopにcherry-pickしておいた後 git switch develop git cherry-pick a1b2c3d # develop側に新しいハッシュのコミットが生まれる # 後日、hotfixをmainにマージし、mainをdevelopにもマージすると... git switch develop git merge main # → developの履歴に「元のa1b2c3d」と「cherry-pick後の別ハッシュ」が # 両方残ってしまう(内容は同じだが別コミットとして扱われるため)
コピー機に例えると、cherry-pickは「原本(元のコミット)をコピーして、写しを別の部署(別のブランチ)に配る」動作です。後から原本そのものもその部署に回覧されると、部署の人からすれば「見覚えのある内容の書類が2部ある」状態になります。どちらも同じ内容ですが、原本と写しはファイル上は別物として扱われるため、放っておくと両方とも保管され続けてしまいます。
よくあるはまりポイント:hotfixの二重管理で、developの履歴がどんどん膨らむ
本番の緊急修正をhotfixブランチで行い、それをmainとdevelopの両方にcherry-pickで配る、という運用は実務でよく見かけます。ですがこの運用を繰り返していると、develop側には「cherry-pickで個別に取り込んだコミット」が積み重なっていき、いずれmainとdevelopを正式にマージするタイミングで、前述の重複コミット問題が一気に噴出することがあります。個々のコミット自体はコンフリクトなく取り込めていても、後から見返すと同じ修正が別ハッシュで何度も履歴に登場し、「このバグ修正は結局何回コミットされているのか」が分からなくなります。恒久的に複数のブランチへ同じ変更を配り続ける必要があるなら、cherry-pickをその場しのぎで繰り返すのではなく、共通の親ブランチを設けて一度そこにマージしてから両方に流す、という構成を見直すきっかけにするとよいでしょう。
- git cherry-pick
- 指定したコミットの変更内容だけを、今のブランチに新しいコミットとして適用する操作
- -n(--no-commit)
- 変更内容だけを適用し、コミットは作らないオプション。複数コミットの変更をまとめて1つのコミットにしたい場合に使う
- --continue / --abort
- cherry-pick中にコンフリクトが起きた場合、解決して再開するなら--continue、取りやめて元に戻すなら--abortを使う
cherry-pickは「本来は別のブランチにある変更を、一時的に緊急でこちらにも入れたい」という単発の場面には向いていますが、同じ変更を複数のブランチへ継続的に配り続ける手段としては不向きです。繰り返し使う必要が出てきたら、ブランチ構成そのものを見直すサインだと捉えましょう。
ここまで「誰が何を変更したか」を扱ってきましたが、最後に「この行を最後に触ったのは誰か」を調べるgit blameを見ていきます。