git add と git commit: 変更を正しく保存する方法
Gitでの変更の保存は2段階で行います。git add で次のスナップショットに含める変更をインデックス(ステージングエリア)に追加し、git commit -m "メッセージ" でそれを1つのスナップショットとして履歴に記録します。git status で変更内容とステージ済みの内容を確認できます。直前のコミットは git commit --amend で修正できます。
Gitには「保存」ボタンがありません。変更は2段階で記録されます。まず git add で、次のスナップショットに含める変更を選びます。次に git commit で、そのスナップショットを履歴に記録します。両者の間の中間領域はインデックス、またはステージングエリアと呼ばれます。この仕組みを長い間理解できない人は多いですが、コミットを意味のある単位にまとめてくれるのがこの領域です。
ファイルの3つの状態: 作業ディレクトリ、インデックス、履歴
どの変更も3つの領域を順に通ります。
- 作業ディレクトリ: エディタでファイルを編集する場所です。
- インデックス(ステージ):
git addで、現在のファイルの内容が次のコミットの下書きに入ります。 - 履歴:
git commitで、インデックスの内容が作成者、日時、メッセージ付きのコミットになります。
宅配にたとえると分かりやすいです。作業ディレクトリは物が置いてある部屋、インデックスは送りたい物を入れていく開いた箱、コミットは封をして署名した荷物です。箱に何を入れるかは自分で決められ、すべてを一度に送る必要もありません。
インデックスが必要な理由
たとえば1時間の間に、認証のバグを直し、README の誤字も直し、新機能の作業を始めたとします。インデックスがなければ、これらはすべて1つの「いろいろ」というコミットに入ってしまいます。インデックスを使えば、3つの別々のコミットにできます。それぞれ内容が分かりやすく、レビューもしやすく、他の変更に影響を与えずに取り消せます。
git status: 変更されたものとステージ済みのもの
add や commit の前には git status を確認すると安心です。これは最も安全なGitコマンドで、何も変更しません。
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/auth.js
Changes not staged for commit:
(use "git add <file>..." to include in what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/profile.js
- Changes to be committed: すでにインデックスにあり、コミットに入ります。
- Changes not staged for commit: 変更はあるが、まだ追加されていません。
- Untracked files: Gitがまだ認識していない新しいファイルです。
ファイルが多い場合は短い形式が便利です。
$ git status -s
M src/auth.js
M README.md
?? src/profile.js
1列目はインデックスの状態、2列目は作業ディレクトリの状態を表します。左の M は「変更されて追加済み」、右の M は「変更されたが未追加」、?? は「追跡されていないファイル」を意味します。
git add: インデックスに変更を追加する
| コマンド | 追加される内容 |
|---|---|
git add file.js | 特定のファイル |
git add src/ | フォルダ内の変更すべて |
git add . | 現在のフォルダ以下のすべての変更(新規・削除されたファイルを含む) |
git add -A | リポジトリ全体のすべての変更 |
git add -u | 追跡済みのファイルのみ。新規ファイルは対象外 |
git add -p | 対話的に、部分(hunk)ごとに選択 |
git add '*.css' | パターンに一致するファイル |
git add の別名は git stage です。同じコマンドなので、「ステージする」という言い方が生まれました。
git add -p: ファイルの一部だけを追加する
あまり知られていませんが、非常に便利な方法です。1つのファイルに別々の2つの変更が混ざっている場合、git add -p は部分ごとに内容を表示し、追加するかどうかを尋ねます。
$ git add -p src/auth.js
@@ -12,7 +12,7 @@ function login(user) {
- if (user.password = input) {
+ if (user.password === input) {
(1/2) Stage this hunk [y,n,q,a,d,s,e,?]?
y は追加、n はスキップ、s は部分をさらに細かく分割、q は終了です。こうすると、1つのファイルから2つの整理されたコミットを作れます。
コミットに何が入るかを確認する方法
git diff # まだインデックスに追加していない変更
git diff --staged # インデックスにあり、コミットに入る変更
よくある混乱があります。git add の後で通常の git diff を実行しても何も表示されません。変更が消えたわけではなく、インデックスに入っているだけです。その場合は git diff --staged(別名 --cached)を確認してください。
ファイルをインデックスから外す方法
git restore --staged file.js
ファイル内の変更はそのまま残り、コミットの対象から外れるだけです。古いバージョンのGitでは git reset HEAD file.js を使っていました。現在も動作します。
git commit: 変更を記録する
git commit -m "ログイン時のパスワードチェックを修正"
[main 3f2a9c1] ログイン時のパスワードチェックを修正
1 file changed, 1 insertion(+), 1 deletion(-)
出力にはブランチ名、新しいコミットの短いハッシュ、統計情報が表示されます。コミットに入るのはインデックスの内容だけです。git add の後でファイルをさらに編集した場合、その変更は改めて追加するまでコミットに入りません。
git commit の便利なオプション
| コマンド | 動作 |
|---|---|
git commit | 複数行のメッセージを書くためのエディタを開く |
git commit -m "..." | コマンドの中でメッセージを指定する |
git commit -am "..." | 追跡済みで変更されたファイルをすべて追加してコミット。新規ファイルは含まれない |
git commit -v | メッセージを書いている間、エディタに差分を表示する |
git commit --amend | 直前のコミットを書き換える |
git commit --allow-empty -m "..." | 空のコミット。たとえばCIを再実行するために使う |
デフォルトのエディタが Vim で抜け出せなくなった場合は、メッセージを入力して Esc を押し、:wq と入力して Enter を押してください。エディタは git config --global core.editor "code --wait" や nano に変更できます。
良いコミットメッセージの書き方
メッセージは、半年後にバグの原因を探すときに読まれます。「今日何をしたか」ではなく、「何を、なぜ変えたか」に答えるべきです。
- 1行目は50〜72文字程度で、変更の要点を書く。
- 命令形、または簡潔な表現を使う: 「Add email validation」「Fix login redirect」。
- 必要に応じて、空行の後に詳細を書く: なぜそうしたのか、どんな制約があるのか。
- 1つのコミットには1つの論理的な変更だけを含める。
悪い例: fix、修正、wip、asdf。良い例: 空のカートで商品一覧が落ちる不具合を修正。多くのチームは Conventional Commits の形式を使います: feat: ...、fix: ...、docs: ...。
git commit --amend: 直前のコミットを修正する
メッセージの誤字や、追加し忘れたファイルのために新しいコミットを作る必要はありません。そのためにamendがあります。
メッセージを修正する
git commit --amend -m "ログイン時のパスワードチェックを修正"
追加し忘れたファイルを加える
git add forgotten.js
git commit --amend --no-edit
--no-edit を付けると、既存のメッセージがそのまま残ります。
amendについての重要な注意
--amend はコミットを「編集」するのではなく、別のハッシュを持つ新しいコミットを作って古いものと置き換えます。そのコミットがまだ自分だけのものであれば安全です。すでに git push している場合、amend後の通常の push は拒否されるので、git push --force-with-lease が必要になります。他の人が作業している共有ブランチでは、このような操作は避けてください。他の人が積み上げている履歴を書き換えることになるからです。
amend を誤って実行した場合、古いコミットは git reflog で見つけられ、git reset --soft HEAD@{1} で元に戻せます。
よくあるエラー
「nothing to commit, working tree clean」
変更がありません。すでにすべてコミット済みか、別のフォルダでファイルを編集しています。git status とプロジェクトのパスを確認してください。
「no changes added to commit」
ファイルは変更されていますが、インデックスに追加されていません。git add を実行するか、追跡済みファイルに対して git commit -am を使ってください。
「Please tell me who you are」
名前とメールアドレスが設定されていません。一度だけ git config --global user.name "名前" と git config --global user.email "メール" を実行してください。
不要なものをコミットしてしまった
まだpushしていないなら、変更をインデックスに残したままコミットを取り消せます: git reset --soft HEAD~1。その後、不要なものを git restore --staged で外し、コミットをやり直してください。コミットにシークレット(パスワードやAPIキーなど)が含まれていた場合は、漏洩したものとみなし、キーを再発行してください。
git add . で不要なファイルが追加された
事前に .gitignore を設定し、コミット前に git status を確認してください。すでに追跡されているファイルは、git rm --cached ファイル を実行した後にのみ追跡対象から外れます。
コミットの実体
コミットに保存されるのは、前のバージョンとの差分ではなく、プロジェクト全体のスナップショットです。内容は、ファイルツリーへの参照、親コミットへの参照、作成者、日時、メッセージです。変更されていないファイルはコピーされず、Gitは既に保存済みのオブジェクトを参照します。そのため、履歴は少ない容量で済みます。
コミットのハッシュ(例: 3f2a9c1)は、これらすべての内容から計算されます。メッセージの1文字を変えるだけで、ハッシュは変わります。だからこそ --amend や rebase は古いコミットを直すのではなく、新しいコミットを作ります。そして、書き換えられた履歴は、同僚が持っているコピーと衝突するのです。
履歴を短く表示するには次のようにします。
git log --oneline --graph -10
各行が1つのコミットです。差分を開かなくても履歴のメッセージから内容が分かるなら、正しく作業できています。
毎日の作業の流れ
git status # 何が変わったか
git diff # 変更を確認
git add -p # 必要なものを選ぶ
git diff --staged # コミットに入る内容を確認
git commit -m "分かりやすい説明"
git log --oneline -5 # コミットが記録されたか確認
git push # サーバーに送る
コミットは変更をローカルにだけ保存します。GitHubなどのサーバーに反映させるには git push が必要です。
まとめ
git add は次のスナップショットをインデックスに集め、git status と git diff --staged はその内容を示し、git commit はスナップショットを履歴に記録します。小さく論理的なコミットを、分かりやすいメッセージとともに作りましょう。直前のコミットへの小さな修正には、pushする前であれば git commit --amend を使います。インデックスとadd→commitの2段階の仕組みは、Gitコースで実践的に扱っています。
よくある質問
どのくらいの頻度でコミットすべきですか?
1つの論理的な変更が完了し、プロジェクトが動く状態になったときです。1日の終わりに巨大なコミットを1つ作るより、小さなコミットを多く作るほうがよいです。
Gitに git save コマンドはありますか?
ありません。保存は git add と git commit で行います。作業途中の変更を一時的に退避したい場合は git stash を使います。
コミットの内容を確認するにはどうすればよいですか?
git show ハッシュ で、作成者、メッセージ、そのコミットのすべての変更を表示できます。ハッシュを省略すると、最新のコミットが表示されます。
git add . と git add -A の違いは何ですか?
新しい Git では、どちらも新規、変更、削除されたファイルを追加します。違いは対象範囲です。git add . は現在のフォルダとその下を対象にし、git add -A はリポジトリ全体を対象にします。
最新ではない、より古いコミットを変更できますか?
はい、対話的な rebase を使います: git rebase -i 親のハッシュ を実行し、対象のコミットを edit または reword に設定します。共有ブランチにまだ送っていないコミットに対してのみ行ってください。