ReDevLab
Git 19分で読める

Git cherry-pickのコンフリクト解決手順と実践ベストプラクティス

Git cherry-pickで発生するコンフリクトの原因から解決手順、rerereやアトミックコミットを活用したベストプラクティスまで、実例コード付きで解説します。

編集部
Git cherry-pickのコンフリクト解決手順と実践ベストプラクティス

Git cherry-pickのコンフリクト解決は、正しい手順を押さえれば怖くありません。 cherry-pickは内部で3-way mergeを使います。 そのため通常のマージと同様のコンフリクトが発生します。 解決の流れはgit status→手動編集→git addgit cherry-pick --continueの4ステップです。 たとえば、hotfixブランチから本番ブランチへ 特定のバグ修正だけを取り込む場面を考えましょう。 コンフリクトが起きてもCHERRY_PICK_HEADrerereによる自動解決を活用すれば短縮できます。 この記事では環境準備からコンフリクト解決、 マージコミットの扱い、チーム運用ルールまで網羅しています。

前提条件:cherry-pick実行前に確認すべき環境と準備

cherry-pickのコンフリクトで手間取る原因の多くは、実行前の準備不足です。 ここでは、作業を始める前に確認すべき2つのポイントを整理します。

必要なGitバージョンとworking treeのクリーン状態チェック

cherry-pickを安全に実行するには、Gitのバージョンと 作業ツリーの状態を事前に確認してください。

  1. Gitバージョンを確認する

rerere(後述)や--rerere-autoupdateを使うには Git 2.x以上が必要です。以下のコマンドで確認します。

# macOS / Linux
git --version
# Windows PowerShell
git --version

出力例は次のとおりです。

git version 2.43.0

2.30以降であれば、本記事のコマンドはすべて動作します。

  1. working tree(作業ツリー)をクリーンにする

Git公式ドキュメントによると、 cherry-pick実行前にworking treeがクリーンである必要があります。 未コミットの変更が残っていると、コンフリクト解決時に どの差分がcherry-pick由来か判別できなくなるためです。

# macOS / Linux
git status
git stash  # 未コミットの変更を一時退避
# Windows PowerShell
git status
git stash  # 未コミットの変更を一時退避

git statusの出力に nothing to commit, working tree clean と表示されれば準備完了です。

リモートブランチのフェッチ(bad objectエラーを未然に防ぐ)

cherry-pickで最も多いエラーの一つがbad objectです。 Graphite Guidesによると、 原因は主に3パターンあります。

  • コミットハッシュの誤記(タイポ)
  • リモートのコミットが未フェッチ
  • リポジトリの破損

中でも最頻出はリモートブランチの未フェッチです。 他のメンバーが作成したコミットを取り込む場合、 ローカルにそのコミット情報がなければbad objectになります。

cherry-pick実行前に、必ずフェッチを行ってください。

# macOS / Linux — 全リモートブランチの最新情報を取得
git fetch --all
# Windows PowerShell — 全リモートブランチの最新情報を取得
git fetch --all

特定のブランチだけ取得したい場合は以下のように指定します。

# macOS / Linux
git fetch origin feature/target-branch
# Windows PowerShell
git fetch origin feature/target-branch

フェッチ後、対象コミットが存在するか確認します。

# macOS / Linux — コミットの存在確認
git cat-file -t <コミットハッシ>
# Windows PowerShell — コミットの存在確認
git cat-file -t <コミットハッシュ>

commitと表示されれば、そのハッシュは有効です。 fatal: Not a valid object nameと出た場合は、 ハッシュの誤記かリポジトリの破損を疑ってください。

cherry-pickでコンフリクトが起きる仕組みと典型パターン

git cherry-pickは、指定したコミットの変更を 現在のブランチに適用するコマンドです。 内部では単純なパッチ適用ではなく、3-way merge(3方向マージ)を使っています。 この仕組みを理解しておくと、コンフリクト発生時に 「なぜ競合したのか」を素早く判断できます。

3-way mergeによるパッチ適用の内部動作

cherry-pickは、以下の3つのバージョンを比較してマージを試みます。

バージョン内容
Base(共通祖先)cherry-pick対象コミットの親コミット
Ours(現在側)現在のHEADが指すファイル状態
Theirs(取込側)cherry-pick対象コミットのファイル状態

Gitはまず、BaseとTheirsの差分を「パッチ」として抽出します。 次に、そのパッチをOurs(現在のブランチ)に適用します。 BaseとOursの間で同じ行が変更されていなければ、自動マージが成功します。 両方が同じ行を変更していた場合、Gitは自動判断できずコンフリクトを報告します (How git cherry-pick and revert use 3-way merge)。

