GitHubのコミット|1回にまとめる範囲をどう決めるか
github コミットで検索する人の多くは、コマンドの打ち方で止まっているわけではありません。止まっているのは、いま手元にある変更をどこで区切るか、メッセージに何を書くか、そして区切りを間違えたあとにどう作り直すかという3点です。操作の手順はどの入門記事にも載っていますが、判断の基準はあまり書かれていません。この記事では、1回のコミットにまとめる範囲の決め方を先に固めて、そのうえでメッセージの形と、範囲を後から組み替えるコマンドを扱います。
コミットが記録しているもの、していないもの
最初に用語を1つに揃えます。コミットは、変更をサーバーへ送る操作ではありません。手元のリポジトリに、その時点のファイルの状態をひとまとまりとして記録する操作です。
GitHubにおけるコミット(commit)とは、ファイルの変更内容を記録する行為です。「どのファイルをどう直したか」を保存することで、履歴が積み重なり、後から過去の状態に戻したり、他の人と変更を共有できます。 出典: note.com
ここで見落とされやすいのは、記録の単位を決めているのが人間だという点です。Gitは変更を自動で意味のある塊に分けてはくれません。ステージに載せたものが、そのまま1つの記録になります。つまりコミットの良し悪しは、コマンドの知識ではなく、載せる前の選び方で決まります。
記録されないものも押さえておく価値があります。作業中の思考の順番、試して捨てた実装、エディタの操作履歴は残りません。残るのは結果の状態と、書いた説明文だけです。だからこそ、3か月後に履歴を辿る人が読むのは、コードの差分と、メッセージの1行目だけになります。
もう1つ、GitHub側の事情も関わります。コミットはGitの機能ですが、プルリクエスト、レビュー、Actionsの実行結果はGitの外側にある機能です。手元で作った履歴の形が、そのままレビューの読みやすさに直結するため、コミットの粒度は自分だけの問題では終わりません。
1回にまとめる範囲を決める3つの基準
「機能単位」「ファイル単位」といった言い方は、実際の作業では判定できません。もっと機械的に判定できる基準を3つ置くと迷いが減ります。
戻せる単位になっているか
そのコミットだけを取り消したときに、リポジトリが壊れないかどうかで判定します。関数の追加と、その関数を呼ぶ側の修正が別のコミットに分かれていると、前者だけを取り消した瞬間に動かなくなります。逆に、変数名の一括変更と新機能の追加が同じコミットに入っていると、新機能だけを取り消すことができません。戻す操作を想像したときに困らない大きさが、1回分の上限です。
1行で説明できるか
メッセージの1行目を書こうとして、「と」や「、」で2つ以上の話を並べたくなったら、範囲が広すぎます。「ログイン画面の検証を追加し、あわせて古いヘルパーを削除」は2つのコミットです。この判定は書きながらできるので、実務では最も速く効きます。
レビューする側が読み切れるか
変更行数の目安を機械的に決める必要はありませんが、レビューの依頼を受けた人が一度に追える量には上限があります。差分が数百行を超えると、指摘は形式的なものに寄り、設計の議論が出てこなくなります。3つの基準のうち、これだけは自分では判定できないため、レビューが毎回コメントなしで通るときは範囲が大きすぎる兆候だと考えます。
3つを同時に満たせない変更もあります。仕様変更に伴う大規模な置換などです。その場合は、機械的な置換だけを1つのコミットにして、判断が入った部分を別のコミットに分けます。読む側は前者を読み飛ばせるようになり、実質的なレビュー量が下がります。
記録する前に差分を確かめる手順
範囲の基準を持っていても、載せるものを見ずに記録すれば意味がありません。手順は3つの道具で足ります。git status で変更されたファイルの一覧を見て、git diff でまだステージに載せていない差分を見て、git diff --staged で載せ終わった差分を見ます。公式の説明では --staged は --cached と同じ意味の指定で、いずれもステージと直前のコミットを比べた結果を出します。
ここで落とせるものは決まっています。デバッグのために入れた出力、コメントアウトしたまま残った古い実装、打ち間違えたままの変数名、試して戻し忘れた設定値です。どれもコードとしては動くため、後で読み返すまで気づきません。
変更点でタイポやデバッグで使用していたコメントなどを解消したらコミットします。コミットするのは以下の2ステップです。 出典: qiita.com
載せる操作と記録する操作が2つに分かれているのは、手間を増やすためではありません。載せる段階で選べるようにするためです。git commit -am のように両方を一度に済ませる書き方は、急いでいるときには便利ですが、選ぶ機会そのものを飛ばします。範囲の判断を手放しているのは、この1行を打った瞬間です。
新しく変わったファイルを差分に含めたい場合は、先に git add で載せる必要があります。追跡されていないファイルは git diff に出ないため、git status の一覧を先に見る順番には理由があります。この順番を固定しておくと、意図せず設定ファイルや鍵を記録に混ぜる事故も減ります。
メッセージの1行目を短く書く根拠
メッセージの形は好みの問題として語られがちですが、Gitの公式ドキュメントに書式の推奨が明記されています。
Though not required, it's a good idea to begin the commit message with a single short (no more than 50 characters) line summarizing the change, followed by a blank line and then a more thorough description. 出典: git-scm.com
同じ箇所には、最初の空行までがコミットのタイトルとして扱われ、そのタイトルがGit全体で使われるとも書かれています。これが50文字という目安の根拠です。短くする理由は美意識ではなく、1行目が一覧表示やブランチの表示、プルリクエストの初期タイトルなど、複数の場所に流用されるからです。
英語の50文字は日本語なら20文字から25文字程度に相当します。ここに収めようとすると、自然に「何を変えたか」だけが残り、「なぜ変えたか」は本文側へ移ります。この分業が読みやすさの正体で、テンプレートを暗記する必要はありません。
本文側に書く内容は3つに絞れます。1つ目は、その変更が必要になった理由です。2つ目は、採用しなかった方法とその理由です。3つ目は、影響範囲の注意です。どれも差分を読めば分かることではないため、書く価値があります。逆に、差分を読めば分かること、たとえば変更したファイル名の列挙は書きません。
日本語で書くか英語で書くかは、リポジトリの読者で決めます。社内の非公開リポジトリなら日本語で統一したほうが情報量が増えます。公開リポジトリや外部委託が入る場合は英語に寄せます。途中で切り替えると履歴の検索性が落ちるため、最初に決めて揃えるのが要点です。
範囲を決め直すためのコマンド
コミットの粒度は、一度で正解を出す必要がありません。作りながら直せます。使う道具は4つで足ります。
ステージに載せる段階で分けるなら git add -p を使います。ファイル単位ではなく変更の塊単位で載せるかどうかを選べるため、1つのファイルの中に2種類の変更が混ざったときに有効です。git commit -p でも同じ選択画面が開きます。
直前のコミットを作り直すなら git commit --amend です。公式ドキュメントでは、現在のブランチの先端を新しいコミットで置き換える操作として説明されています。元のメッセージが初期値として開くため、書き間違いの修正にも使えます。
3つ以上前のコミットを直すなら git commit --fixup=<commit> を使います。これは fixup! で始まるコミットを作り、git rebase --autosquash を実行したときに指定したコミットへ吸収させる仕組みです。メッセージだけを直したいときは --fixup=reword:<commit>、内容とメッセージの両方なら --fixup=amend:<commit> という書き方が用意されています。
git add -p
git commit -m "検証エラーの文言を画面表示にそろえる"
git commit --fixup=HEAD~3
git rebase -i --autosquash HEAD~5
注意点が1つあります。すでに共有したブランチの履歴を作り直すと、同じブランチを見ている人の手元と食い違います。作り直すのは、まだ誰も取得していない自分のブランチに限るのが安全です。また --no-verify でフック(コミット前に走る検査)を飛ばす指定もありますが、これは検査が壊れているときの一時的な回避手段であり、常用すると検査の意味がなくなります。
画面の道具とターミナルの分担
コミットの操作は、ターミナルでもGUIでも行えます。初心者向けの解説では画面の道具から入ることが多く、それは合理的です。差分を色分けして見ながら、載せる範囲をチェックボックスで選ぶ作業は、画面のほうが速く正確です。
一方で、履歴の作り直しはコマンドの側が得意です。--fixup と --autosquash の組み合わせに相当する操作は、画面の道具では用意されていないことが多く、結局ターミナルへ移ることになります。どちらかに決めるのではなく、載せる段階は画面、作り直しはコマンドという分担が現実的です。
その前提で困るのが、窓の数です。差分を見る窓、ターミナルの窓、リポジトリのフォルダを開く窓、そして仕様を確認するブラウザの窓が別々に並びます。コミットの粒度が荒くなる原因の多くは、判断力の不足ではなく、この行き来の途中で「ここまでで1回にしておこう」という区切りの機会を失うことです。フォルダとターミナルとAIが同じ窓にある状態を選ぶと、差分を確認してからコマンドを打つまでの距離が短くなります。ファイル管理アプリで何ができるのかはできることにまとまっています。
窓の行き来を減らすと粒度が変わる
作業の記録を残す道具として見たとき、コミットは「作業を中断できる地点を自分で作る操作」です。中断できる地点が細かく並んでいれば、割り込みが入っても戻ってこられます。粗いと、中断のたびに作業中の変更が宙に浮きます。
この観点で効くのは、コミットのコマンドを覚えることよりも、区切りを作る瞬間の手間を減らすことです。フォルダの一覧とシェルが同じ窓にあると、変更したファイルを見ながら git add -p を打ち、続けて次の作業へ移る動作が1つの窓で完結します。窓を切り替える操作が消えると、区切りを作る回数が増えます。
同じことは作業場所の移動にも当てはまります。手元の機械を離れたあとに続きを確認したい場面では、iPhone・iPadから続きをで扱っている使い方が近い話になります。導入前に条件を確かめたい場合は料金とよくある質問を先に見ておくと、判断が早くなります。
コミットの粒度に正解の数値はありません。決められるのは基準だけです。戻せる単位であること、1行で説明できること、レビューする人が読み切れること。この3つを満たす区切りを作りやすい環境を選ぶことが、メッセージの書き方を覚えるより先に効きます。
よくある質問
コミットメッセージは日本語で書いてもよいですか?
リポジトリを読む人で決めます。社内の非公開リポジトリなら日本語のほうが情報量が増え、検索もしやすくなります。公開リポジトリや外部の開発者が入る場合は英語に寄せます。途中で混在させると履歴の検索性が落ちるため、最初に決めて統一することが要点です。
コミットとプッシュは何が違うのですか?
コミットは手元のリポジトリに変更を記録する操作で、ネットワークを使いません。プッシュは、その記録をGitHub側のリポジトリへ送る操作です。コミットを何回か重ねてから1回プッシュする形が普通で、プッシュしていない履歴は自分の手元にだけある状態になります。
間違ったメッセージで記録してしまいました。直せますか?
直前のものなら git commit --amend でメッセージを書き直せます。もっと前のものは git commit --fixup=reword:<commit> を作ってから git rebase --autosquash で吸収させます。ただし共有済みのブランチで履歴を作り直すと他の人の手元と食い違うため、自分だけのブランチに限るのが安全です。
1回のコミットはどのくらいの行数が適切ですか?
行数で決めず、取り消したときに壊れない単位かどうかで決めます。目安としては、メッセージの1行目を「と」でつながずに書けるかを確かめる方法が速く、2つの話が並ぶなら分けます。レビューで指摘が形式的なものばかりになるときは、範囲が大きすぎる兆候です。