2017年07月14日

git コミット履歴を変更する

コミットした後で、間違ってコミットした!とか、コミットメッセージが適切ではなかったとか、さっきのコミットと今の変更は合わせてコミットしたほうがよかったとか。
そんな場合に、コミットした内容を訂正する方法。

直前のコミットの修正


直前のコミット限定の操作。
"git commit --amend" を使う。

コミットメッセージの修正


コミット後に、コミットメッセージが不足していたとか、レビューで「コミットメッセージが対応内容と一致していない」と指摘されたときとかに使う。
$ git log --oneline
02c1321 commit 内容
9fe9714 master 追加 2
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットID:02c1321 のコミットメッセージを修正する。

$ git commit --amend


エディタが立ち上がるので修正する。


$ git log --oneline
ecb63b0 master 追加 3 (コミットメッセージを修正)
9fe9714 master 追加 2
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットメッセージが修正できた(コミットIDも変更される)


コミットの修正


コミットした後に、1ファイルをステージするの忘れていたとか、"stash" から戻すのを忘れて別の修正をコミットしたとか、そんなときには必要なファイルを "git add" してステージしてから実行する。
$ git log --oneline
a383399 master 追加 3 (間違ったコミット)
9fe9714 master 追加 2
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

$ git add index.html
$ git commit --amend

$ git log --oneline
2e3f7ae master 追加 3 (コミット内容を修正)
9fe9714 master 追加 2
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html




これで、コミット後に、あっ!しまった!!! と思うことがあっても落ち着いて作業できる。ただし、この操作はコミットしたという履歴を無かったことにはできない。あくまでコミットした履歴内容を訂正するだけ。






過去のコミットの修正


"git commit --amend" は直前のコミットに対してのみ。2つ前、3つ前のコミットを修正したい場合はどうするのか?
その場合は、ブランチの付け替えとして使う "rebase" 操作を使う。
"rebase" を使う場面は付け替え操作よりも、コミット内容の修正が多いと思う。


"-i" or "--interactive" オプションを使うと、「interactiveモード」で "rebase" 操作が使える。
具体的な操作は後から指定するから、とりあず指定したコミット履歴を列挙して!という操作。

git_rebase10.png
$ git log --oneline
762bccf master 追加 3
9fe9714 master 追加 2
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html


$ git rebase -i <コミットID>


とすると、指定したコミットIDの次のコミット以降をコミット履歴の修正対象とする。

コミット(C):1c0a080 のコミット履歴を修正したい場合、1つ前の コミット(B):07c5066 を指定する。

$ git rebase -i 07c5066

pick 1c0a080 master 追加1
pick 9fe9714 master 追加 2
pick 762bccf master 追加 3

# Rebase 07c5066..762bccf onto 07c5066 (3 command(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
# d, drop = remove commit
#
# 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 <コミットID> <コミットメッセージ>" と記載されている箇所に注目する。

これを以下の内容で操作することで、コミット履歴を修正できる。
Commands処理
「p」or「pick」このコミットを適用する
「r」or「reword」このコミットを適用するが、コミットメッセージは編集する
「e」or「edit」このコミットを適用するが、ファイルを修正する
「s」or「squash」このコミットを適用するが、1つ前のコミットと合体させる
「f」or「fixup」「squash」と同じだが、このコミットメッセージは破棄する
「x」or「exec」以降のコマンドをshellで実行する
「d」or「drop」このコミットを削除する



対象コミットの指定は、"HEAD" を使い、「"HEAD" から何個目までの履歴を対象とする」と指定することもできる。

$ git rebase -i HEAD~3








コミットメッセージを修正


「reword」を指定。

reword 1c0a080 master 追加1
pick 9fe9714 master 追加 2
pick 762bccf master 追加 3

として、エディタを終了すると、
続けて、コミットメッセージを修正するエディタが立ち上がるのでメッセージ内容を変更し終了する。
$ git log --oneline
0e83bbd master 追加 3
cd5173c master 追加 2
d41ea49 master 追加 1(コミットメッセージを修正)
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットメッセージが変更された(変更したコミット以降もコミットIDが変更される)。
git_rebase11.png





コミットをまとめる1