コンフリクト発生時、GitはCHERRY_PICK_HEADというrefを作成します。 このrefが対象コミットを参照しており、インデックスには 最大3つのバージョン(Base/Ours/Theirs)が記録されます (Git公式ドキュメント)。

コンフリクトが発生する3つの典型シナリオ

cherry-pickでコンフリクトが起きるパターンは、大きく3つに分類できます。

1. 同一行の並行編集

最も多いケースです。 ブランチAとブランチBで同じファイルの同じ行を変更していると、 cherry-pick時に競合します。 hotfix対応で同じ設定値を別々の値に変更した場合などが典型例です。

2. ファイル構造の変更との衝突

cherry-pick対象のコミットが編集したファイルが、 現在のブランチではリネーム・削除されているケースです。 Gitはファイルの対応関係を解決できず、コンフリクトを報告します。

3. 依存関係のあるコミットの部分取り込み

コミットAの変更を前提にコミットBが作られている場合、 コミットBだけをcherry-pickすると競合します。 たとえば、コミットAで追加した関数をコミットBが修正するケースです。 この場合はコミットAから順にcherry-pickするか、 範囲指定で両方を取り込む必要があります。

# macOS / Linux:依存関係のあるコミットを範囲指定で取り込む
git cherry-pick A^..B
# Windows PowerShell:同様の操作
git cherry-pick A^..B

ここでA^と指定するのがポイントです。 範囲指定の開始コミットは「取り込みたいコミットの1つ前」を指す必要があります (Qiita - 複数のcommitをまとめてcherry-pick)。

コンフリクト解決の基本手順【4ステップで完了】

cherry-pick中にコンフリクトが起きても、手順は4つだけです。 Git公式ドキュメントに沿って、 落ち着いて進めれば問題ありません。

Step 1-2:git statusでの特定とコンフリクトマーカーの読み方

まずgit statusで競合ファイルを確認します。

# macOS / Linux
git status
# Windows PowerShell
git status

出力にboth modified:と表示されたファイルが対象です。 該当ファイルを開くと、以下のようなマーカーが挿入されています。

<<<<<<< HEAD
現在のブランチの内容
=======
cherry-pick元のコミットの内容
>>>>>>> (cherry-pickしたコミットハッシュ)

<<<<<<< HEADから=======までが今のブランチの状態です。 =======から>>>>>>>までがcherry-pick元の変更にあたります。 cherry-pickは内部で3-way mergeを使っています。 同じ行を両方が変更した場合にこのマーカーが出ます (参考:Julia Evans - How git cherry-pick and revert use 3-way merge)。

マーカーを含む行をすべて削除し、最終的に残したいコードだけにしてください。

Step 3-4:git addとcherry-pick —continueで再開

編集が終わったら、ステージングして操作を再開します。

# macOS / Linux
git add path/to/conflicted-file.ts
git cherry-pick --continue
# Windows PowerShell
git add path/to/conflicted-file.ts
git cherry-pick --continue

git addは「このファイルの競合を解決しました」とGitに伝える操作です。 --continueを実行するとコミットメッセージの編集画面が開きます。 保存すればcherry-pickが完了します。

解決に自信がない場合は、以下で操作前の状態に戻せます。

# macOS / Linux — cherry-pick全体を取り消す
git cherry-pick --abort
# Windows PowerShell — cherry-pick全体を取り消す
git cherry-pick --abort

複数コミットの連続cherry-pickで中断した場合の再開フロー

範囲指定でcherry-pickする場合、構文に注意が必要です。 開始コミットは取り込みたいコミットの1つ前を指定します (参考:Qiita - 複数のcommitをまとめてcherry-pick)。

# macOS / Linux — abc1234の次からdef5678までを適用
git cherry-pick abc1234..def5678
# Windows PowerShell — abc1234の次からdef5678までを適用
git cherry-pick abc1234..def5678

途中でコンフリクトが発生すると、処理はそのコミットで一時停止します。 Gitは.git/sequencerディレクトリに残りのコミット情報を保持しています。 再開の流れは単一コミット時と同じです。

  1. git statusで競合ファイルを確認する
  2. マーカーを手動編集して解決する
  3. git addでステージングする
  4. git cherry-pick --continueで次のコミットへ進む

コンフリクトが連続する場合は、コミットごとにこの手順を繰り返します。 特定のコミットだけスキップしたいときは git cherry-pick --skipを使ってください。 すべて中断したい場合は--abortで開始前の状態に戻ります。

マージコミットのcherry-pickと-mオプションの使い分け

通常のコミットと違い、マージコミットには「親」が2つ存在します。 そのためcherry-pick時には-mオプションで基準となる親を指定する必要があります。 ここを理解しておくと、hotfix適用時の混乱を防げます。

-m 1と-m 2の親選択ロジックを図解で理解する

