Git入門:バージョン管理のきほん

リポジトリを作る(git init)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • git init が実際に何を作り、何を作らないのか
  • .git ディレクトリの中に何が入っているのか
  • ワークツリー・ステージ・リポジトリという 3 つの場所の役割

練習用のフォルダを作る

まず作業する場所を用意します。ホームディレクトリの下に git-practice というフォルダを作ります。

ターミナル

cd ~ mkdir git-practice cd git-practice

Windows の Git Bash でもこのコマンドはそのまま動きます。cd ~ はホームディレクトリへの移動、mkdir はフォルダの作成、cd はそのフォルダへの移動です。

今どこにいるかは pwd で確かめられます。

ターミナル

pwd

プレーンテキスト

/Users/taro/git-practice

Windows の Git Bash では /c/Users/taro/git-practice のような表示になります。

フォルダ名やその上の階層に日本語やスペースが入っていると、後々コマンドの書き方で悩みます。学習中は英数字とハイフンだけのパスにしておくと余計なつまずきが減ります。デスクトップの下に作る場合も、フォルダ名は英字にします。

git init を実行する

このフォルダを Git の管理下に置きます。

ターミナル

git init

プレーンテキスト

Initialized empty Git repository in /Users/taro/git-practice/.git/

これで完了です。何かをダウンロードしたり、サーバーに接続したりはしていません。この操作はすべてローカルで完結し、一瞬で終わります。

メッセージが言っているのは「空の Git リポジトリを .git の中に作った」ということです。リポジトリとは、履歴とその管理情報が入った保管場所を指します。Git が作ったのは .git という 1 つの隠しディレクトリだけで、あなたの既存のファイルには指一本触れていません。

ls では見えないので、隠しファイルも表示するオプションを付けます。

ターミナル

ls -a

プレーンテキスト

. .. .git

.git があれば成功です。以後、このフォルダの中で git コマンドを実行すると、Git はこの .git を見つけて動きます。サブフォルダの中にいても、Git は親をたどって .git を探すので、プロジェクト内のどこからでもコマンドが使えます。

まだ何も記録していない状態を確認します。

ターミナル

git status

プレーンテキスト

On branch main No commits yet nothing to commit (create/use files to commit)

On branch main は、前のレッスンで設定した init.defaultBranch main が効いている証拠です。master と表示された場合は設定が入っていないので、設定し直したうえで .git を消してやり直すか、そのまま進めても構いません。

.git ディレクトリの中身

中を覗いてみます。仕組みを知ると、後で出てくるエラーメッセージの意味が急に分かるようになります。

ターミナル

ls .git

プレーンテキスト

HEAD config description hooks info objects refs

主なものは次の通りです。

名前種類役割
HEADファイル今どのブランチにいるかを書いた 1 行のファイル
configファイルこのリポジトリだけの設定。前レッスンのローカル設定の保存先
objectsディレクトリコミット本体とファイルの中身が入る。履歴の実体
refsディレクトリブランチやタグの名前と、それが指すコミットIDの対応
hooksディレクトリ特定の操作の前後に自動実行するスクリプトの置き場
indexファイルステージの中身。まだ無いので後で出てくる

HEAD は中身を見られます。

ターミナル

cat .git/HEAD

プレーンテキスト

ref: refs/heads/main

HEAD とは「今どこにいるか」を指す目印のことです。この表示は「今は main というブランチにいる」という意味です。Git のエラーメッセージには HEAD が頻繁に出てくるので、この言葉だけは覚えておきます。

refs の下も見てみます。

ターミナル

ls .git/refs

プレーンテキスト

heads tags

heads にブランチ、tags にタグの情報が入ります。今はどちらも空です。ブランチは 1 つもコミットが無い状態では存在できないため、最初のコミットを作った瞬間に .git/refs/heads/main というファイルが生まれます。中身はコミットIDが書かれた 1 行だけです。

つまり Git のブランチは、重たい何かではなくコミットIDを書いた小さなファイルです。第 3 章でブランチを大量に作ることになりますが、いくら作っても容量はほぼ増えません。この事実を知っていると、ブランチを気軽に使えるようになります。

objects は今は空に近い状態です。コミットを 1 つ作るとここにファイルが増えます。

ターミナル

ls .git/objects

プレーンテキスト

info pack

.git の中を手で書き換えてはいけません。読むのは自由ですが、編集するとリポジトリが壊れて復旧できなくなります。中身を確認するのは catls までにしておきます。

3 つの場所を理解する

ここがこのレッスンで最も大事な部分です。Git を使うとき、あなたのファイルは常に 3 つの場所のどこかにあります。

プレーンテキスト

ワークツリー → ステージ → リポジトリ (編集する) (選ぶ) (記録する)

ワークツリーは、いま目の前にあるフォルダそのものです。エディタで開いて編集しているファイルはここにあります。作業ディレクトリとも呼びます。

ステージは、次のコミットに含めるファイルを並べておく待合室です。インデックスとも呼び、実体は .git/index というファイルです。ここに置いたものだけがコミットされます。

リポジトリは、コミットが積み上がっていく保管場所です。実体は .git/objects です。一度コミットされたものは、意図的に消さない限り残ります。

なぜ間にステージがあるのかというと、変更したもの全部をコミットしたいとは限らないからです。

たとえば作業中に、直したかったバグの修正と、ついでに気付いた誤字の修正を同時にやってしまったとします。この 2 つは別のコミットにしたい。ステージがあれば、まずバグ修正のファイルだけをステージに置いてコミットし、次に誤字修正を置いてコミットできます。ステージが無ければ、変更したものが全部まとめて 1 つのコミットになるしかありません。

