git commit --fixup と git rebase --autosquash で簡単に commit が整理できて感動した話

Written by @ryysud

Apr 2, 2019 00:07 · 2636 words · 6 minutes read #git

よくある面倒なシチュエーション

PR を作成した際に、開発中にメインとなる実装の commit とは別に、レビューを受けての修正であったり、ちょっとしたリファクタリングであったり、typo 修正などの細かな commit を、PR の中で粛々と対応するシチュエーションは、どなたにも経験があることだと思います。そして、マージ前に git rebase -i を使って commit を整理するにしても、エディタを開き、どれがどれに紐付く commit なのかをマッピングするのは非常に面倒な作業だと思います。

そこで今回は、そんな面倒なシチュエーションを簡単に突破するために、git commit –fixup と git rebase –autosquash を組み合わせて簡単に commit を整理する方法をまとめてみようと思います。

前置き

commit の整理に使うコマンドである git rebase -i を実行した際に、エディタ内部で使用するコマンド群を把握しておくと、ここから先で説明することがすんなり理解できると思うので、こちら を参考に一部抜粋して先に説明しておきます。

コマンド 説明
p, pick commit を残す。こちらで指定された commit は受け身なイメージ。
s, squash 直前の pick となっている commit に統合する。commit message も統合する。
f, fixup 直前の pick となっている commit に統合する。commit message は統合 “しない” 。

git rebase -i では、基本となる commit に pick コマンドを、統合したい commit に squash/fixup コマンドをエディタで付与して、最後に統合する流れとなっております。

今回利用する git のバージョン

$ git --version
git version 2.16.1

git commit の –fixup オプションにはどんな作用があるのか?

–fixup オプションは、git rebase –autosquash で使う commit message を作成するオプションで、commit message には fixup! という prefix が付与されるとのこと。

$ git commit --help
...
       --fixup=<commit>
           Construct a commit message for use with rebase --autosquash. The commit message will be the subject
           line from the specified commit with a prefix of "fixup! ". See git-rebase(1) for details.
...

簡単に言うと –fixup オプションは、指定した commit の commit message に fixup! という prefix を付与するオプションです。この付与された fixup! という prefix を元に、後述の git rebase –autosquash で commit に対して動的にコマンドを付与する形となります。

git rebase の –autosquash オプションにはどんな作用があるのか?

–autosquash オプションは、commit message が squash! または fixup! で始まる commit に対して squash/fixup コマンドを付与するもので、git commit の –fixup または –squash オプションとの併用が推奨されているとのこと。もちろん、手動で commit message にそれらの prefix を付与する形でも問題はなさそうです。

$ git rebase --help
...
       --autosquash, --no-autosquash
           When the commit log message begins with "squash! ..." (or "fixup! ..."), and there is already a commit
           in the todo list that matches the same ..., automatically modify the todo list of rebase -i so that the
           commit marked for squashing comes right after the commit to be modified, and change the action of the
           moved commit from pick to squash (or fixup). A commit matches the ...  if the commit subject matches,
           or if the ...  refers to the commit's hash. As a fall-back, partial matches of the commit subject work,
           too. The recommended way to create fixup/squash commits is by using the --fixup/--squash options of
           git-commit(1).

           This option is only valid when the --interactive option is used.

           If the --autosquash option is enabled by default using the configuration variable rebase.autoSquash,
           this option can be used to override and disable this setting.
...

今回の文脈で、簡単に言うと –autosquash オプションは、commit message の fixup! という prefix を元に、commit に対して fixup コマンドを自動で付与するオプションです。

つまりどういうことか?

git rebase -i などで、それぞれの commit に手動でコマンドを付与せずとも、上記の git commit –fixup と git rebase –autosquash を組み合わせることで、commit に対して自動で fixup コマンドの付与が可能となり、簡単に commit が整理することが可能になります。

やってみる

適当なリポジトリを作成します。

$ mkdir test
$ cd test
$ git init
$ git commit --allow-empty -m "Initial commit"

master から add-fruits という branch を切った上で、apple というファイルを追加する commit を行います。

$ git checkout -b add-fruits
$ touch apple
$ git add apple
$ git commit -m 'Add fruits'

commit を確認する。

$ git log --oneline
e235daf (HEAD -> add-fruits) Add fruits
7131aaa (master) Initial commit