1つ前のコミットとまとめる。
"merge" 操作時にコミットをまとめる操作があるが、"rebase" でまとめてからマージするほうが、使用頻度は多いと思う。

pick 1c0a080 master 追加1
squash 9fe9714 master 追加 2
pick 762bccf master 追加 3

# This is a combination of 2 commits.
# The first commit's message is:

master 追加1

# This is the 2nd commit message:

master 追加 2

コミットメッセージを入力するエディタが立ち上がるのでコミットメッセージを入力し終了。
$ git log --oneline
64a5d77 master 追加 3
27f2fe8 master 追加1 & master 追加 2 (コミットを圧縮)
07c5066 master branch :変更 1
830f4ca Add new file :index.html


コミットが圧縮された(変更したコミット以降もコミットIDが変更される)。
git_rebase12.png



コミットをまとめる2


"squash" と "fixup" の違いは、コミットメッセージが編集できるかどうか。

pick 1c0a080 master 追加1
fixup 9fe9714 master 追加 2
pick 762bccf master 追加 3


終了すると、メッセージを編集するエディタは起動せず完了した。
$ git log --oneline
eb3834a master 追加 3
eb91f22 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

"fixup" を指定したコミットはまとめられたが、コミットメッセージは削除された(変更したコミット以降もコミットIDが変更される)。
git_rebase13.png




コミットの順番を入れ替える


作業の区切りの良いところでコミットしていたが、後で履歴を見たときに作業の意図がわかるように整然とコミット順番を並べたいと思ったときにはコミット順番を入れ替える。

コミットを示している行を、意図したコミット順序に並び替え、エディタを終了させる。

pick 1c0a080 master 追加1
pick 762bccf master 追加 3 <<入れ替えた↓
pick 9fe9714 master 追加 2 <<入れ替えた↑


ここで注意したいことは、コミット順番が入れ替わるということは、変更差分の順番も変わるということ。
git が上手いことやってくれる訳ではないので、コンフリクトが発生することを前提として、自分の手できちんと組み替え直さないとダメ。
特に、この作業をやっている時点では、きっと自分自身では変更差分の最終形がわかっているので、この操作でのコンフリクトを理解せずに、最初のコンフリクトで思わず最終形で修正し、以降のコンフリクトでも同じ状態で保存してしまうと、本来発生していたはずの差分が発生せず、結果的にコミットが圧縮(squash)された状態になってしまう。

コミット順番の入れ替えは、過去のファイル差分の入れ替えにあたるので、確実に差分の登録順番を意識して修正すること。
$ git log --oneline
1d79372 master 追加 2'
7335bd0 master 追加 3'
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットの順番が変更された(変更したコミット以降もコミットIDが変更される)。
git_rebase14.png




コミットを分割する


コミットしたが、内容的に2つの目的を含んだコミットだったので、別々にコミットしたほうが良かったと思った場合にコミットを分割する。

コミット(D):9fe9714 の内容を分割したいとすると、

pick 1c0a080 master 追加1
edit 9fe9714 master 追加 2
pick 762bccf master 追加 3


終了させると、"rebase" 処理が指定した箇所で止まり、shell に制御が戻される。
Stopped at 9fe9714240de35b1ed77601d14bd965e647f226c... master 追加 2
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

ここで変更内容を確認してみると、コミット(D):9fe9714 までコミットされた状態で止まっている。

既にコミット(D):9fe9714 が含まれているので、このコミット分割するためには、直前のコミットを修正する
対象ファイルを変更し、ステージしたら、直前のコミットを修正する。

$ git add index.html
$ git commit --amend -m "master 追加 2-1"


分割したかったので、残りの変更も追加する。

$ git add index.html
$ git commit -m "master 追加 2-2"


コミットが修正できたら、"rebase" を継続する。

$ git rebase --continue

$ git log --oneline
48f861d master 追加 3
c9d0e42 master 追加 2-2
a85263e master 追加 2-2
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットが分割された(変更したコミット以降もコミットIDが変更される)。
git_rebase15.png




コミットを挿入する


"edit" は分割だけではない。"rebase" 処理が指定場所で中断するので、その間にコミットを行えば、コミットを挿入することもできる。

pick 1c0a080 master 追加1
edit 9fe9714 master 追加 2
pick 762bccf master 追加 3

Stopped at 9fe9714240de35b1ed77601d14bd965e647f226c... master 追加 2
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

$ git add index.html
$ git commit -m "master 挿入"
$ git rebase --continue

$ git log --oneline
ded34b1 master 追加 3
ecba81d master 挿入
9fe9714 master 追加 2
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットが挿入された(変更したコミット以降もコミットIDが変更される)。
git_rebase16.png




コミットを削除する1


コミットを削除する。
「コミットを削除」と言っても、変更差分も無かったことにはしてくれないので、コンフリクトが起こることを前提とし、コンフリクトした場合はしっかりと内容確認して削除しないといけない。

pick 1c0a080 master 追加1
drop 9fe9714 master 追加 2
pick 762bccf master 追加 3

$ git log --oneline
f82c062 master 追加 3
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットが削除された(変更したコミット以降もコミットIDが変更される)。
git_rebase17.png




コミットを削除する2


"drop" を指定せずとも、並んでいるコミットの行から不要なものを削除しても同様。

pick 1c0a080 master 追加1
<<< 行を削除 >>>
pick 762bccf master 追加 3

$ git log --oneline
29ff05c master 追加 3
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミットが削除された(変更したコミット以降もコミットIDが変更される)。
git_rebase18.png





shell を使う


"exec" は趣旨が異なる。コミットに対する操作ではない。
"rebase" の途中に指定した処理を挿入することができる。

全く意味が無いが、例として、"ls -a" コマンドを実行してみる。

pick 1c0a080 master 追加1
pick 9fe9714 master 追加 2
exec ls -a
pick 762bccf master 追加 3

Executing: ls -a
. .. .git index.html
Successfully rebased and updated refs/heads/master.

"ls -a" が途中で実行されたのがわかる。


コミット履歴の修正で使う場合、次のようなやり方がある。
今の作業ツリー上の変更は、コミット(D):9fe9714 と合わせてコミットしたほうが良かったと思った場合に、"stash" を使って設定してみる。
今の変更分を一旦 "stash" しておく。

$ git stash
$ git rebase -i HEAD~3



"exec" を使い、git 操作を記述する。

pick 1c0a080 master 追加1
pick 9fe9714 master 追加 2
exec git stash pop; git commit -a --amend
pick 762bccf master 追加 3

$ git log --oneline
ba09ebd master 追加 3
9706693 master 追加 2(追加修正あり)
1c0a080 master 追加1
07c5066 master branch :変更 1
830f4ca Add new file :index.html

コミット(D):9fe9714 を "rebase" したところで、shell の実行に移る。
"git stash pop" を実行し、変更したファイルを作業ツリーに戻し、続けて、
"git commit -a --amend" で直前のコミットを、現在の作業ツリーのファイルで修正コミットする(変更したコミット以降もコミットIDが変更される)。
git_rebase19.png






まとめ


"git commit --amend" はまだしも、
"rebase" は過去の歴史を変えるという非常に強力な操作のため、普段は "reword" "squash" ぐらいの内容に留めておくのが安全で良い。

コミット履歴の変更を行えば、ほとんど場合でコンフリクトが発生すると考えておき、コンフリクトの修正もコミットという履歴を理解したうえで、慎重に対応すべき。
後から修正できるからと、とりあえずコミットしておけばよいとか思わないように。
特に、他メンバーに展開したブランチに対する、コミット履歴の修正は混乱を招くだけなのでダメ。

コミット履歴の修正は、しまった!と思うような場合に、「非効率なリカバリではなく、効率よくリカバリできる手段としてのみ使用できるもの」ぐらいの意識でいたほうが良い。




gitをインストール
gitサーバーのセットアップ
git ブランチについて
git 変更を一時的に退避 stash
git ブランチを合流するマージ
git ブランチを付け替える
git コミットを取り消す
git 変更をリセットする
git リモートでの操作
git ファイルの追跡
git リリース準備
git リモートブランチを追加
git チェックアウトをもっと便利に使う
git プロジェクトの構成
git 変更をpatchファイルにする
git コンフリクトに対処する
git 失敗したときの復元
posted by Zorinos at 20:00| Comment(0) | Linux | 更新情報をチェックする
この記事へのコメント
コメントを書く
コチラをクリックしてください