git add・commit・pushの違い:保存して送るまでの流れ

公開 更新 書いた人 大林 諒

Gitを触り始めて最初に混ざるのが、git add、git commit、git push の3つです。どれも「保存」に見えるのに、順番を飛ばすとGitHubに何も出ません。

この記事では、サンプル料理教室の料金表を直して送るまでを、実際に動かした表示つきで順に読みます。読み終わると、どのコマンドが何をどこへ動かしたのかを、画面の英語から自分で判断できるようになります。

この記事の内容
  1. 先に答えると
  2. 変更を置く場所は4か所ある
  3. 料金表を直して送るまでを動かす
  4. コミットの単位とメッセージ
  5. git add . の危険と、入れる前に見る習慣
  6. pushと本番反映は別の話
  7. できないことと、その代わり
  8. よく詰まるところ
  9. 次に読む

先に答えると

3つのコマンドは、変更を置く場所を1つずつ先へ動かします。git add は次のコミットに入れる変更を選ぶ操作、git commit は選んだ変更を手元の履歴に記録する操作、git push はその履歴をGitHubへ送る操作です。commitは自分のMacの中のセーブで、pushはそれをネットへ送ることです。Windowsでも同じです。

  • git add だけでは履歴に残りません。
  • git commit だけではGitHubに出ません。手元のパソコンの中で完結します。
  • git push で送られるのはコミットだけです。コミットしていない編集は送られません。
  • git push をしても、本番サイトは変わりません。変わるのは自動で出す設定があるときだけです。
  • 迷ったら git status を打ちます。今どこまで進んでいるかを、Git自身が教えてくれます。

変更を置く場所は4か所ある

混ざる原因は、ファイルの置き場を「手元」と「GitHub」の2か所だと考えてしまうことです。Gitでは4か所に分けて考えます。

場所何があるかここへ動かすコマンド中身を見るコマンド
作業中のファイルエディタで編集している今のファイルエディタで保存git status、git diff
ステージ次のコミットに入れると選んだ変更git addgit status、git diff --staged
手元の履歴コミットの積み重ねgit commitgit log --oneline
GitHubチームと共有している履歴git pushGitHubの画面

ステージは、料理を客席へ運ぶ前に載せるお盆のようなものです。調理台に料金表と電話番号の修正が両方あっても、お盆には料金表だけを載せられます。1回のコミットに入れるものを、ファイル単位で選ぶための場所です。

コミットは、ある時点の変更に名前を付けて履歴に残す記録です。リポジトリ(ファイルと履歴をまとめて入れておく箱)の中に積み重なります。エディタの保存とコミットは別物で、エディタで保存しただけでは履歴には何も残りません。

4つの場所と、git add・git commit・git push の関係
4つの場所と、git add・git commit・git push の関係

料金表を直して送るまでを動かす

ここから先の表示は、すべて実際に動かしたものです。GitHubの代わりに、手元のフォルダに練習用のリモート(送り先のリポジトリ)を作って試しました。そのため送り先の場所がフォルダの形で出ます。本物のGitHubなら https://github.com/sample-cooking/website.git のようなURLになります。ハッシュ(3dae8b9 のようなコミットの番号)は毎回違う値になります。

リポジトリを作って最初の版を登録する

ホームページのフォルダで git init を打つと、そのフォルダがリポジトリになります。-b main は最初の枝(ブランチ)の名前を main にする指定です。表示の途中の長いフォルダ名は … に縮めました。

$ git init -b main
Initialized empty Git repository in /private/tmp/…/gitlab-09/website/.git/

index.html、menu.html、style.css を置いて状態を見ます。

$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	index.html
	menu.html
	style.css

nothing added to commit but untracked files present (use "git add" to track)

「Untracked files」は、Gitがまだ管理していないファイルの一覧です。3つとも選んで、最初のコミットにします。

$ git add index.html menu.html style.css

