patch コマンドを知って、こういう使い方もできそうじゃないか、と思っていくつか試してみたらうまくいったので記事にしました。
この記事は何か
Git を使っていて、
- 変更を stash し忘れたまま別件の改修を加えてしまった
- フォーマッタが改修対象以外にも変更を発生させてしまった
など、ファイルの一部分だけを除いて内容をもとに戻したい、と思ったことはないでしょうか?
この記事では、Git と patch コマンドを使って、指定した変更箇所を除いて、内容を直前の commit に切り戻す方法を紹介します。
前提
前提として、以下のツールが使える必要があります。
- Git
- patch
Linux であればデフォルトで入っていたり、package で簡単に入れられると思います。
Windows の場合は WSL を使うのが早いのではないでしょうか。
どう実現するか
1. 現在の変更差分をファイル出力する
まずは、現在の変更差分を適当なファイルに出力しましょう。
ここでは、diff.patch
というファイルに差分を出力することにします。
git diff > diff.patch
2. 残したい変更差分を抽出する
続いて 1. で出力した diff.patch
を適当なエディタで編集し、欲しい差分だけ残して不要部分を削除していきます。
念のため編集前の diff.patch
をコピーして残しておくことをお勧めします。
cp diff.patch diff.patch.bak
以下のような diff.patch
が得られているとします。
diff --git a/sample.txt b/sample.txt
index 0edb856..6757c5e 100644
--- a/sample.txt
+++ b/sample.txt
@@ -1,5 +1,5 @@
-a
-b
+1: a
+2: b
c
d
e
@@ -8,6 +8,7 @@ g
h
i
j
+---
k
l
m
この差分に対し、---
の追加のみを残して、ほかの差分を削除したい場合は、以下のように diff.patch
を変更します。
diff --git a/sample.txt b/sample.txt
index 0edb856..6757c5e 100644
--- a/sample.txt
+++ b/sample.txt
@@ -8,6 +8,7 @@ g
h
i
j
+---
k
l
m
残念ながら、残したい差分となくしたい差分が、同じhunk (マーカーで示されたブロック) 内に混在する場合は、単純に削除すればよい、というわけにはいきません。
例えば、以下のような diff.patch
に対し、 ---
の追加だけを残したい場合です。
diff --git a/sample.txt b/sample.txt
index 0edb856..4971857 100644
--- a/sample.txt
+++ b/sample.txt
@@ -16,6 +15,7 @@ o
p
q
r
+---
s
t
u
-v
-w
+V
+W
diff.patch
を適切に修正することもできなくはないのですが、多少不要な差分が混じることを許容して差分削除を行い、ファイルを直接編集して差分を修正するのが早いと思います。
3. ファイルを直前の commit の内容に戻す
git checkout
でファイルの内容を直前の commit に戻しましょう。
git checkout .
4. 変更差分を適用する
patch コマンドを使って diff.patch
を適用します。
patch < diff.patch
patch コマンドが失敗した場合は、diff.patch.bak
を適用し、1. の状態に戻しましょう。
git checkout .
patch < diff.patch.bak
5. diff.patch
で取り切れなかった差分を取り除く
必要に応じ、 diff.patch
の編集で取り切れなかった差分を直接ファイルで取り除きましょう。
6. 完成
これで完成です。
念のため git diff
で差分を確認しておくとよいです。
また事故らないうちに変更を commit しておきましょう。。。
応用例
変更差分を 2つに分けて commit を作る
stash し忘れて別な変更を入れてしまった場合などは、以下の手順で修正することが、できます。
1. ちなみに1つ目の commit を作成する
どう実現するか の手順に沿って、1 つ目の commit の内容を作成します。
このとき、2.残したい変更差分を抽出する で diff.patch.bak
を作っておいてください。
2. 現在の commit hash を記録する
target_hash=$(git rev-parse HEAD)
3. 1つ前の commit に戻る
git checkout HEAD^
4. 1 つ目の commit との差分を取る
2. 現在の commit hash を記録する で取得した、 1 つ目の commit との差分をファイルに出力します。
ここではこの差分ファイルを diff2.patch
とします。
git diff ${target_hash} > diff2.patch
5. commit したい branch に checkout する
git checkout .
git checkout <branch_name>
6. 2 つ目の commit を作成する
4. 1 つ目の commit との差分を取る で作成した diff2.patch
を使って、4. 変更差分を適用する 以降を実行する。
よくわかってないこと
patchファイルの編集により変更後の hunk 開始位置が普通にずれるのがだいぶもやもやしてます…
一応期待通り動きますが、どうしてうまくいくのか理解できてません… 教えて詳しい人…
まとめ
Git と patch を駆使して、変更内容の一部だけを切り戻す方法について解説しました。
意図しない変更が大量に混じってしまった場合に使うと便利なので、ぜひお試しあれ!!!