1. ホーム
  2. Git

【Git】git cherry-pick の使い方|他のブランチのコミットだけを取り込む方法

Share

別のブランチで直したバグの修正を、今いるブランチにも入れたい。でもそのブランチには他の作業も混ざっていて、まるごとマージするわけにはいかない。そんなときに使うのが git cherry-pick です。名前のとおり、他のブランチのコミットの中から「欲しいものだけをつまんで」自分のブランチに持ってくるコマンドです。この記事では基本の使い方から、複数コミットの指定方法、コンフリクトしたときの進め方、そしてマージとの使い分けまでを順に解説します。

どんなときに cherry-pick を使うのか

いちばん多いのは、緊急の修正を複数のブランチに反映したいケースです。たとえば本番用の main ブランチで見つかった不具合を hotfix ブランチで直して main に入れたとします。開発を進めている develop ブランチにも同じ修正が要りますが、main をまるごとマージするとリリース済みの他の変更まで巻き込んでしまいます。修正コミットだけを develop に cherry-pick すれば、必要な差分だけが入ります。

もうひとつは、コミットするブランチを間違えたときです。新しいブランチを切ったつもりで main に直接コミットしてしまった、という経験は誰にでもあります。正しいブランチに cherry-pick してから、元のブランチ側でそのコミットを取り消せば元通りになります。

他にも、レビュー待ちで長く残っているブランチから「先に入れても問題ない部分」だけを取り出す、実験用ブランチで書いたユーティリティ関数だけを本流に移す、といった使い方があります。共通しているのは「ブランチ全体は要らないが、この1コミットだけ欲しい」という状況です。

基本の使い方

取り込みたいコミットのハッシュを調べる

cherry-pick はコミットを識別子(コミットハッシュ)で指定します。まずは取り込み元のブランチのログを見て、欲しいコミットのハッシュを控えます。--oneline を付けると1コミット1行で表示されるので探しやすくなります。

ターミナル
# hotfix ブランチのコミット履歴を1行ずつ表示する
git log --oneline hotfix

a1b2c3d ログイン時のエラーメッセージを修正
e4f5g6h ヘッダーの余白を調整
i7j8k9l READMEを更新

左側に並んでいる a1b2c3d のような文字列がコミットハッシュです。本来は40桁の文字列ですが、他と区別できる長さ(多くの場合は先頭7桁)だけを書けば Git が判別してくれます。

取り込み先のブランチで実行する

cherry-pick は今いるブランチにコミットを追加するコマンドです。必ず取り込み先のブランチに切り替えてから実行します。

ターミナル
# 取り込み先のブランチに移動する
git switch develop

# 欲しいコミットだけを取り込む
git cherry-pick a1b2c3d

これで a1b2c3d の変更内容が develop の先頭に新しいコミットとして積まれます。ここで大事なのは、元のコミットが移動するのではなく、同じ内容の別のコミットが作られるという点です。コミットメッセージと作者は引き継がれますが、親コミットが変わるためハッシュは別物になります。git log で確認すると、元と同じメッセージのコミットが違うハッシュで載っているはずです。

なお、ハッシュの代わりにブランチ名やタグ名も書けます。git cherry-pick hotfix と書くと、hotfix ブランチの最新の1コミットだけが取り込まれます。ブランチ全体ではないので注意してください。

複数のコミットをまとめて取り込む

ハッシュはスペース区切りで並べられます。書いた順に適用されるので、基本的には古いものから新しいものへの順で並べます。逆順にすると、後のコミットが前提としている変更がまだ無い状態で適用することになり、コンフリクトの原因になります。

ターミナル
# 3つのコミットを古い順に取り込む
git cherry-pick i7j8k9l e4f5g6h a1b2c3d

連続しているコミットなら、A..B という範囲指定が使えます。この書き方は「A より後ろから B まで」という意味なので、A 自身は含まれません。A も含めたい場合は A^..B と書きます。^ は「ひとつ前のコミット」を指す記号で、A のひとつ前から数え始めることで A を範囲に入れています。

ターミナル
# i7j8k9l は含まず、その次から a1b2c3d までを取り込む
git cherry-pick i7j8k9l..a1b2c3d

# i7j8k9l 自身も含めて a1b2c3d までを取り込む
git cherry-pick i7j8k9l^..a1b2c3d

範囲指定は左が古く、右が新しいコミットになるように書きます。順序を取り違えると何も取り込まれないので、不安なときは先に git log --oneline i7j8k9l^..a1b2c3d を実行して、対象になるコミットの一覧を目で確かめると確実です。

よく使うオプション

cherry-pick には、コミットの作られ方を調整するオプションがいくつかあります。実務でよく使うものをまとめます。

オプション意味
-n / --no-commit変更を作業ツリーとインデックスに適用するだけで、コミットはしない
-e / --editコミット前にエディターが開き、コミットメッセージを編集できる
-xメッセージの末尾に取り込み元のコミットハッシュを追記する
--continueコンフリクト解消後に、中断していた処理を再開する
--abort実行前の状態にすべて戻して中止する
--skip今のコミットだけを飛ばして、残りの処理を続ける

-n でコミットせずに内容を確認する

-n を付けると、変更がステージされた状態で止まります。中身を確認してから自分でコミットしたいとき、あるいは複数のコミットをまとめてひとつにしたいときに便利です。

ターミナル
# 2つのコミットをコミットせずに適用する
git cherry-pick -n e4f5g6h a1b2c3d

# ステージされた内容を確認する
git diff --staged

# まとめてひとつのコミットにする
git commit -m "ログイン周りの修正をdevelopに反映"

取り込んだ内容の一部だけが必要な場合も、-n で適用してから不要なファイルを git restore --staged などで外す、という進め方ができます。

