Git入門:init・add・commitと.gitignoreを実際に動かして学ぶ

最初のコミットまで5分。作業ツリー・ステージング・リポジトリの違い、status/diff/log の読み方、.gitignore が効かない理由、パスワードをコミットしたときの本当の対処を実際の出力で解説。

シリーズ:Git 講座 1/6(初級)。 Git を触ったことがない人、社内 Wiki のコマンドをコピペして何となく使っている人向けです。読み終えるとリポジトリを作り、コミットし、status・diff・log を読み、秘密情報やビルド生成物を履歴に入れない運用ができるようになります。

配属初日に「とりあえずリポジトリにプッシュしておいて」と言われて固まった新人エンジニアのための記事です。初心者がつまずく原因の多くは、自分の変更がいまどこにあるのか(ディスク上だけか、ステージ済みか、履歴に保存済みか)が分からないことです。git status からそれを読み取れるようになれば、Git の混乱の大半は消えます。

以下はすべて Git 2.54.0(Apple Git-157)、Apple Silicon Mac(M1 Pro)上の使い捨てフォルダで、デモ用の ID(demo <demo@example.com>)を使って実行しました。出力は表示されたままを貼り付け、ホームフォルダのパスだけ ~ に置き換えています。

変更が置かれる3つの場所

Git のエラーメッセージには必ずと言っていいほど次の3つの言葉が出てきます。最初に覚えてしまいましょう。

  • 作業ツリー(working tree):プロジェクトフォルダにある普通のファイル。あなたが編集するのはここです。
  • ステージングエリア(インデックスとも呼ぶ):次のコミットに入る内容の一覧。git add はファイルの今の中身をここへ写します。
  • リポジトリ:隠しフォルダ .git。すべてのコミットが保存されます。コミットとはステージングエリアのスナップショットに、作者・日時・メッセージ・直前のコミットへの参照を付けたものです。
図:ファイルを編集する作業ツリーは git add でステージングエリアへ、ステージングエリアは git commit でリポジトリ(.git フォルダ)へ進む。git diff は作業ツリーとステージングエリアを、git diff --staged はステージングエリアと最後のコミットを比べる。
変更が置かれる3つの場所。git add がファイルをステージングエリアへ写し、git commit がそれをスナップショットとしてリポジトリに保存する。

意外に思われるのがステージングエリアです。これがあるおかげで、変更の一部だけをコミットできます。たとえばバグ修正と誤字修正を別々のコミットに分けられます。

5分で最初のコミット

ターミナルを開いて1行ずつ実行します。まずはほぼ全員がやる失敗から。リポジトリの外で Git を実行してみます。

git status
fatal: not a git repository (or any of the parent directories): .git

fatal: は Git が処理を中止したという意味で、終了コードは128でした。リポジトリを作る(またはその中に cd する)と直ります。

mkdir shop && cd shop
git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: will change to "main" in Git 3.0. To configure the initial branch name
hint: to use in all of your new repositories, which will suppress this warning,
hint: call:
hint:
hint:   git config --global init.defaultBranch <name>
...
Initialized empty Git repository in ~/demo/shop/.git/

hint: の行は助言で、エラーではありません。多くのチームは main を使っており、git init -b main とすればそのリポジトリだけ main になります。ファイルを作り、git status で3つの場所を移動する様子を見ます。

printf 'print("hello, shop")\n' > app.py
git status
On branch master

No commits yet

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

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

Untracked(未追跡)は「見えているが一度も保存していない」という意味です。ステージしてコミットします。

git add app.py
git commit -m "Add app.py"
git log
[master (root-commit) 8d02147] Add app.py
 1 file changed, 1 insertion(+)
 create mode 100644 app.py
commit 8d02147eba3b707bfb452bed66288443ef8f808b
Author: demo <demo@example.com>
Date:   Thu Nov 12 01:00:00 2026 +0000

    Add app.py

8d02147… はコミットの ID で、中身から計算したハッシュ値です。先頭7文字で指定できます。root-commit は親のない最初のコミットという意味です。

名前とメールの設定を忘れると

本番の最初のコミットより前に、git config --global user.name "名前" と git config --global user.email "<会社のメールアドレス>" を一度だけ設定します。macOS で何も設定していない状態でコミットしたところ、エラーにならず、Mac のアカウント名と ホスト名から作った .local で終わるアドレスを勝手に組み立ててコミットし、その後で Your name and email address were configured automatically… と表示しました。この中途半端な名前はもうコミットの中に入っています。作業を始める前に、チームで決められたアドレスを設定しましょう。

status・diff・log の読み方

ファイルを書き換えて、Git に何が起きたか聞いてみます。

printf 'print("hello, shop")\nprint("total:", 3 * 1200)\n' > app.py
git status
git diff
On branch master
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:   app.py

no changes added to commit (use "git add" and/or "git commit -a")
diff --git a/app.py b/app.py
index d0b0cc7..fb97e2d 100644
--- a/app.py
+++ b/app.py
@@ -1 +1,2 @@
 print("hello, shop")