マージコミットの親は、git logで確認できます。

# macOS / Linux
git log --oneline --graph --merges -5
# Windows PowerShell
git log --oneline --graph --merges -5

出力例を見てみましょう。

Merge: a1b2c3d e4f5g6h

この場合、a1b2c3dが第1親、e4f5g6hが第2親です。 親の関係をテーブルで整理します。

オプション基準となる親典型的な意味
-m 1第1親(a1b2c3d)マージ先ブランチ(main等)
-m 2第2親(e4f5g6h)マージ元ブランチ(feature等)

-m 1を指定すると、第1親との差分が変更内容として適用されます。 つまり「feature側で加えた変更だけを取り込む」動きです。 実務では-m 1を使うケースがほとんどでしょう。

一方-m 2は、main側の変更を基準にします。 featureブランチへmainの変更を取り込む場面で使いますが、頻度は低いです。

Git公式ドキュメントによると、 マージコミットのcherry-pickでは 指定しなかった親側の個別コミット履歴が統合されます。 履歴が失われるリスクを認識したうえで実行してください。

「no -m option was given」エラーの原因と解決策

マージコミットに対して-mなしでcherry-pickすると、 次のエラーが出ます。

error: commit <hash> is a merge but no -m option was given

解決は、-mオプションを付けて再実行するだけです。

# macOS / Linux — 第1親を基準にcherry-pick
git cherry-pick -m 1 <commit-hash>
# Windows PowerShell — 第1親を基準にcherry-pick
git cherry-pick -m 1 <commit-hash>

チーム開発では-xオプションの併用をおすすめします。 コミットメッセージに元コミットのハッシュが自動追記され、追跡が容易になります (Git公式ドキュメント)。

# macOS / Linux — 追跡情報付きでcherry-pick
git cherry-pick -x -m 1 <commit-hash>
# Windows PowerShell — 追跡情報付きでcherry-pick
git cherry-pick -x -m 1 <commit-hash>

そもそもマージコミットのcherry-pickが頻発する場合は、 ブランチ戦略の見直しを検討してみてください。 個別のコミットを直接cherry-pickするほうが、履歴もシンプルに保てます。

よくあるエラーと具体的トラブルシューティング

cherry-pick中に遭遇するエラーは、原因さえ把握すれば対処は難しくありません。 ここでは現場で頻出する3つのパターンを取り上げます。

「bad object」エラー:原因3パターンと対処法

bad object <コミットハッシュ> と表示される場合、原因は主に3つです。

  1. コミットハッシュの誤記 — タイプミスやコピー漏れを確認してください
  2. リモートのコミットが未フェッチ — 最も多い原因です
  3. リポジトリの破損 — まれですがgit fsckで検証できます

Graphite Guidesによると、 最多の原因はリモート未フェッチです。以下のコマンドで解決します。

# macOS / Linux
git fetch origin
git cherry-pick <コミットハッシ>
# Windows PowerShell
git fetch origin
git cherry-pick <コミットハッシュ>

特定ブランチだけ取得したい場合は git fetch origin <ブランチ名> を使ってください。

「CHERRY_PICK_HEAD exists」エラーと空コミット問題の解決

このエラーは、前回のcherry-pickが中途半端な状態で残っているときに発生します。 Git公式ドキュメントによると、 CHERRY_PICK_HEADは未完了のcherry-pickを指すrefです。

対処法は2つあります。

# macOS / Linux — 前回の操作を取り消して最初からやり直す
git cherry-pick --abort

# または、状態だけクリアして既存の変更は残す
git cherry-pick --quit
# Windows PowerShell
git cherry-pick --abort
# または
git cherry-pick --quit

--abortはpre-cherry-pick状態に完全に戻します。 --quitは適用済みの変更を残したまま、 sequencer(連続操作の管理機構)の状態だけをリセットします。

また、cherry-pick対象の変更がすでに取り込み済みだと「空コミット」になります。 この場合は git cherry-pick --skip でスキップできます。 記録として残したいなら git commit --allow-empty を使ってください。

Git Hooks失敗時の挙動とGUIツール使用時の注意点

huskyなどのGit Hooksがcherry-pick時に失敗すると、 CLIでは明確なエラーが表示されます。 しかし、 GitHub Desktop Issue #16369で 報告されているとおり、一部のGUIツールはエラーを表示せず、 以前のブランチに戻ってしまうことがあります。

この問題を回避するポイントは以下の2つです。

  1. cherry-pickはCLIで実行する — エラーメッセージを確実に確認できます
  2. GUIツール使用時はHooksを一時無効化する--no-verifyオプションの検討も一つの手段です

なお、Gitのコマンド自体はWindows・macOS・Linuxで同一に動作します。 OS間の差異はGUIツールの実装に限定されます。 トラブル時はCLIでの再現確認をおすすめします。

