gitで部分的な変更だけを安全に移す:複雑なブランチを“パス限定cherry-pick”で救う方法

こんにちは、さるまりんです。

ブランチが長く育ってくると、「この修正だけ main に持っていきたい」という場面が必ず訪れます。 ですが、普通に git cherry-pick すると、関係ないファイルまで一緒に取り込まれてコンフリクト祭りに……。 今回は、特定ファイルだけを安全に適用する“パス限定 cherry-pick” という方法です。 実際に自分の環境でやってみて、「スクリプト実行 → コンフリクト → 手動解決 → 最終確認」までたどり着いた記録をメモとして残します。

🔧 通常の cherry-pick ではうまくいかない

git cherry-pick は便利ですが、ファイル単位で指定できません。 複雑なブランチでは:

  • 意図しない修正まで混ざる
  • コンフリクトが増える
  • 差分を追うのが難しい

ということが起こります。 これを避けるために、git diffgit apply を組み合わせて「必要なファイルだけ」移植しました。

🚀 パス限定 cherry-pick の考え方

コミットごとの差分を抽出し、その中の「特定ファイル」だけを安全に git apply --3way で当てていきます。

git diff --binary <commit>^! -- path/to/file.js | git apply --3way --index

ポイントはgitコマンドのオプションです。それぞれ

  • --binary:画像や非テキストファイルも安全に扱える
  • --3way:コンフリクト検出あり
  • --index:ステージに直接反映

です。

手順①:対象コミットの取得

対象コミットを並べるため、まず変数にまとめます。

COMMITS="a1b2c3d e4f5g6h i7j8k9l"
ORDERED=$(echo "$COMMITS" | xargs git rev-list --reverse --topo-order)

ところが…ここで macOS の Terminal に直接貼り付けたらエラー! 改行やクォートの扱いがうまくいかず、スクリプトが途中で止まってしまいました。

手順②:/tmp にスクリプトを保存して実行

ターミナル直貼りではうまくいかないので、次のように `/tmp/port_patches.sh` を作って実行する形にしました。

#!/bin/bash
set -e

ORDERED=(a1b2c3d e4f5g6h i7j8k9l)

for c in "${ORDERED[@]}"; do
  echo "=== apply $c ==="
  git diff --binary "$c^!" -- path/to/file1.js path/to/file2.js \
    | git apply --3way --index || {
      echo "⚠️ コンフリクト発生: $c"
      exit 1
    }
done
# 実行
bash /tmp/port_patches.sh

set -e にしておくと、途中でエラーが出た時点で安全に停止できます。

手順③:マージコミットに遭遇

途中でマージコミットに当たり、`git diff` がエラーを出しました。

error: No valid patches in input (allow with "--allow-empty")

この場合、マージの親が2つあるので、どちらの差分を取るかを手動で判断します。

git diff <commit>^1 <commit> -- path/to/file.js
git diff <commit>^2 <commit> -- path/to/file.js

今回は 親2側(^2) の変更を採用。 以降は自動スクリプトを使わず、同じ形式で 手動実行 することに切り替えました。

手順④:コンフリクトの解消

<<<<<<< ours
 methodA(); 
=======
 this.methodA(); 
>>>>>>> theirs

残したい方(今回は theirs)を選んで編集後、git add

手順⑤:コミット前の最終確認

git --no-pager diff --stat --cached

--no-pager を付けると出力が less に流れず、即時確認できて便利です。 差分内容まで見たい場合は:

git diff --cached

これで変更範囲をすべて確認してから、まとめてコミットします。

git commit -m "Port selected fixes using path-limited cherry-pick"

🧩 結果と学び

最終的に目的のファイルだけを安全に main に反映できました。 途中のスクリプト化やマージ対応で得たポイントは以下の通りです。

課題 解決策
xargs展開エラー 配列として ORDERED を定義
macターミナルで途中停止 /tmp にスクリプト保存して実行
マージコミットでエラー 親2(^2)側の差分を手動適用
コミット前の確認 git --no-pager diff --stat --cached

⚙️ まとめ

この“パス限定 cherry-pick”手法は、「特定のファイルだけを取り込みたい」という状況で使えます。 コンフリクトの発生箇所も明確にしつつ、差分の追跡もしやすいです。自動でも、手動かは臨

今回のようにスクリプトを /tmp/ に置いて何度も再実行できる形にしておくと、安心して試行錯誤ができます。 ブランチが複雑に絡み合っても、1ファイルずつ丁寧に適用していくアプローチで、安全かつ確実に作業できます。バージョン管理ってミスすると怖いイメージがずっとありますが、何をやっているかわかってると自信を持ってcommitやpushができると思います。 大きなマージや古いブランチの整理に悩んだときは、この“パス限定 cherry-pick”をよかったら試してみてください。

読んでくださってありがとうございました。 それではまた!