-x で「どこから持ってきたか」を残す

-x を付けると、コミットメッセージの末尾に (cherry picked from commit a1b2c3d...) という1行が自動で追加されます。あとから「このコミットはどこ由来なのか」を追えるようになるので、公開ブランチ同士で修正を配る場合はぜひ付けておきたいオプションです。

ターミナル
git cherry-pick -x a1b2c3d

# 出来上がったコミットメッセージ
ログイン時のエラーメッセージを修正

(cherry picked from commit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0)

コンフリクトしたときの進め方

取り込み先のブランチで同じ箇所がすでに変更されていると、cherry-pick は途中で止まります。取り込み元と取り込み先では周辺のコードが違っていることが多いので、マージよりもコンフリクトは起きやすいと考えておいてください。

ターミナル
git cherry-pick a1b2c3d

Auto-merging src/login.js
CONFLICT (content): Merge conflict in src/login.js
error: could not apply a1b2c3d... ログイン時のエラーメッセージを修正
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run "git cherry-pick --continue".

やることはマージのときと同じです。表示されたファイルを開き、<<<<<<< と >>>>>>> で挟まれた部分を正しい形に直します。どのファイルが残っているか分からなくなったら git status で確認できます。解消したら git add でステージし、--continue で再開します。

ターミナル
# 衝突を直したファイルをステージする
git add src/login.js

# 中断していた cherry-pick を再開する
git cherry-pick --continue

--continue を実行するとコミットメッセージの確認画面が開き、そのまま保存すればコミットが作られます。複数のコミットを指定していた場合は、残りの適用が続きます。

「思ったより大変そうなのでやめたい」というときは git cherry-pick --abort です。すでに取り込み済みのコミットも含め、実行前の状態にまるごと戻ります。一方 --skip は、今つまずいているコミットだけを捨てて次に進みます。複数指定したうちのひとつが取り込み先にとって不要だった、というときに使います。

マージとの使い分けと、同じ変更が二重に入るリスク

cherry-pick は同じ内容のコミットを複製します。そのため、あとから同じブランチをマージすると、リポジトリの履歴には同じ変更が2回登場することになります。

実際のところ、変更がまったく同じであれば Git は差分を見て「両方が同じ状態にした」と判断できるので、多くの場合マージは何事もなく通ります。問題になるのは、cherry-pick したあとで取り込み元か取り込み先のどちらかを手直しした場合です。同じ行に対する別々の変更として扱われ、マージのたびにコンフリクトを解消し直すことになります。取り込み元にもう一段の修正が入ったのに、cherry-pick 済みの側が古いまま取り残される、という食い違いも起こります。

判断の目安はシンプルです。ブランチの内容をすべて反映したいならマージ、特定のコミットだけが必要ならcherry-pick。そして、あとでどのみちマージする予定があるブランチからは、なるべく cherry-pick しないことです。どうしても必要なら -x を付けて由来を残しておくと、後日コンフリクトに出会ったときに「これは cherry-pick 済みの変更だ」と判断できます。

また、すでに他の人と共有している(push 済みの)ブランチから cherry-pick したコミットを、あとで git rebase などで作り直すのは避けてください。相手の手元の履歴と食い違い、余計な混乱を招きます。

cherry-pick がうまくいかないとき

bad revision と表示される

指定したハッシュが見つからない状態です。打ち間違いのほかに多いのが、リモートのコミットをまだ取得していないケースです。同僚から共有されたハッシュをそのまま指定しても、手元のリポジトリにそのコミットが無ければ解決できません。git fetch を実行してから、もう一度試してください。ハッシュが短すぎて複数のコミットに一致している場合も、別のエラーとともに拒否されます。そのときは桁数を増やして指定します。

「previous cherry-pick is now empty」と出て止まる

適用しようとした変更が、取り込み先にすでに入っていて差分がゼロになった状態です。同じコミットを二度 cherry-pick したときや、コンフリクトの解消で取り込み先の側を全面的に採用したときに起こります。内容としてはすでに目的を達しているので、git cherry-pick --skip で飛ばして先に進めば問題ありません。空のコミットとしてでも履歴に残したい場合は、git commit --allow-empty を実行してから --continue します。

作業中の変更があって実行できない

コミットしていない変更が残っていると、cherry-pick は上書きを避けるために実行を拒否します。先にコミットするか、git stash で退避してから実行し、終わったあとに git stash pop で戻してください。

取り込んだあとで取り消したい

cherry-pick が完了してしまったあとは、--abort は使えません。普通のコミットと同じように取り消します。まだ push していなければ git reset --hard HEAD^ で直前のコミットごと消せますし、すでに共有している場合は git revert で打ち消すコミットを作るのが安全です。

ターミナル
# まだ push していない場合(直前のコミットを消す)
git reset --hard HEAD^

# すでに共有している場合(打ち消すコミットを追加する)
git revert HEAD

まとめ

git cherry-pick は、他のブランチのコミットを選んで今のブランチに複製するコマンドです。git log --oneline でハッシュを調べ、取り込み先のブランチに移動してから git cherry-pick <hash> を実行するのが基本の流れになります。複数のコミットはスペース区切りで並べるか、A..B(A は含まない)、A^..B(A も含む)の範囲指定で渡します。-n でコミットせずに適用、-e でメッセージを編集、-x で取り込み元の記録を残せます。コンフリクトしたら解消して git add してから --continue、やめるなら --abort、そのコミットだけ飛ばすなら --skip です。便利な反面、同じ変更が履歴に二重に入るという性質があるので、ブランチ全体が必要なときは素直にマージを選ぶ、というのが安全な使い分けになります。

参考ページ