+print("total:", 3 * 1200)

+ で始まる行が追加、- が削除です。ここでステージしてから2種類の diff を実行します。

git add app.py
git diff            # 何も表示されない
git diff --staged   # 変更が表示される

この講座でいちばん役に立つ事実がこれです。git diff は「まだステージしていない変更」、git diff --staged は「次のコミットに入る変更」を表示します。 git add の直後に git diff が空になるので「変更が消えた」と焦る人が多いのですが、消えたのではなくステージングエリアに移っただけです。

git commit -m "Print total"
git log --oneline
292126a Print total
8d02147 Add app.py
知りたいこと コマンド
どのファイルが変わり、それぞれどこにあるか git status(短縮形は -s)
ステージ前の変更を行単位で git diff
次のコミットに入る内容 git diff --staged
履歴を1行ずつ git log --oneline
各コミットで変わったファイル git log --stat
過去のある時点のファイル git show <コミット>:<パス>

.gitignore でゴミと秘密情報を締め出す

ビルド生成物、キャッシュ、パスワード入りの .env などは決してコミットしてはいけません。わざと典型的な失敗をしてみます。.env と Python のキャッシュフォルダを作り、「全部追加」の git add . を実行します。

printf 'DB_PASSWORD=demo-secret-123\n' > .env
mkdir __pycache__; printf 'x' > __pycache__/app.cpython-39.pyc
git add .
git status --short
git commit -m "Add config"
A  .env
A  __pycache__/app.cpython-39.pyc
[master 0d2cbec] Add config
 2 files changed, 2 insertions(+)
 create mode 100644 .env
 create mode 100644 __pycache__/app.cpython-39.pyc

多くの人が最初に試す「修正」は .gitignore を書くことです。

printf '.env\n__pycache__/\n' > .gitignore
git status --short
git check-ignore -v .env; echo "exit=$?"
?? .gitignore
exit=1

git check-ignore は何も表示せず終了コード1、つまり「無視されていない」と答えました。.gitignore は未追跡のファイルにしか効きません。 .env はすでに追跡中なので、Git は見張り続けます。--no-index を付けるとルール自体は一致している(.gitignore:1:.env .env)と分かりますが、追跡中のファイルには使われません。ディスク上のファイルは残したまま追跡だけをやめるには次のようにします。

git rm --cached .env __pycache__/app.cpython-39.pyc
git add .gitignore
git commit -m "Stop tracking .env and pycache"
rm '.env'
rm '__pycache__/app.cpython-39.pyc'
[master ba6ab87] Stop tracking .env and pycache
 3 files changed, 2 insertions(+), 2 deletions(-)
 delete mode 100644 .env
 create mode 100644 .gitignore
 delete mode 100644 __pycache__/app.cpython-39.pyc

ls -a で見ると .env はフォルダに残っているので、手元の設定はそのまま動きます。

よくある失敗:「削除したから秘密情報は消えた」

実際に起きること。 コミット 0d2cbec は残っていて、ファイルもその中にあります。

git show HEAD~1:.env
git log --oneline -- .env
DB_PASSWORD=demo-secret-123
ba6ab87 Stop tracking .env and pycache
0d2cbec Add config

リポジトリを clone した人なら誰でも同じコマンドで読めます。ファイルを削除しても「そのファイルがない新しいスナップショット」が増えるだけで、履歴は変わりません。

ではまだプッシュしていないコミットならどうでしょう。よく使われる git commit --amend(直前のコミットを作り直す)を試しました。

git rm --cached .env
printf '.env\n' > .gitignore
git add .gitignore
git commit --amend -m "Add settings (without .env)"
git reflog
git show 'HEAD@{1}:.env'
75ed8d6 HEAD@{0}: commit (amend): Add settings (without .env)
88dcb5b HEAD@{1}: commit: Add settings
7b51afc HEAD@{2}: commit (initial): Add app.py
DB_PASSWORD=demo-secret-123

ブランチからは秘密情報が消えましたが、古いコミットは手元の .git に残っていて、reflog(HEAD がどこを指していたかを記録する Git のローカル日記)からたどれます。どのブランチからも指されていないのでプッシュでは送られず、いずれ期限切れで消えます。reflog は講座5で詳しく扱います。

対処の順番:

  1. 漏えいしたものとして扱い、パスワードを変える・キーを無効化するのが最初です。一度プッシュしたら、本当に身を守れるのはこれだけです。
  2. プッシュ前なら:git rm --cached、.gitignore に追加、git commit --amend。
  3. プッシュ済みなら:履歴から消すには共有履歴の書き換えが必要で(git filter-repo などの別ツール。Git には同梱されておらず、ここでは実行していません)、チーム全員が clone し直すことになります。それでも手順1が先です。
  4. 予防:最初の git add . より前に .gitignore を作る。慣れるまでは git add <ファイル名> で個別に追加する。