banana というファイルの commit 忘れに気がついたので、’Add fruits’ の commit に紐づく形で、git commit –fixup を使って、fixup! という prefix が付与された commit message で commit を行います。引数には紐づけたい commit を識別するための commit hash または HEAD または branch などを渡します。

$ touch banabana
$ git add banabana
$ git commit --fixup e235daf

# 紐づけたい commit を識別できれば良いので以下のパターンでも可
# git commit --fixup HEAD
# git commit --fixup add-fruits

再度 commit を確認すると、想定通りの commit が追加されたことがわかります。

$ git log --oneline
46891da (HEAD -> add-fruits) fixup! Add fruits
e235daf Add fruits
7131aaa (master) Initial commit

誤って banana でなく banabana というファイルを追加してしまったので、同じように git commit –fixup を実行します。

$ mv banabana banana
$ git add .
$ git commit --fixup e235daf

再度 commit を確認すると、想定通りの commit が追加されたことがわかります。

$ git log --oneline
bacee03 (HEAD -> add-fruits) fixup! Add fruits
46891da fixup! Add fruits
e235daf Add fruits
7131aaa (master) Initial commit

最後に git rebase –autosquash を実行してみます。引数には、どこからの commit を autosquash するかを指定する必要があるので、今回は ‘Initial commit’ 以降の commit を対象にするために、その commit を識別できる値を引数として渡します。git rebase –autosquash の実行には -i ( –interactive ) オプションが必須なのでご注意を。

$ git rebase -i --autosquash master

# commit を識別できれば良いので以下のパターンでも可
# git rebase -i --autosquash 7131aaa

エディタが開かれるので、各 commit を確認してみると、fixup! という prefix が付与された commit message の commit( git commit –fixup で追加した commit )に、想定通り fixup コマンドが付与されていることがわかります。

 1 pick e235daf Add fruits
 2 fixup 46891da fixup! Add fruits
 3 fixup bacee03 fixup! Add fruits
 4
 5 # Rebase 7131aaa..bacee03 onto 7131aaa (3 commands)
 6 #
 7 # Commands:
 8 # p, pick = use commit
 9 # r, reword = use commit, but edit the commit message
10 # e, edit = use commit, but stop for amending
11 # s, squash = use commit, but meld into previous commit
12 # f, fixup = like "squash", but discard this commit's log message
13 # x, exec = run command (the rest of the line) using shell
14 # d, drop = remove commit
15 #
16 # These lines can be re-ordered; they are executed from top to bottom.
17 #
18 # If you remove a line here THAT COMMIT WILL BE LOST.
19 #
20 # However, if you remove everything, the rebase will be aborted.
21 #
22 # Note that empty commits are commented out

エディタを閉じて、commit を確認してみると git rebase により commit が統合されて整理されたことがわかります。

$ git log --oneline
56aea71 (HEAD -> add-fruits) Add fruits
7131aaa (master) Initial commit

最後に git push -f して commit の整理が完了です。

$ git push -f origin add-fruits

以上です。

Tips のご紹介

その1

毎回 –autosquash オプションを付与するのが面倒であれば、以下のコマンドで git rebase -i 実行時に常に –autosquash オプションを有効にすることが可能です。

# 全てのリポジトリに設定したい場合
$ git config --global rebase.autosquash true

# 特定のリポジトリに限定して設定したい場合
$ git config --local rebase.autosquash true

その2

git commit –fixup で紐付ける commit を、git log を叩いて毎回探すのが面倒であれば、以下の便利スクリプトを導入することで万事解決です。素晴らしすぎますね…!!!
https://qiita.com/uasi/items/57da2e4268d348b371fb

その3

review bot を自ら実装、もしくは既存のものを探して、GitHub に組み込み、マージ前に git rebase –autosquash が自動実行されるようにすると、とても幸せになれそうです。
https://github.com/search?q=autosquash&type=Repositories

さいごに

git commit –fixup と git rebase –autosquash を組み合わせることで、commit の整理が簡単に行えて、とても便利なのでどんどん使っていこうと思います。また、このような git の便利な使い方を他にも知っていきたい気持ちが高まったので、今後新たな発見があったら、今回と同様に理解するがてらまとめていこうと思います。おしまい!

参考資料