はじめに

筆者は恥ずかしながらgitコマンドでは大抵commitpushしか使わず、訂正したいときでも commit --amendするくらいです。
なので、ちょっと凝った(?)操作をするときに調べては忘れを繰り返していて、いい加減どうかと思うので初歩的な内容ばかりだと思いますがメモを残しておきます。(𝑛番煎じ ( 𝑛∈ℕ))

主にコミット履歴の改変についてのメモですが、それ以外のトピックも記載するつもりです。
新しく出会ったこと思い出したことあれば更新します。

空コミットする

まれにファイルに変更は加えたくないけれどコミットはしたいよという場合があります。
例えば、コミットを契機に何かの処理が動くのだけれどそれを動かすためだけに、アプリケーションの挙動を変えないような変更(適当な箇所にスペースや空行を入れるなど)を行ってコミットしたくない場合などでしょうか。
あるいはイニシャルコミットを空コミットにするなど。

こういった場合は--allow-empty オプションを利用します。

$ git commit --allow-empty -m 'initial commit'

2つ以上前のコミットに差分を混ぜる

追加漏れファイルなどがあったりして、直前のコミットに差分を混ぜたいなというときにはcommit --amendを使えばいいのですが、別のコミットを行った後で追加漏れに気づいた場合対応に困ります。
「追加漏れ」みたいなコミットメッセージでコミットしてもうるさく言われない環境であればそれでもいいのですが、ちょっと格好悪い気もします。

こういった場合rease -iが使えるようです。

例として、下記のようなコミットの状況があったとして直近のコミット(7344f3fc)を「2番目のコミット」(94f9ffe8)に混ぜたい場合を考えてみます。

$ git log --oneline
7344f3fc 5番目のコミット
9fd223f8 4番目のコミット
ced7e037 3番目のコミット
94f9ffe8 2番目のコミット
6ae9cb5d 1番目のコミット

2番目のコミットは4つ前のコミットになるのでHEAD~4を指定してrebase -iを実行します。

$ git rebase -i HEAD~4

下記のような内容でエディタが開きます。

pick 94f9ffe8 2番目のコミット
pick ced7e037 3番目のコミット
pick 9fd223f8 4番目のコミット
pick 7344f3fc 5番目のコミット

5番目のコミットを2番目に混ぜたいので2番目のコミットの下に5番目のコミットの行を持ってきてpickとなっている箇所をfにして保存します。

pick 94f9ffe8 2番目のコミット
f 7344f3fc
pick ced7e037 3番目のコミット
pick 9fd223f8 4番目のコミット

これで、「5番目のコミット」が「2番目のコミット」に取り込まれました。
「1番目のコミット」のコミットハッシュは変わっていませんがそれより後のコミットのハッシュが変わっていることに注意してください。
HEAD~4..HEAD に含まれるすべてのコミットが(実際に変更したか否かに関わらず)書き換えられます。

$ git log --oneline
0a8cabf0 4番目のコミット
11262398 3番目のコミット
ee9a0b7b 2番目のコミット
6ae9cb5d 1番目のコミット

既に共有リポジトリにプッシュしたコミットをここに含めると同じ変更内容のものが別のコミットとして見えるため混乱のもとになりますので、それは基本的には行わない方が良いようです。

コミットのrevert

開発していると、あるコミットの変更内容が不要になる場合が時々あります。
例えば、複数の機能をリリースするつもりでコミット積んでいたが一部機能はとある理由でリリースを見送りたいなどでしょうか。(機能毎に細かくブランチ切っておけという話もありましょうが。)

こういった場合には、revertが利用できます。

下記のようなコミット履歴がありリリースを待っているような状況があるとします。
素敵な機能が揃っていて、リリースされればきっと顧客も満足するはずです。

$ git log --oneline
3565bbb9 great feature
2033f2f3 すごい機能2
174d2b16 powerful feature
0b18f6c5 awesome feature
5cbc3087 すごい機能1

ここで、「すごい機能2」に関してはまだ変更が入りそうで一旦リリースを延期してほしいとの連絡が来ました。
別ブランチを立てて「すごい機能2」を除いたコミットを一つ一つ再現するとかあるいはチェリーピックするとか考えらえそうですが、まぁ手間です。

「すごい機能2」のハッシュコミットを指定してrevertしてやることで当該コミットを打ち消すコミットを追加できます。

$ git revert 2033f2f3
$ git log --oneline
952c486d Revert "すごい機能2"
3565bbb9 great feature
2033f2f3 すごい機能2
174d2b16 powerful feature
0b18f6c5 awesome feature
5cbc3087 すごい機能1

また不要だからrevertしたのだけれどやっぱりその変更が必要だったみたいな場合も起こりうるでしょう。
revertされたコミットに対しても同じ要領でrevertが可能です。

マージコミットのRevert

参考: git revertでmerge commitを取り消す

revertしようとしたコミットがマージコミットだった場合エラーになります。
(-m オプションがついてないといって怒られる。)

$ git revert aaaaaa
error: Commit aaaaaa is a merge but no -m option was given

大雑把に言うと、マージしてるわけなので、切り戻すんならどっちを生かすの?ということらしく、それを-mオプションで指定するようです。
-mオプションには1または2を基本的には指定するみたい。

-m オプションの値 意味
1 マージされた側のブランチ
2 マージする側のブランチ

なので、マージしたコミットを戻す場合は1を指定する。
基本的には1を使うことが多いはず。

$ git revert -m 1 aaaaaa

(マージした・されたってわかりにくいな。図示した方がいいか?)