写真の比喩で言えば、ワークツリーが撮影する部屋、ステージが「この人たちを撮ります」と並んでもらった状態、リポジトリが撮り終えた写真のアルバムです。並んでいない人は写真に写りません。

初めて Git を触る人の多くは、この中間の段階を余計な手間だと感じます。実際、変更を全部まとめて記録するだけなら不要です。しかし開発が進むと、1 回の作業で複数の関係ない変更が混ざる場面が必ず出てきます。そのときステージがあると、コミットを意味のある単位に切り分けられます。前のレッスンで扱った「意味の単位で戻せる」という利点は、このステージによって支えられています。

さらに、ステージは同じファイルの中の一部だけを選ぶこともできます。1 つのファイルの 100 行目の修正だけをコミットし、20 行目の実験的な変更は手元に残す、といった操作が可能です。この使い方は少し高度なので、まずは「ファイル単位で選べる」ところから始めれば十分です。

状態がどう変わるか

3 つの場所とコマンドの対応は次の通りです。ここでは名前だけ押さえて、実際の操作は次のレッスンでやります。

操作コマンド動き
編集するエディタワークツリーが変わる
ステージに置くgit addワークツリー → ステージ
記録するgit commitステージ → リポジトリ
今の状態を見るgit status3 か所の差を教えてくれる

git status はこの 3 か所を比べて、どのファイルがどの段階にいるかを教えてくれるコマンドです。Git で迷ったらまず git status を打つ、という習慣が付けばそれだけで大半の混乱は避けられます。何も書き換えないコマンドなので、何度打っても害はありません。次のレッスンからは、コマンドを 1 つ実行するたびに git status を挟む形で進めます。

大事なのは、この 3 か所がそれぞれ独立に違う内容を持てることです。ファイルを編集してステージに置き、そのあとさらに編集すると、ワークツリーとステージとリポジトリの 3 つが全部違う中身になります。初学者が「add したのにコミットに入っていない」と混乱するのは、ほぼこのケースです。git add はその瞬間の内容をステージに写す操作なので、あとから編集しても自動では追随しません。

もう 1 つ、Git は世界を「追跡しているファイル」と「していないファイル」に分けます。git init した直後は、既にフォルダにあったファイルも含めて全部が未追跡です。一度でも git add して記録すると、そのファイルは追跡対象になり、以後の変更が検知されるようになります。

試しにファイルを 1 つ作って様子を見ます。

ターミナル

echo "# Git practice" > README.md git status

プレーンテキスト

On branch main No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) README.md nothing added to commit but untracked files present (use "git add" to track)

Untracked filesREADME.md が出ました。ワークツリーにはあるが、ステージにもリポジトリにも無い状態です。Git は「これを含めたいなら git add を使え」と案内までしてくれています。

未追跡と追跡済みの区別が効いてくるのは、パスワードを書いた設定ファイルや、自動生成された巨大なフォルダを扱うときです。追跡していない限り、それらは履歴に一切入りません。ただし毎回 git status に出てきて邪魔なので、第 1 章の最後で .gitignore を使って表示から外す方法を扱います。

やってしまいがちな失敗

最も多いのが、間違った場所で git init することです。特にホームディレクトリで実行してしまう事故はよく起きます。cd するのを忘れたまま打つと起こります。

ターミナル

cd ~ git init

こうすると、ホームディレクトリ配下の全ファイルが Git の管理候補になります。git status を打つと数万件の未追跡ファイルが並び、動作も重くなります。

気付いたら .git を消せば元に戻ります。

ターミナル

cd ~ rm -rf .git

rm -rf は中身ごと消すコマンドなので、パスを間違えないよう十分に確認してから実行します。消すのは .git だけで、他のファイルには影響しません。

そもそも自分が今どこにいるか分からなくなったら、git status の結果と pwd を見ます。リポジトリの外にいる場合、Git ははっきりそう答えます。

プレーンテキスト

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

このメッセージが出たら、原因は 2 つのどちらかです。git init をしていないか、違うフォルダにいるかです。pwd で場所を確かめれば、たいていその場で解決します。

次に多いのが、リポジトリの中に別のリポジトリを作ってしまうことです。git-practice の中に frontend フォルダを作り、そこでも git init してしまう、という形で起きます。

この状態になると、外側のリポジトリから見て frontend は「中身の分からない何か」として扱われ、中のファイルが個別に記録されなくなります。git status にも frontend/ としか出てきません。意図してやる高度な使い方もありますが、学習中はまず事故です。

対処は内側の .git を消すだけです。

ターミナル

rm -rf frontend/.git

消したあとで git status を打つと、frontend の中のファイルが 1 つずつ未追跡として並びます。それが正しい状態です。

もう 1 つの失敗は、既にリポジトリになっているフォルダでもう一度 git init してしまうことです。これは無害で、次のように再初期化した旨が出るだけです。履歴は消えません。

プレーンテキスト

Reinitialized existing Git repository in /Users/taro/git-practice/.git/

次のレッスンでは、いま未追跡になっている README.md を実際にステージに乗せてコミットします。この 3 層モデルが頭に入っていれば、addcommit の役割分担はすぐに掴めます。

この章のポイント
  • git init.git ディレクトリを作るだけで、既存ファイルには一切触らない
  • .git の中の HEAD は今いる場所、objects は履歴の実体、refs はブランチ名の対応表
  • ファイルは常にワークツリー・ステージ・リポジトリの 3 か所のどこかにいる
  • ステージがあるおかげで、変更のうち一部だけを選んでコミットできる