git add と git rebase のちょっと応用的な使い方(add -p, rebase -i)
Git 可愛いよ、Git
という訳で、最近Git の使い方を覚えてきたので、少しまとめておく。
書いたのは、下記の2コマンドのオプションについてです。
- git add -p
- git rebase -i
コミットの選択(git add -p)
普通のコミット
例えば、下記のような(作成中の)ソースがあるとする。
- fizzbuzz.py
#!/usr/bin/env python # -*- coding:utf-8 -*- def fizzbuzz(num): return num if __name__ == '__main__': for n in range(1, 20): print n
これを普通にコミットする場合、add してcommit すればいい。
$ git add fizzbuzz.py $ git commit -m "引数をそのまま返すfizzbuzzを作成"
変更箇所の選択(ハンクの選択)
次に、fizzbuzz の条件分岐を作ったとする。
- fizzbuzz.py
#!/usr/bin/env python # -*- coding:utf-8 -*- def fizzbuzz(num): if num % 3 == 0: return 'Fizz' elif num % 5 == 0: return 'Buzz' return num if __name__ == '__main__': for n in range(1, 20): print fizzbuzz(n)
変更箇所は下記の2点
- 3, 5での除算の条件式を追加
- fizzbuzz を使用しておらず、数字がそのまま出力されていたバグ修正(最後の行)
この例のように、機能追加とバグ修正を一緒にしてしまうケースは結構ある。
しかし、入門Gitにも書かれているように、コミットは分けるべきです。
論理的に関連のない複数の変更は、別々のコミットとして記録するようにするのが、お行儀の良い版管理のコツです。
(入門Git p.57, p.58)
git add には、-p オプションで、どの変更をコミットに含めるかを選択する機能がある。
$ add -p fizzbuzz.py
コマンドを実行すると、下記のように変更箇所が表示され、ハンクを選択できる。
$ git add -p fizzbuzz.py diff --git a/fizzbuzz.py b/fizzbuzz.py index db4a9bf..a66fef3 100644 --- a/fizzbuzz.py +++ b/fizzbuzz.py @@ -2,9 +2,14 @@ # -*- coding:utf-8 -*- def fizzbuzz(num): + if num % 3 == 0: + return 'Fizz' + elif num % 5 == 0: + return 'Buzz' + return num if __name__ == '__main__': for n in range(1, 20): - print n + print fizzbuzz(n) Stage this hunk [y,n,q,a,d,/,s,e,?]?
-p は近い行の変更を自動的に1つのハンクとして検知する。
今回のように近い場所の変更を分けてコミットする場合は、 e (manually edit the current hunk) を選択する。
コメントで指摘を頂きました。
今回のケースだと s (split the current hunk into smaller hunks) でコミットを分ける方が適切です。
Stage this hunk [y,n,q,a,d,/,s,e,?]? s Split into 2 hunks. @@ -2,8 +2,13 @@ # -*- coding:utf-8 -*- def fizzbuzz(num): + if num % 3 == 0: + return 'Fizz' + elif num % 5 == 0: + return 'Buzz' + return num if __name__ == '__main__': for n in range(1, 20): Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y @@ -5,6 +10,6 @@ return num if __name__ == '__main__': for n in range(1, 20): - print n + print fizzbuzz(n) Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n
コミットに含めるハンクで "y" , 含めないハンクでは "n" を選択します。
これで、条件分岐の部分だけをコミットできます。
$ git commit -m "3, 5での除算の条件式を追加"
もし手動でハンクを選択する場合
e (manually edit the current hunk) を選択します。
Stage this hunk [y,n,q,a,d,/,s,e,?]? e
するとエディタが起動するので、ステージングしないハンクを削除し、保存する。
- 編集前
# Manual hunk edit mode -- see bottom for a quick guide @@ -2,9 +2,14 @@ # -*- coding:utf-8 -*- def fizzbuzz(num): + if num % 3 == 0: + return 'Fizz' + elif num % 5 == 0: + return 'Buzz' + return num if __name__ == '__main__': for n in range(1, 20): - print n + print fizzbuzz(n) # --- # 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.
- 編集後
# Manual hunk edit mode -- see bottom for a quick guide @@ -2,9 +2,14 @@ # -*- coding:utf-8 -*- def fizzbuzz(num): + if num % 3 == 0: + return 'Fizz' + elif num % 5 == 0: + return 'Buzz' + return num # --- # 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.
これで、上記の -s と同等のコミットになります。
歴史の書き換え(git rebase -i)
現在のコミット系譜は下記のようになっているとする。
A---B---C master
この歴史を下記のように変えてみます。
--C---D---E master / A---B---C (old)master
C とB の順番を変えて、B のコミットを2つ (D, E) に分けている。
つまり、"先にバグ修正をして、条件分岐を1つずつ実装していた"ことにする。
順番変え + コミット編集
歴史を変える場合、git rebase -i を使用する。
今回の場合、歴史はA から分岐するため、A のコミットを引数に指定する。
$ git rebase -i HEAD^^
すると、下記の状態でエディタが起動する。
- 編集前
pick e28c55a 3, 5での除算の条件式を追加 pick 78ac991 fizzbuzzを使用していないバグ修正 # Rebase dadcfb3..78ac991 onto dadcfb3 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. #
1行目と2行目を入れ替え、編集するコミットを"edit"に書き換え、保存する。
- 編集後
pick 78ac991 fizzbuzzを使用していないバグ修正 edit e28c55a 3, 5での除算の条件式を追加 # Rebase dadcfb3..78ac991 onto dadcfb3 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. #
コミットの編集
git rebase を実行すると、B のコミットの状態に作業コピーが変わる。
まだコミットを2つに分けていないため、rebase 中はこういうイメージ
↓ココ --C---B master / A---B---C (old)master
ここでBの変更を残したまま、Cに戻す。
$ git reset HEAD^
↓ココ(作業コピーはBと同じ) --C master / A---B---C (old)master
後は、add -p を使ってコミット箇所を選択し、2回コミットする。
--C---D---E master / A---B---C (old)master
Bのコミット編集が全て終わったら、rebase を再開する。
$ git rebase --continue
これで、歴史の書き換え完了です。
もしrebase 中にコンフリクトしたら・・・
焦らずに、まずはrebase 実行前に戻す。
$ git rebase --abort
実行前に戻せたら、コンフリクト解消用のブランチ作ったり、cherry-pickで少しずつマージして対応する。
- 作者: 濱野純(Junio C Hamano)
- 出版社/メーカー: 秀和システム
- 発売日: 2009/09/24
- メディア: 単行本
- 購入: 31人 クリック: 736回
- この商品を含むブログ (155件) を見る