いものやま。

雑多な知識の寄せ集め

Gitによる歴史改変。(その2)

昨日は、リベースの基本的な使い方まで。

今日は、さらに強力なコミットの細かい指定と、リベース中のコントロールについて。

コミットの細かい指定

リベースの基本的な考え方として、リベースとはパッチを順番に当てていく操作だということを書いたけど、なんと、そのパッチの当て方を細かくコントロールすることも出来る。
これによって、各コミットの内容自体を改変することも出来てしまう。

実行方法

コミットの細かい指定を行うには、--interactiveオプション(あるいは省略形の-i)を使う。

$ git rebase -i (newbaseの指定など)

このコマンドを実行するとエディタが立ち上がり、例えば次のような画面が表示される。

pick bbbbbbb 機能Aを実装。
pick ccccccc 機能Bを実装。
pick ddddddd バグを修正。
pick eeeeeee 機能Cを実装。
pick fffffff 開発ブランチをマージ。

# Rebase aaaaaaa..fffffff onto 1111111 (       5 TODO item(s))
#
# 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
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

ここには、これからリベースで当てていく予定のパッチのリストと、このリストの編集方法に関するコメントが書かれている。
このリストを編集することで、コミットの細かい指定を行っていくことが出来る。

リストの編集

リストの各行には、そのコミットをどう扱うのかを表したコマンドと、コミットのハッシュ値(の先頭部分)、それにコミットメッセージが書かれている。
このコマンドをデフォルトのpickから変えることで、そのコミットの扱いが変わってくる。

  • pick
    そのコミットをそのまま使う。(デフォルト)
  • reword
    そのコミットを使うが、コミットメッセージの修正を行う。
  • edit
    そのコミットを使うが、コミットの修正を行うためにリベースを一時停止する。
  • squash
    そのコミットでの変更内容を直前のコミットに入れるようにし、コミットを1つにする。
    (コミットメッセージの変更も行う)
  • fixup
    そのコミットでの変更内容を直前のコミットに入れるようにし、コミットを1つにする。
    (コミットメッセージの変更は行わず、そのコミットのコミットメッセージは捨てられる)
  • exec
    execに続く内容をシェルで実行する。
    (コミットのリストの間に追加する)

なお、このリストの順番を入れ替えると、パッチを当てる順番が変更される。
さらに、リストから一行削除すると、そのコミットは使われなくなる。

コミット内容の修正

過去のコミットに遡ってそのコミットの変更内容を修正したいという場合がある。
その場合、上記のeditコマンドを使う。

editを指定すると、まずそのコミットが適用され(ここ重要)、コミットが終わった状態でリベースが一時停止される。
その状態で追加の変更などを行い、git commit --amendを行うことで、そのコミットの内容を修正することが出来る。

なお、git commit --amendでなく単にgit commitを行うと、新たなコミットが追加されるので、注意。
(逆に言えば、あるコミットとあるコミットの間に新しいコミットを挟みたい場合には、この方法をとることになる)

コミット内容が修正し終わったら、git rebase --continueとすることで、リベースが再開される。

コミットの分割

過去のコミットを2つ以上のコミットに分割したいというときが、たまにある。
例えば、機能Bを実装したときに機能Aのバグに気がついて一緒に直したけれど、変更理由が明確になるように、この変更だけを切り出して一つのコミットにしたい場合とか。

そういった場合、まずはコミットのリストを編集するときに、分割したいコミットに対してeditを指定する。
そして、分割したい変更内容がコミットされ、リベースが一時停止している状態で、次のようにする。

$ git reset HEAD^

こうすると、直前にされたコミットが取り消され、その変更内容だけがワーキングツリーに残っている状態になる。
そこで、適切にgit addgit commitをしてやることで、コミットを分割することが出来る。

リベース中のコントロール

リベースを行っていると、変更の衝突(コンフリクト)が起きたり、あるいはeditコマンドを使ったことにより、リベースが一時停止されることがある。
その場合、変更の衝突を解決したり、コミット内容の修正が終わったら、リベースを再開させないといけない。

リベースを再開させるには、次のコマンドを実行する。

$ git rebase --continue

こうすると、リベースが再開され、次のパッチを当てる操作が行われる。

あるいは、場合によっては、やっぱり途中でリベースを止めたいとなることもある。
その場合、次のコマンドを実行する。

$ git rebase --abort

こうすると、途中まで進んでいたリベースも全て巻き戻されて、リベースを行う直前の状態に戻る。

なお、リベースが終わってから、やっぱりリベースしない方がよかった、となることもある。
その場合には、次のコマンドを使う。

$ git reset --hard ORIG_HEAD

これで何もかもが元通り!

今日はここまで!