$ git commit -m "ホームページの最初の版を登録"
[main (root-commit) 3dae8b9] ホームページの最初の版を登録
 3 files changed, 6 insertions(+)
 create mode 100644 index.html
 create mode 100644 menu.html
 create mode 100644 style.css

git add は成功しても何も表示しません。止まったように見えても、それで正常です。

料金表を編集して、差分を読む

menu.html の体験レッスンを3,000円から3,500円に変えて、エディタで保存します。

$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   menu.html

no changes added to commit (use "git add" and/or "git commit -a")

「Changes not staged for commit」は、変更はあるけれどステージにはまだ載っていない、という意味です。何を変えたかは git diff で見ます。

$ git diff
diff --git a/menu.html b/menu.html
index 5bc3b63..6ffeb1e 100644
--- a/menu.html
+++ b/menu.html
@@ -1,3 +1,3 @@
 <h1>料金表</h1>
-<p>体験レッスン 3,000円</p>
+<p>体験レッスン 3,500円</p>
 <p>月謝 12,000円</p>

差分で読むところは多くありません。

表示読み方
- で始まる行消えた行(変更前)
+ で始まる行増えた行(変更後)
記号のない行変わっていない前後の行。場所をつかむために出ている
@@ -1,3 +1,3 @@変更がファイルの何行目あたりか
--- a/ と +++ b/比べている2つのファイル。a が前で b が後

1行だけ直しても、- と + の2行が出ます。Gitは行単位で変更を見るので、「古い行を消して新しい行を足した」という形で表します。

git diff の読み方
git diff の読み方

addで選ぶ

$ git add menu.html

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   menu.html

見出しが「Changes to be committed」に変わりました。次のコミットに入る、という意味です。ここでもう一度 git diff を打つと、何も出ません。

変更が消えたのではありません。git diff は作業中のファイルとステージの差を見るコマンドで、両者が同じになったので差がないだけです。ステージと直近のコミットの差は git diff --staged で見ます。先ほどと同じ - と + の表示が出るので、コミットの直前に打つと、これから記録する中身を確かめられます。

commitで手元の履歴に残す

$ git commit -m "料金表を修正"
[main 0eb3823] 料金表を修正
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git log --oneline
0eb3823 (HEAD -> main) 料金表を修正
3dae8b9 ホームページの最初の版を登録

$ git status
On branch main
nothing to commit, working tree clean

git log --oneline は履歴を1件1行で出します。上が新しいコミットです。(HEAD -> main) は今いる場所を指す印で、main の先頭にいると読みます。「nothing to commit, working tree clean」は、作業中のファイルと履歴に差がない状態です。ここまでの操作は、GitHubとは一度も通信していません。

pushでGitHubへ送る

送り先を origin という名前で登録します。origin は慣例で使う名前です。本物のGitHubなら、最後の部分がURLになります。

$ git remote add origin ../remote/website.git

$ git push -u origin main
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 11 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (8/8), 867 bytes | 867.00 KiB/s, done.
Total 8 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To ../remote/website.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

前半の数行は進み具合で、読まなくて構いません。見るのは To の行から下です。main -> main は、手元の main を送り先の main へ送ったという意味です。-u は「この枝の送り先はここ」と覚えさせる指定で、最初の1回だけ付けます。次からは git push だけで通ります。

$ git log --oneline
0eb3823 (HEAD -> main, origin/main) 料金表を修正
3dae8b9 ホームページの最初の版を登録

origin/main が手元の main と同じコミットに並びました。GitHubと手元がそろった印です。次の変更からは、編集、git add、git commit、git push の4つを繰り返すだけです。

pushしたコミットがGitHubの画面に出ているところ
pushしたコミットがGitHubの画面に出ているところ

コミットの単位とメッセージ

1つのコミットには、1つの理由で変えたものだけを入れます。料金表の修正と電話番号の修正が同時に手元にあるなら、2回に分けます。あとで料金表だけ元に戻したくなったとき、電話番号まで一緒に戻らずに済むからです。

メッセージは、半年後の自分が履歴から探せる一言にします。Git公式は1行目を50文字以内の短い要約にすることを勧めています。これは英語を前提にした目安で、日本語ならもっと短く収まります。

悪い例良い例理由
修正料金表を修正何のファイルの話か分かる
いろいろ直した問い合わせの電話番号を修正変えた対象が1つに絞れている
0612作業分夏の教室を追加日付はGitが自動で記録している

良いメッセージは、そのまま検索の手がかりになります。

$ git log --oneline --grep="料金"
a6a2355 体験レッスンの料金を修正
0eb3823 料金表を修正

$ git log --oneline -- index.html
603f419 夏の教室を追加
e58e60e 問い合わせの電話番号を修正
3dae8b9 ホームページの最初の版を登録

--grep はメッセージの言葉で探し、-- の後ろにファイル名を書くとそのファイルを変えたコミットだけが出ます。

git add . の危険と、入れる前に見る習慣

git add . は、今のフォルダの変更をまとめてステージに載せるコマンドです。楽ですが、入れてはいけないファイルも一緒に載ります。パスワードを書いた .env がある状態で動かすと、こうなります。

$ git add .

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   .env
	modified:   style.css

.env が「new file」として次のコミットに入る状態です。コミットの前なら、まだ手元の話で済みます。ステージから下ろします。

$ git restore --staged .env

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   style.css

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.env

実際に運用していて効くのは、git add のあとコミットの前に、毎回 git status を1回打つことです。ファイル名の列を読むだけなので数秒で済みます。慣れるまでは git add . を使わず、git add menu.html のようにファイル名で指定すれば、そもそも混ざりません。

根本の対策は、.gitignore というファイルに書いて、Gitの管理から最初から外すことです。まだ管理していない .env なら、.env と書いた .gitignore を置くと、git status の一覧からも git add . の対象からも消えます。コミット済みなら git rm --cached .env も要ります。pushまで終えてしまったら、話は別です。「APIキーやパスワードをGitHubにpushしてしまったときの対応」に進んでください。

コミットの前に、git status で混ざったファイルを見つける
コミットの前に、git status で混ざったファイルを見つける

pushと本番反映は別の話

deploy(デプロイ)は、変更を本番のサイトやサーバーへ出す作業です。pushはGitHubへ送るだけなので、deployとは別の操作です。料金表をpushしても、サンプル料理教室のホームページを見に来た人の画面は、そのままでは変わりません。

変わるのは、pushをきっかけに本番へ出す設定があるときです。たとえばGitHub Pagesで枝から公開する設定なら、その枝にpushされた変更が公開されます。GitHub Actionsで出すように組んである場合もあります。

そういう設定があるリポジトリでは、main へのpushがそのまま本番の変更になります。pushしたのにサイトが変わらないときも、変わって驚いたときも、自分のリポジトリがどちらなのかを外注エンジニアBのような担当者に確かめるのが近道です。

できないことと、その代わり

できないこと代わりにどうするか
ステージしただけの変更をpushする先にコミットする。送られるのはコミットだけ
コミットしていない編集をGitの履歴から取り戻す区切りごとにコミットする。履歴に残るのはコミットした時点だけ
空のフォルダを登録する中にファイルを置く。空の images/ は git add しても git status に出ない
100MiBを超えるファイルをGitHubへ送るGit LFSを使うか、Googleドライブなど別の置き場へ。GitHubは50MiB超で警告、100MiB超でブロックする
pushした内容を履歴ごと消す通常のpushでは消えない。戻し方は「Gitで元に戻す:revert・reset・restoreの違いと使い分け」へ

よく詰まるところ

nothing to commit と言われる

症状は、コミットしようとするとこう返ってくることです。

$ git commit -m "料金表を修正"
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

