Git で変更されたファイルを部分的にステージする方法


git add -p コマンドを使って, 変更されたファイルを部分的にステージする方法を紹介します. 一度にファイルを色々編集してしまって, それらの編集部分を分割してコミットしたいという時に便利です.

この記事のサンプルコードは次の環境での動作を確認しております:

  • Git – 2.19.1

準備

Ruby のスクリプトファイルを例に git add -p を使って, そのファイルの変更を部分的にステージする方法を紹介させていただきます.

まず example.rb を次のように編集して middle メソッドを定義します:

def middle
  'This is the middle method.'
end

そして最初のコミットを行います:

git commit -am 'Add `middle` method'

そして 2 つのメソッド topbottom を新たに加えます:

def top
  'This is the top method.'
end

def middle
  'This is the middle method.'
end

def bottom
  'This is the bottom method.'
end

既にコミットされた middle メソッドを挟んで, topbottom メソッドを加えることによって, これから git add -p を使う準備が整いました.

git add -p の使い方

準備が整ったので git add -p を使って, その 2 つのメソッド topbottom どちらか一方のメソッドのみステージしてみたいと思います.

次のコマンドを入力すると, ハンクと呼ばれる変更部分をステージするかどうか聞かれます:

$ git add -p example.rb
diff --git a/example.rb b/example.rb
index ca0ed8e..64fb189 100644
--- a/example.rb
+++ b/example.rb
@@ -1,3 +1,11 @@
+def top
+  'This is the top method.'
+end
+
 def middle
   'This is the middle method.'
 end
+
+def bottom
+  'This is the bottom method.'
+end
Stage this hunk [y,n,q,a,d,s,e,?]?

y, n, q, a, d, s, e, ? の中から一つオプションを選択するよう聞かれます.

? を選択するとそれぞれのオプションのヘルプが表示されます:

Stage this hunk [y,n,q,a,d,s,e,?]? ?
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help

y を選択すると, ただの git add example.txt と変わらなくなりますので, この場合 se を選択します.

それぞれを選択した場合の方法を紹介します.

s を選択した場合

s を選択すると, ハンクが topbottom メソッドの部分の 2 つに分割されるので, それぞれを別々に, ステージさせるかどうかを選択することができるようになります. このように分割できるのもあらかじめコミットしておいた middle メソッドが topbottom メソッドを隔てているためです:

Stage this hunk [y,n,q,a,d,s,e,?]? s
Split into 2 hunks.
@@ -1,3 +1,7 @@
+def top
+  'This is the top method.'
+end
+
 def middle
   'This is the middle method.'
 end
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?

top メソッドのみステージしようと思いますので, y を選択します:

Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -1,3 +5,7 @@
 def middle
   'This is the middle method.'
 end
+
+def bottom
+  'This is the bottom method.'
+end
Stage this hunk [y,n,q,a,d,K,g,/,e,?]?

bottom メソッドはステージしようとは思いませんので, n を選択します:

Stage this hunk [y,n,q,a,d,K,g,/,e,?]? n

これで top メソッドのみステージすることができました.

次のコマンドを入力すると, インデックスとワーキングディレクトリの両方共に変更されていることを確認できます:

$ git status -s
MM example.rb

次のコマンドでステージされている変更部分を表示してみると, top メソッドのみステージされていることを確認できます:

$ git diff --staged
diff --git a/example.rb b/example.rb
index ca0ed8e..74abf73 100644
--- a/example.rb
+++ b/example.rb
@@ -1,3 +1,7 @@
+def top
+  'This is the top method.'
+end
+
 def middle
   'This is the middle method.'
 end

そしてそのステージされた top メソッドをコミットします:

git commit -m 'Add `top` method'

そして残りの bottom メソッドもステージし, コミットします:

git commit -am 'Add `bottom` method'

次のコマンドで今までのコミットを確認してみます:

commit 39d32f31916e87a1430b1eb956814224e61ebcb1
Author: Yu Hamada <yu@yu8mada.com>
Date:   Thu Nov 1 17:29:07 2018 +0900

    Add `bottom` method

diff --git a/example.rb b/example.rb
index 74abf73..64fb189 100644
--- a/example.rb
+++ b/example.rb
@@ -5,3 +5,7 @@ end
 def middle
   'This is the middle method.'
 end
+
+def bottom
+  'This is the bottom method.'
+end

commit 3540a24ec0e3583729f75cd2f073adc926553667
Author: Yu Hamada <yu@yu8mada.com>
Date:   Thu Nov 1 17:27:15 2018 +0900

    Add `top` method

