Git基礎 #1 Gitとは — スナップショットモデルと3つの領域

読了 9分

コードをファイルコピーで管理した経験があれば、こんなフォルダを作ったことがあるはずです。report_final.docxreport_final_real.docxreport_final_real2.docx。どのファイルが本当に最新なのか、2つの間で何が変わったのか、先週の版に戻るにはどれを開けばいいのか、誰にも答えられない状態になります。コードでも同じことが起き、そこに「複数人が同じファイルを同時に修正する」という条件まで加わると、コピー方式は完全に破綻します。

バージョン管理システムはこの問題を解決するツールです。ファイルの変更履歴を記録し、いつでも過去の時点に戻り、複数人の修正を安全に統合します。そして現在、この分野の標準は圧倒的にGitです。Gitがどんな問題を解決するツールなのか、まず全体像から見たい場合は、非エンジニアのためのIT常識 #5 Gitとバージョン管理を先に読むのもおすすめです。あの記事がコードなしで読める概念紹介だとすれば、このシリーズはターミナルで実際にコマンドを実行しながら身につける実務入門です。

全6回で構成します。

  • #1 Gitとは — スナップショットモデルと3つの領域 ← この記事
  • #2 add・commit・status — ステージングエリアの意味
  • #3 ブランチとマージ — fast-forwardと3-way
  • #4 リモートリポジトリ — clone・fetch・pull・pushとoriginの正体
  • #5 取り消し — restore・reset・revertの使い分け
  • #6 GitHubと初めてのPull Request

今回の記事では、コマンドを覚える前に知っておくべきGitの核心モデル2つ、スナップショット3つの領域を押さえ、git initで最初のリポジトリを作るところまで進めます。この2つのモデルさえ立てておけば、残り5回のすべてのコマンドが「3つの領域の間でスナップショットを動かす作業」として一貫して読めるようになります。

バージョン管理が解決する3つの問題 #

コピー方式と比べると、バージョン管理システムが解決する問題は3つに整理できます。

  • 履歴 — いつ、誰が、なぜ変えたのかが変更単位で残ります。ファイル名に日付を付ける代わりに、記録そのものを照会します。
  • 復元 — 昨日の午後の状態、リリース直前の状態のように、特定の時点へ正確に戻れます。
  • コラボレーション — 複数人が同じプロジェクトを同時に修正し、互いの変更を後から統合します。最後に保存した人によって他人の作業が消える事故がなくなります。

このシリーズでは、履歴と復元は#2と#5で、コラボレーションは#4と#6で、それぞれ実際のコマンドとして確認します。

Gitはデルタではなくスナップショットを保存します #

Git以前の世代のバージョン管理システムは、多くがファイルごとの差分(デルタ)を積み上げる方式でした。ファイルAの1回目の修正、2回目の修正のように差分だけを記録し、特定時点の全体の状態が必要になると、デルタを最初から順に適用して再構成していました。

Gitはアプローチが違います。コミットのたびにその時点のプロジェクト全体のスナップショットを保存します。コミット1つは、その時点ですべてのファイルがどんな内容だったのかを丸ごと指し示す記録です。もちろん、内容が変わっていないファイルを毎回重複して保存することはありません。変わっていないファイルは、すでに保存されている同一内容への参照だけを持つため、ストレージの問題は起きません。

コミットにはスナップショットのほかに2つの情報が入ります。親コミットを指すポインタと、作成者・日時・メッセージなどのメタデータです。コミットが自分の親を指しながらつながっていくので、履歴全体は次のような連鎖になります。

コミット — スナップショットと親ポインタの連鎖
[C1] ◀── [C2] ◀── [C3]
  │        │        │
スナップ    スナップ    スナップ
ショット    ショット    ショット
(その時点)(その時点)(その時点)
(の全体) (の全体) (の全体)

各コミットは親を指します。C3から出発してポインタを
たどれば、プロジェクトの全履歴が復元されます。
注記
このモデルの重要性は#3ではっきりします。ブランチはこの連鎖の上の特定のコミットを指すポインタ1つにすぎないため、Gitでブランチを作るコストは事実上ゼロです。デルタベースのシステムでブランチが重くて遅かったのと対照的な点です。

リポジトリ全体が自分のコンピュータにあります #

Gitは分散型バージョン管理システムです。リポジトリを複製すると、最新のファイルだけでなく、最初のコミットから現在までの履歴全体が自分のコンピュータに入ってきます。そのため、ネットワークがなくてもコミット、履歴の照会、バージョンの比較がすべて可能です。サーバーに接続しないとコミットできなかった中央集権型のシステムと区別される特徴です。

自然とGitHubという名前が思い浮かぶと思いますが、GitHubはGitそのものではなく、リモートのコピーを置いておくホスティングサービスです。自分のコンピュータのリポジトリとGitHubのリポジトリは対等なコピーであり、両者を同期させるコマンドが#4で扱うpushとpullです。GitHub上でのコラボレーションの流れは#6で整理します。

インストール確認と最初の設定 #

Gitがインストールされているかどうかから確認します。

インストール確認
git --version
# git version 2.47.1

バージョンが表示されなければインストールが必要です。macOSはxcode-select --installまたはHomebrewのbrew install git、WindowsはGit for Windowsのインストーラー、Linuxはディストリビューションのパッケージマネージャー(dnf install gitapt install git)を使います。

インストールを確認したら、自分が誰なのかをGitに伝える設定を一度だけしておきます。

最初の設定 — 名前、メール、デフォルトブランチ
git config --global user.name "Yamada Taro"
git config --global user.email "taro@example.com"
git config --global init.defaultBranch main

user.nameuser.emailは、これから作るすべてのコミットに作成者情報として記録されます。後でGitHubアカウントに接続するメールアドレスを入れておくとよいです。init.defaultBranchは、新しいリポジトリのデフォルトブランチ名をmainに指定する設定です。Gitが長らくデフォルトとして使っていたmasterの代わりに、現在はmainが事実上の標準で、GitHubのデフォルトもmainです。設定が正しく入ったかどうかはgit config --listで確認します。

git init — .gitディレクトリの正体 #

いよいよ最初のリポジトリを作ります。空のディレクトリが1つあれば十分です。

最初のリポジトリを作る
mkdir hello-git
cd hello-git
git init
# Initialized empty Git repository in /Users/taro/hello-git/.git/

出力メッセージが核心をそのまま語っています。git initがしたことは、現在のディレクトリの中に隠しディレクトリ.gitを作ったことがすべてです。これから作るすべてのコミット、すべてのスナップショット、すべての設定がこの.gitの中に保存されます。逆に言えば、.gitディレクトリを消した瞬間、このプロジェクトの履歴はすべて消えて、ただのフォルダに戻ります。

プロジェクトフォルダと.gitの関係を区別しておくと、次の節の3つの領域が自然に理解できます。ファイルを作って修正するプロジェクトフォルダが作業スペースで、.gitが記録の保管庫です。

ヒント
.gitの中のファイルを直接開いて修正することはありません。内部構造が気になって覗いてみるのは良い勉強ですが、変更は必ずgitコマンドを通して行います。

3つの領域 — ファイルが記録されるまで #

Gitを学び始めたとき、最も多くの人がつまずくのがここです。Gitではファイルは保存ボタン1回で記録されるのではなく、それぞれ役割の違う3つの領域を通過します。

3つの領域と2回の移動
working directory        staging area          repository (.git)
─────────────────        ────────────          ─────────────────
ファイルを作って          次のコミットに          コミット(スナップ
修正する実際の            入れる変更の            ショット)が永久に
作業スペース              待機リスト              保管される場所

      │                       │
      └────── git add ───────▶│
                              └───── git commit ──────▶ 新しいスナップショットを記録
  • working directory — いま目に見えているプロジェクトフォルダそのものです。エディタでファイルを作り、修正するすべての作業がここで起きます。
  • staging area — 次のコミットに含める変更を選んでおく待機リストです。git addが変更をこの領域に上げます。
  • repository.gitディレクトリです。git commitがstaging areaに上がった内容で新しいスナップショットを作り、ここに永久に記録します。

ファイルを修正してもすぐには記録されず、git addで上げてもまだ記録されたわけではありません。git commitまで済ませて、はじめてスナップショットの連鎖に1つ追加されます。それにしても、なぜわざわざaddという中間段階を挟むのでしょうか。この問いが次回のテーマです。先に一行で答えるなら、コミットに「何を入れるか」を設計できるようにするためです。

まとめ #

今回の核心は2つのモデルです。

  • Gitはデルタではなくスナップショットを保存します。コミットはその時点全体のスナップショットと親ポインタの束であり、履歴はコミットの連鎖です。
  • ファイルはworking directory、staging area、repositoryの3つの領域を通って記録されます。git addgit commitが2回の移動を担当します。

さらにgit configで最初の設定を済ませ、git initで最初のリポジトリを作りました。次回の「Git基礎 #2 add・commit・status — ステージングエリアの意味」では、このhello-gitリポジトリに実際のファイルを作り、add、commit、status、diffを手で動かしながら、ステージングエリアがなぜ存在するのかを確認します。

X