原因は、記録する変更が1つもないことです。すでにコミット済みか、エディタで保存していないか、別のフォルダを開いています。エディタの保存を確かめて、git status を打ちます。

no changes added to commit と言われる

ファイルは編集したのに、ステージに載せていない状態です。

$ git commit -m "月謝を修正"
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   menu.html

no changes added to commit (use "git add" and/or "git commit -a")

直し方は git add menu.html をしてから、もう一度コミットします。

addしたのに、同じファイルが2か所に出る

git add のあとにまた編集すると、menu.html が両方の一覧に出ます。

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   menu.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   menu.html

git add は、打った時点の中身だけをステージに載せます。そのあとの編集は載っていません。最新の中身でコミットしたいなら、もう一度 git add menu.html を打ちます。

コミットしたのにGitHubに出ない

原因はpushしていないことです。git status が教えてくれます。

$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

$ git log --oneline
e58e60e (HEAD -> main) 問い合わせの電話番号を修正
0eb3823 (origin/main) 料金表を修正
3dae8b9 ホームページの最初の版を登録

「ahead of ‘origin/main’ by 1 commit」は、手元の履歴がGitHubより1つ先に進んでいるという意味です。git log でも origin/main が1つ下に残っています。git push を打てば2つがそろいます。

別の枝でコミットしたまま main でpushしている場合もあります。そのときは送るものがないので、こう出るだけです。

$ git push
Everything up-to-date

$ git branch
  add-summer-class
* main

* が今いる枝です。枝の話は「git branchとmergeの使い方:枝を分けて合流する、GitHub Flowまで」で扱います。

no upstream branch と言われる

送り先を覚えさせていない枝で、git push だけを打ったときの表示です。

$ git push
fatal: The current branch main has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin main

To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.

表示のとおりに、git push -u origin main を打ちます。-u は --set-upstream の短い書き方です。新しい枝を作るたびに、最初の1回だけ出ます。

rejected と言われる

外注エンジニアBが先にpushしていると、代表のpushは断られます。

$ git push
To ../remote/website.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '../remote/website.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

送り先に、手元にないコミットがあるという意味です。そのまま上書きするとBの変更が消えるので、Gitが止めています。先に受け取ってから送ります。お互いがコミットしている状態では git pull だけだと止まるので、--no-rebase を付けて合流させました。合流のメッセージを書くエディタが開いたら、既定の文言のまま保存して閉じます。

$ git pull --no-rebase
Merge made by the 'ort' strategy.
 index.html | 1 +
 1 file changed, 1 insertion(+)

このあとの git push は通りました。受け取る側の詳しい話は「git clone・pull・fetchの違い:受け取る操作の使い分け」にあります。git push --force で押し切る方法もありますが、Bの変更を消す操作なので、ここでは使いません。

pushが断られたときは、受け取ってから送り直す
pushが断られたときは、受け取ってから送り直す

git commit だけを打ったら見慣れない画面が開いた

-m を付けないと、メッセージを書くためのエディタが開きます。1行目にメッセージを書いて保存して閉じれば、コミットされます。何も書かずに閉じると、コミットは中止されます。

$ git commit
Aborting commit due to empty commit message.

中止されただけで、編集もステージも消えていません。-m 付きで打ち直せば済みます。

次に読む

受け取る側の操作は「git clone・pull・fetchの違い:受け取る操作の使い分け」へ。間違えてコミットしたときの戻し方は「Gitで元に戻す:revert・reset・restoreの違いと使い分け」にあります。

.env を入れない設定は「.gitignoreの書き方と、リポジトリに入れてはいけないファイル」、人と一緒に作業する話は「git branchとmergeの使い方:枝を分けて合流する、GitHub Flowまで」に続きます。

コマンドをまとめて引きたいときは「Gitコマンド一覧:場面ごとによく使うものと、危ないもの」へ。

あわせて読む