きれいな Git コミットログをつくる

下記の Qiita 投稿内容を試した時の作業ログです。

綺麗なコミットログを作りたいときのgitテクニック

環境

  • Ubuntu 20.04.4 LTS
  • git version 2.37.3

コードの更新内容

更新前

./main.go

package main

import (
    "fmt"
)

func main() {
    fmt.Println(double(2))
}

./sub.go

package main

func double(x int) int {
    return x * 2
}

更新後

./main.go

package main

import (
    "fmt"
)

func main() {
    fmt.Println(triple(2))           // double() を triple() に変更
    fmt.Println(quadruple(2))        // quadruple() を作成
}

./sub.go

package main

func triple(x int) int {              // double() を triple() に変更
    return x * 3                      // ...
}                                     // ...

func quadruple(x int) int {           // quadruple() を作成
    return x * 4                      // ...
}                                     // ...

コミットログの変更方針

以下の通り、ファイル単位の編集で都度コミットしていたものを、関数単位の編集でコミットする内容に書き換えます。

  • 変更前
    • main.go ファイルの更新 ([WIP] update main.go)
    • sub.go ファイルの更新 ([WIP] update sub.go)
  • 変更後
    • double() を triple() に変更 ([update] change double() to triple())
    • quadruple() を作成 ([update] create quadruple())

コミットログをきれいにする

実際にコミットログを変更していきます。

変更前のログを確認

コミットログの確認。

$ git log --oneline
56e25d9 (HEAD -> develop) [WIP] update sub.go
3bc796e [WIP] update main.go
6cac0ed (origin/master, master) init

各コミットでの更新内容。

$ git log -p -2
commit 56e25d9aadd674ea27023bd860ae41100c1a53b6 (HEAD -> develop)
Author: goodbyegangster <xxx@example.com>
Date:   Sun Oct 2 16:15:51 2022 +0900

    [WIP] update sub.go

diff --git a/sub.go b/sub.go
index d4e9f4f..29d3e4c 100644
--- a/sub.go
+++ b/sub.go
@@ -1,5 +1,9 @@
 package main
 
-func double(x int) int {
-       return x * 2
+func triple(x int) int {
+       return x * 3
+}
+
+func quadruple(x int) int {
+       return x * 4
 }

commit 3bc796eaef34b0f430471b8db640c480ae902a67
Author: goodbyegangster <xxx@example.com>
Date:   Sun Oct 2 16:15:40 2022 +0900

    [WIP] update main.go

diff --git a/main.go b/main.go
index 450d92b..a91ce44 100644
--- a/main.go
+++ b/main.go
@@ -5,5 +5,6 @@ import (
 )
 
 func main() {
-       fmt.Println(double(2))
+       fmt.Println(triple(2))
+       fmt.Println(quadruple(2))
 }

コミットのリベース

git rebase コマンドは、既存のコミットを作り直してくれるコマンドです。

git-rebase - Reapply commits on top of another base tip

gi rebase

作り直したいコミットを指定して、 git rebase コマンドをインタラクティブモードで起動します。指定するコミット ID は、「作り直したいコミットの直前のもの」を指定します。言い換えると、「指定したコミットIDより後のコミットを作り直す」ことになります。

テキストエディタが起動され、変更可能なコミットがエディタ冒頭に表示されます。行頭部分の文字列(下記例では pick 等)が、そのコミットへの変更指示箇所となります。下記例では、3bc796e というコミットに、それより後のコミット内容を統合させる指示となります。

$ git rebase -i 6cac0ed
pick 3bc796e [WIP] update main.go   <- この行は変えない。以下行のコミットが、このコミットに統合される
f 56e25d9 [WIP] update sub.go       <- pick から f(fixup) に変更

# Rebase 6cac0ed..56e25d9 onto 6cac0ed (2 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
#
# 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.

git rebase -i

リベース実行後、コミットが統合されました。

$ git log --oneline
e55ce27 (HEAD -> develop) [WIP] update main.go
6cac0ed (origin/master, master) init

コミット内容の確認。

$ git log --oneline --name-status
e55ce27 (HEAD -> develop) [WIP] update main.go
M       main.go
M       sub.go
6cac0ed (origin/master, master) init
A       go.mod
A       main.go
A       sub.go

リベースのインタラクティブモードから、処理を実行させないで抜ける方法

注意点です。git rebase コマンドをインタラクティブモードで起動した後、心変わりあってリベースを実行したくなった場合、例えば git のテキストエディタvim であれば、:q! のように未保存でファイルを閉じたとしても、リベース処理は実行されます。

リベース処理を実行したくない場合、以下のどちらかで対応できます。

If you exit the editor with an error code, the rebase will be aborted. Just delete all the lines that are not comments from the file. Then save it to the default supplied path and exit the editor.

How to abort a git rebase from inside vim during interactive editing

コミットの打ち消し

上述手順で統合させたコミットを、git reset コマンドで打ち消します。

$ git reset HEAD~
Unstaged changes after reset:
M       main.go
M       sub.go

mode を指定せずリセットした場合、該当コミット内容が作業ツリーに返ってきます。

This form resets the current branch head to <commit> and possibly updates the index (resetting it to the tree of <commit>) and the working tree depending on <mode>. If <mode> is omitted, defaults to --mixed.

--mixed Resets the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what has not been updated. This is the default action.

git reset

打ち消されたことを確認。

$ git log --oneline
6cac0ed (HEAD -> develop, origin/master, master) init

作業ツリーにファイルがいることを確認。

$ git status --short
 M main.go
 M sub.go

コミットしたい行単位で、インデックスする

-p(--patch) オプションを付与して git add すると、インテックスしたい範囲を詳細に指定できます。本例では e(manual edit) を指定します。

$ git add -p main.go
diff --git a/main.go b/main.go
index 450d92b..a91ce44 100644
--- a/main.go
+++ b/main.go
@@ -5,5 +5,6 @@ import (
 )
 
 func main() {
-       fmt.Println(double(2))
+       fmt.Println(triple(2))
+       fmt.Println(quadruple(2))
 }
(1/1) Stage this hunk [y,n,q,a,d,e,?]? e    <= e と入力する

選択肢の説明は下記。

y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
e - manually edit the current hunk
? - print help

次に表示される画面で、インデックスしたい範囲を行単位で指定できます。

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -5,5 +5,6 @@ import (
 )

 func main() {
-       fmt.Println(double(2))
+       fmt.Println(triple(2))
 }
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# If the patch applies cleanly, the edited hunk will immediately be marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

git add -p

エディタを閉じると、指定した内容でインデックスされます。インテックス内容の確認。

$ git status --short
MM main.go
 M sub.go

より詳細に確認。

$ git diff --cached main.go
diff --git a/main.go b/main.go
index 450d92b..448d47b 100644
--- a/main.go
+++ b/main.go
@@ -5,5 +5,5 @@ import (
 )
 
 func main() {
-       fmt.Println(double(2))
+       fmt.Println(triple(2))
 }

上記の方法で、コミットしたい行単位でインデックス登録処理を繰り返します。繰り返し処理部分は割愛。

希望通りにインデックスできた後、コミットを実行。

$ git commit -m "[update] change double() to triple()"
[develop 90b5d82] [update] change double() to triple()
 2 files changed, 3 insertions(+), 3 deletions(-)

これをコミットさせたい単位で繰り返します。

最終確認

最終確認です。

$ git log --oneline
c0b98f2 (HEAD -> develop) [update] create quadruple()
90b5d82 [update] change double() to triple()
6cac0ed (origin/master, master) init