こんにちは。バックエンドエンジニアの河内です。
今回は、コミットを整理する方法のうち、個人的に使用頻度が高いものについて書きます。
前提方針
コミットログを整えるために編集を加えるのは、ローカルリポジトリのコミットだけとします。 逆に言うと、リモートリポジトリにpushされたコミットについては編集を加えないということです。
たとえば、以下リモートリポジトリで(C)が最新の場合、 ローカルリポジトリで編集してよいのは(d)と(e)です。
[リモートリポジトリ]
※ 行頭の(A)などの識別子は説明のため加えており、実際には表示されません (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
[自分のローカルリポジトリ]
(e) 58e6b3a コミットe ← 編集してもよい (d) 3c36383 コミットd ← 編集してもよい (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
各行頭の識別子に関し、ローカルのコミットログについてはアルファベット小文字で表現しました。 後述の事例でも区別のため、そのルールで表記します。
よくある事例とその対処
最新のコミットに対して
(1) 最新のコミットのメッセージを間違えた
以下のコミットログを見てみると…「官僚」ではなく「完了」のような気がします。
(d)はローカルリポジトリのコミットなのでまだ間に合います。
git log --oneline (d) ee17560 事例1用管理画面実装官僚 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
[対処]
git commit --amend
エディタが開くので、メッセージを「官僚」→「完了」と編集し、保存します。
事例1管理画面実装官僚 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # :
修正できました。
ハッシュ値は変わります。既存コミットは上書き保存され、別物になりました。
git log --oneline (d) 03a9339 事例1用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
前提方針でリモートリポジトリにpushされたコミットに編集を加えない、としたのは、このようにコミットが上書き保存される操作のためです。
上書きしたコミットを再pushした場合、上書きされる前のコミットをすでにローカルリポジトリに取り込んでいた別の作業者がいたら不整合が発生するので、なんらかのすり合わせ作業が必要となってしまいます。
次の事例に進みます。
(2) 現在の変更点を最新のコミットに含めたかった
git log --oneline (d) ce9ed66 事例2用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
(d)のコミットのあとindex.phpを編集したが、この内容は(d)に含めたかった…とします。
[対処]
git add index.php git commit --amend
まず、index.phpをインデックスに登録します。
git add index.php
そして、git commit --amend
です。
すると、addした内容が(d)のコミットに含まれる形でコミットログの編集を求められます。 メッセージはこのままでよいので編集せずに保存。
事例2用管理画面実装完了 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # :
コミットを増やさず保存できました。
git log --oneline (d) 5d75642 事例2用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
ちなみに、(1)と(2)が同時に起こった場合、(2)でメッセージ編集をすればよいわけなので1回のgit commit --amend
でいけますね。
最新のコミットより前のコミットに対して
(3) 最新のコミットの1つ前のコミットのメッセージを間違えた
(1)、(2)は最新のコミットに対しての話でしたが、それより前のコミットとなると別のアプローチをとる必要があります。
今回は、(d)のコミットメッセージを間違えているようです。
git log --oneline (e) 0e647f2 リファクタ (d) 7cfd024 事例3用管理画面実装官僚 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
[対処]
git rebase -i 32096c2 ※ 「r, reword」利用
git rebase
を使います。
git rebase
には他のブランチを巻き込む編集も存在しますが、今回は現行のブランチだけを対象とし過去のコミットに対してまとめて編集を加える、-i (--interactive)オプションを使います。
引数には、編集対象の1つ前のコミットを指定します。 今回は(d)の直前の(C)なので、ハッシュ値「32096c2」を指定しました*1。
すると、以下のように表示されます。git log
の順と違い、上から古い順である(d)、(e)の順で表示されます。
よく見ると、コメント部の「Commands:」にできることが書いています。 今回したいことは、コミットメッセージの編集なので、これには「r, reword」を使います。
(d) pick 7cfd024 事例3用管理画面実装官僚 (e) pick 0e647f2 リファクタ # Rebase 32096c2..0e647f2 onto 32096c2 (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 <commit> = like "squash", but discard this commit's log message # 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. # # Note that empty commits are commented out
該当コミットの「pick」を「r」もしくは「reword」に置き換えます*2。 今回は短い「r」を使用します。置き換えたら保存します。
(d) r 7cfd024 事例3用管理画面実装官僚 (e) pick 0e647f2 リファクタ :
すると、そのコミットログの編集を求められます。(1)の時と同様、メッセージ編集〜保存すると反映されます。
事例3用管理画面実装官僚 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # :
(d)のハッシュ値のほかに、内容としては何も変更のなかったはずの(e)のハッシュ値も変更されています。 対象コミットの後続のコミットすべてのコミットについて、このように変更がなされます。
逆に、「(d)以降」を指定するためにgit rebase -i
のオプションに(C)のハッシュ値を渡しましたが、(C)に影響はありません。
git log --oneline (e) 0dad9a6 リファクタ (d) 79377c3 事例3用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
(4) 現在の変更点を最新のコミットの1つ前のコミットに含めたかった
(2)と同様、index.phpを編集しましたが、この編集内容は(d)に含めたかったです。メッセージは正しそうです。
git log --oneline (e) 2092de6 リファクタ (d) b3fed10 事例4用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
[対処]
git add index.php git commit git rebase -i 32096c2 ※ 「f, fixup」利用
同じくgit rebase -i
ですが、「f, fixup」を使います。
まず、index.phpの編集内容をコミットします。
git add index.php git commit
このあと(d)に取り込まれるので、コミットメッセージはなんでもよいです。
事例4用管理画面実装完了の作業漏れ # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # :
(f)ができました。(f)を(d)に取り込みます。
git log --oneline (f) adfec57 事例4用管理画面実装完了の作業漏れ (e) 2092de6 リファクタ (d) b3fed10 事例4用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
git rebase -i 32096c2
と打つと編集モードになります。
(d) pick b3fed10 事例4用管理画面実装完了 (e) pick 2092de6 リファクタ (f) pick adfec57 事例4用管理画面実装完了の作業漏れ # Rebase 32096c2..adfec57 onto 32096c2 (3 commands) # # Commands: :
含めたいコミット(f)を、含め先のコミット(d)の直下に移動します。そして、(f)の「pick」を「f」に書き換えて保存します。
(d) pick b3fed10 事例4用管理画面実装完了 (f) f adfec57 事例4用管理画面実装完了の作業漏れ (e) pick 2092de6 リファクタ # Rebase 32096c2..adfec57 onto 32096c2 (3 commands) # # Commands: :
すると、(f)は(d)に吸収されます。ハッシュ値は、(d)以降のコミットについて変更されています。
git log --oneline (e) 605cb37 リファクタ (d) 533f9bb 事例4用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
――上記のフローを理解した上で、少しだけ早い方法を使うこともできます。
[少しだけ早い対処]
git add index.php git commit --fixup=b3fed10 git rebase -i 32096c2 --autosquash
コミットの時に、含め先のコミット(d)のハッシュ値を--fixupで指定します。 すると、プレフィックスのついたコミットメッセージを持つコミットが生成されます。
git log --oneline (f) b230d6b fixup! 事例4用管理画面実装完了の作業漏れ (e) 2092de6 リファクタ (d) b3fed10 事例4用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
そして、git rebase -i 32096c2
のオプションに--autosquashをつけると、コミットの移動と「pick」の置き換えが自動でおこなわれた状態で編集モードに入ります。「pick」の置き換えは「f」ではなく正式な「fixup」となるようですね。
(d) pick b3fed10 事例4用管理画面実装完了 (f) fixup b230d6b fixup! 事例4用管理画面実装完了の作業漏れ (e) pick 2092de6 リファクタ # Rebase 32096c2..b230d6b onto 32096c2 (3 commands) # # Commands: :
保存すれば、目的達成です。
(5) 現在の変更点を最新のコミットの1つ前のコミットに含め、メッセージも変更したい。
では、(2)で同時に(1)、(2)の操作をおこなうことを考えたように、(3)と(4)を同時におこないたい場合どうしたらよいでしょうか。もちろん、(3)と(4)を別々におこなうこともできますが、同時にもおこなえます。
[対処]
git add index.php git commit git rebase -i 32096c2 ※ 「s, squash」利用
git rebase
での編集まで(4)と同じ流れです。
コミット。
git add index.php git commit
メッセージ編集〜保存。
事例5用管理画面実装官僚の作業漏れ # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # :
(f)ができました。
git log --oneline (f) 547cd2b 事例5用管理画面実装官僚の作業漏れ (e) 9a636f7 リファクタ (d) 16b489d 事例5用管理画面実装官僚 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
(f)を(d)に取り込みます。git rebase -i 32096c2
ですね。
含めたいコミット(f)を、含め先のコミット(d)の直下に移動するところまでは(4)と同じです。 ここで、(f)の「pick」を「s」に書き換えて保存するだけです。
(d) pick 事例5用管理画面実装官僚 (f) s 547cd2b 事例5用管理画面実装官僚の作業漏れ (e) pick 9a636f7 リファクタ # Rebase 32096c2..16b489d onto 32096c2 (3 commands) # # Commands: :
すると、2つのメッセージが上下に配置された状態で編集モードに入ります。
# This is a combination of 2 commits. # This is the 1st commit message: 事例5用管理画面実装官僚 # This is the commit message #2: 事例5用管理画面実装官僚の作業漏れ # Please enter the commit message for your changes. Lines starting :
上記のまま保存すると、以下のようなコミットメッセージになってしまいます。
事例5用管理画面実装官僚 事例5用管理画面実装官僚の作業漏れ
以下のように編集して保存します。
# This is a combination of 2 commits. # This is the 1st commit message: 事例5用管理画面実装完了 # Please enter the commit message for your changes. Lines starting :
すると、(f)は(d)に吸収されメッセージも編集されました。
git log --oneline (e) 2536867 リファクタ (d) 35bcb56 事例5用管理画面実装完了 (C) 32096c2 コミットC (B) ae4f281 コミットB (A) 6dcd4ce コミットA
まとめ
小分けのコミットを心がけると整理も容易になります。また、逆に整理を意識するとコミットを小分けするよう意識が向きます。
コミットログは今ままで自分が何をしてきたかの歴史となるので、きれいに記せていければよいのかな、と思います。
最後に
Wizではエンジニアを募集しております。 興味のある方、ぜひご覧下さい。