diff --git a/example.rb b/example.rb
index ca0ed8e..74abf73 100644
--- a/example.rb
+++ b/example.rb
@@ -1,3 +1,7 @@
+def top
+  'This is the top method.'
+end
+
 def middle
   'This is the middle method.'
 end

commit 95fde09c63644c14b250d6f5687015ed138e5f27
Author: Yu Hamada <yu@yu8mada.com>
Date:   Thu Nov 1 16:58:03 2018 +0900

    Add `middle` method

diff --git a/example.rb b/example.rb
new file mode 100644
index 0000000..ca0ed8e
--- /dev/null
+++ b/example.rb
@@ -0,0 +1,3 @@
+def middle
+  'This is the middle method.'
+end

e を選択した場合

今度は e を選択した場合の部分的なステージの方法を紹介します.

e を選択すると:

Stage this hunk [y,n,q,a,d,s,e,?]? e

次のファイルが表示されます:

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,3 +1,11 @@
+def top
+  'This is the top method.'
+end
+
 def middle
   'This is the middle method.'
 end
+
+def bottom
+  'This is the bottom method.'
+end
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# 
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

先頭の + はステージされるということを示しているので, 今度は bottom メソッドのみステージしようと思いますので, top メソッドの行のみ削除します:

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,3 +1,11 @@
 def middle
   'This is the middle method.'
 end
+
+def bottom
+  'This is the bottom method.'
+end
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# 
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

自分の場合 Vim なので, :wq で保存 & 終了します.

同じようにインデックスとワーキングディレクトリの両方が変更されていることを確認できます:

$ git status -s
MM example.rb

ステージされた箇所を表示してみると, 確かに bottom メソッドのみステージされたことを確認できます.

$ git diff --staged
diff --git a/example.rb b/example.rb
index ca0ed8e..fbc679a 100644
--- a/example.rb
+++ b/example.rb
@@ -1,3 +1,7 @@
 def middle
   'This is the middle method.'
 end
+
+def bottom
+  'This is the bottom method.'
+end

そしてその bottom メソッドをコミットします:

git commit -m 'Add `bottom` method'

残りの top メソッドもステージし, コミットします:

git commit -am 'Add `top` method'

コミット履歴を確認してみます:

$ git log -p
commit 0c0e54264129b1d4e1223d1913099362c9ab42b8
Author: Yu Hamada <yu@yu8mada.com>
Date:   Thu Nov 1 17:35:13 2018 +0900

    Add `top` method

diff --git a/example.rb b/example.rb
index fbc679a..64fb189 100644
--- a/example.rb
+++ b/example.rb
@@ -1,3 +1,7 @@
+def top
+  'This is the top method.'
+end
+
 def middle
   'This is the middle method.'
 end

commit a71cc5663509baf906bcff6a574ed435c1aa7f10
Author: Yu Hamada <yu@yu8mada.com>
Date:   Thu Nov 1 17:35:02 2018 +0900

    Add `bottom` method

diff --git a/example.rb b/example.rb
index ca0ed8e..fbc679a 100644
--- a/example.rb
+++ b/example.rb
@@ -1,3 +1,7 @@
 def middle
   'This is the middle method.'
 end
+
+def bottom
+  'This is the bottom method.'
+end

commit 95fde09c63644c14b250d6f5687015ed138e5f27
Author: Yu Hamada <yu@yu8mada.com>
Date:   Thu Nov 1 16:58:03 2018 +0900

    Add `middle` method

diff --git a/example.rb b/example.rb
new file mode 100644
index 0000000..ca0ed8e
--- /dev/null
+++ b/example.rb
@@ -0,0 +1,3 @@
+def middle
+  'This is the middle method.'
+end

まとめ

git add -p を使ってワーキングディレクトリの変更箇所を部分的にステージする方法を紹介させていただきました.

そのコマンドを入力した時にわかりますように, y, n, se 以外にも選択できるオプションがありますので, 今回はあくまで基本的なオプションの使い方のみ紹介させていただいたということになります. ただそれらのオプションは基本的なものですが, エッセンシャルなものでもありますので, それらのオプションの使い方さえ知っていれば “部分的にステージする” ことができると思います.

ただ e オプションのもう一つの使い方の削除した箇所を元に戻してステージする方法には今回触れませんでしたが, その場合削除した部分の先頭に - が表示されるので, - をスペース (U+0020) に変えてあげると, コンテキストとして認識されるので, ワーキングディレクトリでは削除されているけど, インデックスでは削除されていないという状態を作ることができます.

あるファイルを 1 つのコミットに収まらない範囲で編集してしまって, 1 つのコミットに収まるように部分的にステージしたいという時や, 削除してしまったけど, インデックスはそのままにして, 次のコミットでは残しておきたいという時に git add -p コマンドは役立ちます.

参考資料