コンフリクトを減らすベストプラクティスと運用ルール

cherry-pick時のコンフリクトは、事前の設計と運用ルールで大幅に減らせます。 ここではコミット設計・便利オプション・使い分けの3つの観点から、 チーム開発で実践できる方法を紹介します。

アトミックコミット設計とrerere活用で解決コストを削減する

コンフリクトの根本原因は、1つのコミットが複数の変更を抱えすぎていることです。 「1コミット=1つの論理的変更」に分割する アトミックコミット設計を徹底してください。 MoldStudの報告では、この手法により コンフリクト解決が約43%削減されたとされています (ただし測定条件は非公開)。

さらに、rerere(reuse recorded resolution)を有効にすると、 過去に解決したコンフリクトのパターンを記録・再利用できます。 設定は以下のとおりです。

# macOS / Linux
git config --global rerere.enabled true
# Windows PowerShell
git config --global rerere.enabled true

rerereを有効にすると、類似のコンフリクト解決時間を最大60%削減できるという 報告もあります。 ただし、誤った解決パターンも記録されるリスクがあります。 初回は --no-rerere-autoupdate で手動確認するのがおすすめです。

# macOS / Linux — 自動適用せず手動確認する場合
git cherry-pick --no-rerere-autoupdate <commit-hash>
# Windows PowerShell
git cherry-pick --no-rerere-autoupdate <commit-hash>

-xオプションによる履歴追跡とチーム共有のルール化

チーム開発では「どのコミットをどこにcherry-pickしたか」の追跡が欠かせません。 -x オプションを付けると、 コミットメッセージに元コミットのハッシュが自動追記されます (Git公式ドキュメント)。

# macOS / Linux
git cherry-pick -x <commit-hash>
# Windows PowerShell
git cherry-pick -x <commit-hash>

実行すると、コミットメッセージ末尾に以下が追記されます。

(cherry picked from commit 3a4b5c6d7e8f...)

筆者の経験では、この1行があるだけで hotfix適用漏れの調査時間が大きく短縮されます。 チームのコミット規約に「cherry-pickには必ず -x を付ける」 と明記しておくのが効果的です。

cherry-pickを避けるべきケース(merge・rebaseとの比較)

cherry-pickは万能ではありません。 以下のケースではmergeやrebaseを優先してください。

状況推奨操作理由
機能ブランチ全体の取り込みgit merge履歴が保存され、コンフリクトも1回で済みます
連続する多数のコミット取り込みgit rebaseコミット順序を維持したまま適用できます
マージコミットの取り込みgit merge推奨cherry-pickでは -m で親指定が必要で、個別コミット履歴が失われます
単発のbugfix/hotfix適用git cherry-pick必要な変更だけを正確に取り込めます

LabExのチュートリアルでも指摘されているように、 複雑なコンフリクトが頻発する場合はmergeを検討すべきです。 cherry-pickの多用はブランチ運用自体に課題がある兆候でもあります。 チームのブランチ戦略を見直すきっかけにしてください。

まとめ:cherry-pickを「最後の手段」として正しく使いこなす

cherry-pickのコンフリクト解決は、4ステップの基本手順 (git status→手動編集→git add--continue)を 身につければ確実に対処できます。 ただし、cherry-pickを頻繁に使う状況が続いているなら、 ブランチ戦略そのものを見直すタイミングかもしれません。 cherry-pickの多用はGit運用ルールに課題がある兆候です。

記事の内容を踏まえて、明日から実践できるアクションを3つ提案します。

  1. rerereを今日有効にするgit config --global rerere.enabled trueを実行するだけで、 同じコンフリクトの再解決から解放されます。 ただし--no-rerere-autoupdateで手動確認を挟む運用から始めると安全です
  2. -xオプションをチームの標準にする — cherry-pick時に元コミットの追跡情報を残すことで、 「このコミットはどこから来たのか」という問い合わせが激減します
  3. アトミックコミットを習慣化する — 1コミット1責務を徹底すると、cherry-pick対象の選定が容易になります。 コンフリクト発生率そのものが下がります

cherry-pickよりも安全な選択肢がないか、常に検討してください。 単一ファイルの特定リビジョン取得なら git checkout <commit> -- <file>が使えます。 ブランチ全体の統合ならgit mergegit rebaseのほうが履歴を健全に保てます。 cherry-pickは「ピンポイントで必要なコミットだけを取り込む」 場面でこそ真価を発揮します。 Git公式ドキュメントAtlassian Git Tutorialを ブックマークしておくと、オプションの詳細を確認したいときにすぐ参照できます。

参考文献

s

この記事を書いた人

数学科出身のWebエンジニア

共有: