Git で複数のコミットを1つにまとめられる「スカッシュ」というテクニック
こんにちは、CX 事業本部 Delivery 部の若槻です。
今回は、Git で複数のコミットをまとめる方法を確認してみました。
ちなみに Git で行うこの操作のことを「スカッシュ(squash)」するとも言います。squash は「押しつぶす」とか「ぺちゃんこにする」という意味だそうです。
環境
$ vim --version VIM - Vi IMproved 9.0 (2022 Jun 28, compiled Jun 23 2023 22:12:29) macOS version - arm64 Included patches: 1-1544
確認してみた
スカッシュしたいコミットが「連続する」場合と「連続していない」場合の 2 通りの方法を確認してみました。
連続するコミットの場合
まずは「連続する」複数のコミットをスカッシュする場合の方法です。
スカッシュ前の状態
次のようなコミットログがある。このうち funcB
の実装に関する変更である 3 つのコミット e03354c
、 af7c678
および 346de39
を 1 つにまとめたいとします。
$ git log --oneline -5 d8a5fac (HEAD -> main) implement funcC 346de39 typo funcB af7c678 fix funcB e03354c implement funcB f3b0727 implement funcA
スカッシュする
コマンド git rebase -i HEAD~
を実行します。-i
は --interactive
のエイリアスです。
git rebase -i HEAD~5
するとコミットログがインタラクティブモードで開き、リベースできるようになります。
pick f3b0727 implement funcA pick e03354c implement funcB pick af7c678 fix funcB pick 346de39 typo funcB pick d8a5fac implement funcC # Rebase ab8e0fe..d8a5fac onto ab8e0fe (5 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous # commit's log message, unless -C is used, in which case # keep only this commit's message; -c is same as -C but # opens the editor # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # create a merge commit using the original merge commit's # message (or the oneline, if no original merge commit was # specified); use -c <commit> to reword the commit message # u, update-ref <ref> = track a placeholder for the <ref> to be updated # to this position in the new commits. The <ref> is # updated at the end of the rebase # # 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. #
キーボードでi
を押下し、インサートモードに入ります。インサートモードが有効になるとエディター末尾に-- INSERT --
と表示されます。
pick f3b0727 implement funcA pick e03354c implement funcB pick af7c678 fix funcB pick 346de39 typo funcB pick d8a5fac implement funcC (中略) -- INSERT --
スカッシュしたい一連のコミットのうち、最も古いコミット以外のコミットの行頭の pick
を s
(または squash
)に変更します。
pick f3b0727 implement funcA pick e03354c implement funcB s af7c678 fix funcB s 346de39 typo funcB pick d8a5fac implement funcC
変更が完了したら ESC
キーでインサートモードを抜けて、:wq
で保存します。
すると次はコミットメッセージを編集する画面になります。ここでは、スカッシュしたいコミットのメッセージを残して、それ以外のコミットのメッセージは削除します。編集はまたインサートモードで行います。
# This is a combination of 3 commits. # This is the 1st commit message: implement funcB # This is the commit message #2: fix funcB # This is the commit message #3: typo funcB # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Sat Oct 7 03:14:46 2023 +0900 # # interactive rebase in progress; onto ab8e0fe # Last commands done (4 commands done): # squash af7c678 fix funcB # squash 346de39 typo funcB # Next command to do (1 remaining command): # pick d8a5fac implement funcC # You are currently rebasing branch 'main' on 'ab8e0fe'. # # Changes to be committed: # new file: funcB.ts #
編集後の画面です。#
で始まる行はコミットメッセージには表示されないので、残したままでも問題ありません。
# This is a combination of 3 commits. # This is the 1st commit message: implement funcB # This is the commit message #2: # This is the commit message #3:
変更が完了したら ESC
キーでインサートモードを抜けて、:wq
で保存します。
スカッシュ後の状態
確認のためコミットログを表示します。funcB
の実装に関する変更である 3 つのコミットが 1 つにまとめられていることが分かります。またスカッシュされたコミットおよびスカッシュ後のコミットのハッシュ値は変わっています。
$ git log --oneline -3 64daa2c (HEAD -> main) implement funcC 972f682 implement funcB f3b0727 implement funcA
スカッシュされたコミットの内容を確認します。funcB
の実装に関する変更である 3 つのコミットが 1 つにまとめられていることが分かります。(変更内容はかなり適当です。)
$ git show 972f682 commit 972f68222885a7558563db9a948f6c013e9feddb Author: cm-rwakatsuki <wakatsuki.ryuta@classmethod.jp> Date: Sat Oct 7 03:14:46 2023 +0900 implement funcB diff --git a/funcB.ts b/funcB.ts new file mode 100644 index 0000000..d03c6d8 --- /dev/null +++ b/funcB.ts @@ -0,0 +1,2 @@ +() => {}; +() => {};
連続していないコミットの場合
次に、「連続していない」複数のコミットをスカッシュする場合です。と言っても連続した場合と比べてそんなに特別な操作は必要ありません。
スカッシュ前の状態
例えば、次のようなコミットログのうち、間に別のコミットを挟んでいる 64daa2c
と f3b0727
をスカッシュしたいとします。
$ git log --oneline -3 64daa2c (HEAD -> main) implement funcC 972f682 implement funcB f3b0727 implement funcA
スカッシュする
先ほどと同様に、コマンド git rebase -i HEAD~
を実行します。
git rebase -i HEAD~3
リベースのインタラクティブモードが開かれます。
pick f3b0727 implement funcA pick 972f682 implement funcB pick 64daa2c implement funcC
開いたエディターでインサートモードに入ったら、次のようにスカッシュしたいコミットが連続するように並べ替えます。また先ほどと同様に最も古いものを除くスカッシュしたいコミットの行頭の pick
を s
(または squash
)に変更します。
pick 972f682 implement funcB pick f3b0727 implement funcA s 64daa2c implement funcC
変更が完了したら ESC
キーでインサートモードを抜けて、:wq
で保存します。
ここからも同じです。コミットメッセージを編集する画面になるので、スカッシュ後のメッセージを記載します。
# This is a combination of 2 commits. # This is the 1st commit message: implement funcA # This is the commit message #2: implement funcC
変更後のコミットはこのようにしてみました。
implement funcA funcC
変更が完了したら ESC
キーでインサートモードを抜けて、:wq
で保存します。
スカッシュ後の状態
コミットログの確認。指定したコミットメッセージの新しいコミットにスカッシュされていることが分かります。
$ git log --oneline -2 7771112 (HEAD -> main) implement funcA funcC 22fe013 implement funcB ab8e0fe fix
コミットの内容も期待どおりです。
$ git show 7771112 commit 7771112b3d51a1c22c784e8a9f9b28c520cc4756 (HEAD -> main) Author: cm-rwakatsuki <wakatsuki.ryuta@classmethod.jp> Date: Sat Oct 7 03:14:29 2023 +0900 implement funcA funcC diff --git a/funcA.ts b/funcA.ts new file mode 100644 index 0000000..e69de29 diff --git a/funcC.ts b/funcC.ts new file mode 100644 index 0000000..e69de29
スカッシュをもとに戻したい場合
スカッシュを間違えてしまった時などは git reset --hard
コマンドでリベース前に戻すことが可能です。方法は下記を参考にしてみてください。
おわりに
Git で複数のコミットをまとめる(スカッシュする)方法を確認してみました。
開発をする中で本来まとめたかったコミットが分かれてしまうことはあるあるだと思いまます。そのような場合はぜひスカッシュを試してみてください。
参考
以上