実測:ビルドフォルダをうっかり git add . すると

現場でよくある事故が、大きなビルドフォルダがあるプロジェクトでの git add . です。コミット済みのファイルが1つだけのリポジトリに、2万個の小さなファイル(1個16〜20バイト、build/d0…d199/f0…f99.o)を作り、Apple Silicon Mac(M1 Pro)、Git 2.54.0 で測りました。時間は各7回の中央値、git add . は1回だけ実行しています。

状態 git status 中央値 .git のディスク使用量
build/ が未追跡(追加前) 16.6 ms 112 KB
git add . の後(所要 15.2 秒) 55.0 ms 82,284 KB
git reset でステージから外した後 — 80,116 KB
git gc --prune=now の後 — 128 KB
build/ を .gitignore に追加 13.3 ms —
棒グラフ:.git のサイズは追加前112KB、2万ファイルを git add した後82,284KB、git reset 後80,116KB、git gc --prune=now 後128KB。
ビルド生成物2万ファイルをうっかり git add . したときの .git のディスク使用量。git reset で外してもほぼ残り、git gc --prune=now で初めて消えた。Git 2.54.0、Apple M1 Pro、APFS。

教訓は2つです。1つ目、git add はその場で全ファイルをオブジェクトとして .git に書き込みます。git reset で外してもオブジェクトは消えず、ガベージコレクションまで参照されないデータとして残ります。2つ目、表の数値はディスク使用量です。APFS では小さなオブジェクトも1個4KiBのブロックを使うので、20バイト未満のオブジェクト2万個が約80MBになりました。大きなバイナリがある実プロジェクトではもっと深刻で、コミットまでしてしまえば履歴に永久に残ります。

正直な限界とバージョンの注意

  • 実行環境は Git 2.54.0(Apple Git-157)、macOS、Apple Silicon(M1 Pro)。master/Git 3.0 のヒントや git config set advice… という文言はこのバージョンのもので、古い Git はもっと短いヒントを出します。
  • git status が案内する git restore は git switch と同じく Git 2.23(2019年)で追加されました。古い解説では同じことを git checkout -- <ファイル> で行っています。
  • macOS が ID を自動で組み立てる挙動は、グローバル設定のないこの Mac で見たものです。環境によっては Author identity unknown でコミット自体が拒否されます。
  • 時間はディスクやファイル数で変わる、1台のマシンでの数値でありベンチマークではありません。
  • 履歴を書き換えるツールは説明のみで、実行していません。

練習問題

  1. 新しいリポジトリで a.txt と b.txt を作り、a.txt だけをコミットしてください。b.txt が未追跡のままだと確認するコマンドは?
  2. app.py を編集して git add app.py しました。git diff が何も出しません。変更を表示するコマンドは?
  3. notes.log はコミット済みです。.gitignore に *.log を書いたのに git status で変更ありと表示されます。ディスクから消さずに直してください。

解答

  1. git add a.txt && git commit -m "Add a" の後に git status(git status -s なら ?? b.txt と出ます)。
  2. git diff --staged。ステージ済みの変更は素の git diff には出ません。
  3. git rm --cached notes.log の後 git commit -m "Stop tracking notes.log"。git check-ignore -v notes.log が一致したルールを表示すれば成功です。

次の講座: Git 講座2:ブランチ・マージ・コンフリクト ・シリーズ内:リモートと push・pull・fetch ・新人向け学習ロードマップに戻る。コマンドライン自体が初めてならシェルの基本から。

よくある質問

.gitignore に書いたのに、ファイルの変更がまだ表示されます。なぜですか?

.gitignore は「まだ追跡していないファイル」にしか効きません。一度コミットしたファイルは追跡され続けます。git rm --cached <ファイル> で追跡だけを外し(ディスク上には残ります)、コミットしてください。追跡中のファイルに git check-ignore -v を実行すると何も表示されないので、すぐ気づけます。

パスワードをコミットしてしまいました。次のコミットで削除すれば大丈夫ですか?

大丈夫ではありません。古いコミットにはそのまま残っており、検証では git show HEAD~1:.env でパスワードが表示されました。まず漏えいしたものとして扱い、パスワードやキーを変更します。プッシュ前なら git commit --amend で枝からは消せますが、それでも古いコミットは reflog から期限切れまで参照できます。

git diff と git diff --staged の違いは何ですか?

git diff は作業ツリーとステージングエリアの差(まだ add していない変更)、git diff --staged はステージングエリアと最後のコミットの差(次のコミットに入る内容)を表示します。検証では git add の直後、git diff は何も出さず、git diff --staged に変更が出ました。

  • #git
  • #初級
  • #バージョン管理
  • #gitignore
  • #ステージング

t.laboratory

実際に作って運用しているアプリ・サーバー・サービスで起きた問題と、その解決に使ったコードを記録する技術ブログです。

運営者情報 · アプリ

↑