何とは言わない天然水飲みたさ

git入門の前に知っておきたい概念

はじめに

git入門記事が巷に(というか主にwebに)満ち溢れる時代である。 で、私は「git使ってみよう!ほら簡単でしょ!」方式の入門記事を見る度に、「ブラックボックスっぽいなぁ」と思うわけである。

プログラミングにおいてもそうなのだが、とりあえず使えはするが原理や思想をわかっていない状態というのは非常に微妙で、ややもすればその言語やツールを嫌い/苦手な状態へと転がっていってしまう。 (私の場合だとPythonなんかがそうだ。) なにかブラックボックスめいたものを使うとき、そのインターフェースに統一性や合理性を見出して楽に覚えるためには、内部を理解するか思想を理解するのが一番の早道である。

そういうわけで、この記事ではgitのコマンドを見て、「それで何ができるか」ではなく「それで何が起きるか」がわかるように概念の解説を試みる。

それから、GitHubとgitは全く別の概念なので、一旦GitHubのことは忘れること

用語集

reposiotry, リポジトリ [名詞]

repositoryは、一般の英語では「貯蔵庫」「倉庫」といった意味である。 gitやバージョン管理の文脈でいうリポジトリとは、プロジェクトに関連するデータを一元的に保管する場所、およびその場所にあるデータ全体と捉えて問題ないだろう。 たとえば、ありがちなプログラム開発のリポジトリに含まれる情報としては、仕様書やドキュメント、過去から現在に至るまでのソースコードと変更履歴、画像等のリソース、テストコード、会議の議事録やフォーラム、バグ情報や進捗情報などが含まれる。

gitが管理するのは、このうちでも、過去から現在に至るまでのファイルと変更履歴である(これを特にgitリポジトリと呼んだりする)。 (ファイルとして管理されていれば、仕様でもドキュメントでもソースコードでもテストでもリソースでも、何でも管理できる。)

gitと並べてよく紹介されるGitHubであるが、こいつはgitリポジトリに加えてissue trackingのためのシステム等を提供しているもので、要するにgitリポジトリを内部に持つ、もっとパワフルな何か別物である。 これが「gitとGitHubを一緒に教えるな」という派閥の人々の主張の根拠のひとつである。

commit, コミット [名詞]

コミットは、「gitリポジトリで管理されているファイル群の、ある瞬間の状態」を示す。 (この「ある瞬間の状態」が曲者で、実際にそうなったことのない状態も保存できるわけだが、それはまあ初学者は無視して良い。)

たとえばバックアップのために、プロジェクトのディレクトリをコピーしてzipで保存した(hoge_project_20160627.zip)としよう。 概念としては、このzipファイルが「コミット」だ。 コミットを、「プロジェクトの(ファイル群の)スナップショットである」という表現をすることもあるが、こちらの方がわかりやすいという人もいるかもしれない。

実際には、gitはもっと効率的な形でファイルを保存するため、zipを使わず別の方式でやっている[0]が、本質的には同じことである。

gitリポジトリにおけるコミット全体は、有向(非巡回)グラフを形成する。

commit <file(s)>, <ファイル(群を)>コミットする [他動詞]

コミット[名詞]を作ること。

コミット[名詞]を「zipで保存されたプロジェクト」で喩えたが、これに合わせて言うなら、コミットするとは「プロジェクトをzipで保存する」行為をいう。

では何からコミット[名詞]を作るかといえば、直観的には「現在のプロジェクト(ファイル群)の状態」(ワーキングディレクトリ)からである。 しかし、実はgitでは間に一段挟んで、ファイルを即座に保存するのではなく、ステージング・エリアに追加されたファイルを、そこに追加された状態で保存することになっている。

詳細はステージング・エリアを参照。

checkout <commit>, <コミットを>チェックアウトする [他動詞]
checkout <branch>, <ブランチを>チェックアウトする [他動詞]

コミット[名詞]を(ワーキングディレクトリに)展開し再現すること。

コミット[動詞]を「プロジェクトをコピーしてzipで保存する」ことだと喩えたが、これをなぞるなら、チェックアウトとは「zipを展開してプロジェクトに上書きすることで、別の状態のプロジェクトを再現する」ということである。

ブランチをチェックアウトするというのは、ブランチが実際にはひとつのコミットを指すポインタのようなものであるから、「ブランチで指されたコミットをチェックアウトする」という意味である。

working directory, ワーキングディレクトリ [名詞]

現在あるがままのプロジェクトのディレクトリ(のファイル群)の状態のこと。 また、その状態のディレクトリ自体のこと。

要するに直訳の通り「作業中のディレクトリ」の状態である。

branch, ブランチ [名詞]

普通の英語で言うと「枝」。

gitの文脈では、ブランチはあるコミット[名詞]を指すラベルのようなものであり、また、そのコミットを含む先祖の一連のコミット(つまり、履歴が繋がっている、以前のコミット)を意味する。 詳しくは「git branch commit」とかでGoogleで画像検索した方がわかりやすいだろう。

staging area, ステージング・エリア, ステージング領域 [名詞]

次のコミット[動詞]で作成されるコミット[名詞]に保存されるファイル群(の追加や変更)。

  • staging areaに追加されたファイルは、その追加された新たな内容が(古い内容に代わって)次の新たなコミットに含まれる。
    • 前回のコミットに含まれていたファイルは、staging areaにあるファイルで置き換えられる。
    • 前回のコミットに含まれていなかったファイルは、次のコミット[名詞]から新規追加される。
  • staging areaから排除された(unstageされた)ファイルは、次のコミット[名詞]に含まれなくなる。
  • staging areaに追加されなかったファイルは、前回のコミットでの内容がそのまま引き継がれる。 (追加・変更・削除関係なく、次のコミットに反映されない。)

詳しくは下で解説する。

add <file(s)>, <ファイル(群を)>追加する [他動詞]
stage <file(s)> [他動詞]
index <file(s)> [他動詞]

新規ファイルやファイルの変更を次のコミットに含むために、ステージング・エリアに追加すること。 逆に、stageされた新規ファイルや変更されたファイルをステージング・エリアから取り除くことをunstageとも言う。

たとえば「ブログを管理してるgitリポジトリで、前回のコミットの状態から2記事を新しく追加したけど、まず1記事だけ追加した状態のコミットを作っておきたい」などのケースは実際よくあることだ。 もし現在のファイルの状態からしかコミット[名詞]を作れないのでは、追加しない方の記事を一度削除してコミット[動詞]、という面倒な手順を踏むことになってしまうが、ステージング・エリアをうまく使ってやることで、ワーキングディレクトリを変更することなく部分的に変更をコミットに追加することができる。

diff, 差分 [名詞] change(s), 変更 [名詞]

グラフの用語で言うなら、コミット[名詞]ステージング・エリアのファイルがノードで、diff(差分)はエッジ(辺)だ。 よって、diffはコミット間以外でも、コミットとステージング・エリア間やステージング・エリアとワーキングディレクトリ間でも考えることができる。

diffが何に役立つかというと、コミット[名詞]間で何がどう変更されたかを確認できたり、またあるdiffを別のブランチコミット[名詞]に適用(rebase/cherrypick)することで、あるブランチでの変更を別のブランチへ同様に適用することができる。 要するに「安定版用のブランチで行われたバグ修正を、不安定版用ブランチにも持ってくる」ということが簡単に可能になる。 また、過去の変更をなかったことにする(revert)こともできる。 (取り消したいdiffの逆の変更を適用すればいい。)

merge <branches>, <ブランチを>マージする [他動詞]

ある共通のコミット[名詞]から分岐した2つのブランチを融合し、新しくひとつのコミット[名詞]を作ること。

共通のファイルはそのまま使用。 一方のみで変更された箇所はそちらの変更を取り込む。 両方で変更された箇所は、衝突(conflict)として扱い、どうするかユーザに委ねる。

コマンド

ファイル関連

ワーキングディレクトリ→ステージング・エリア

git add [files...]

変更を部分的にステージング・エリアに追加するには、 git add -p [files...] とする。 -p--patchの略らしい。

ステージング・エリアから除外 (ただしワーキングディレクトリに変更は加えない)

git rm --cached [files...]

--cachedは「ステージング・エリアから」という指定だ。

ステージング・エリアとワーキングディレクトリ両方から除外

git rm [files...]

ステージング・エリアの内容から次のコミットを作成

git commit

チェックアウト

git checkout [commit] git checkout [branch]

[commit]としては、コミットごとに付けられている番号(16進数)を使う。 本当は結構長いのだが、重複がなければ後ろの方は省略が許される。 だいたい最初の4〜7桁で十分なことが多い。

[branch]としてはローカルのブランチを指定する。

なんかもう途中で面倒になった。 とにかくworking directoryとstaging areaとcommitの違いさえ区別していれば、git commit--soft--hardオプションの違いも区別できるはずだ。

git cherry-pickとかgit rebaseはコミットではなくdiff基準で動作しているということに注意。

まとめ

もう面倒になったので参考文献を見て。

参考サイト

Git - ブランチとマージの基本

ブランチとコミットの図がわかりやすい。

見えないチカラ: A successful Git branching model を翻訳しました

実際のソフトウェア開発におけるブランチの効果的な利用法。 git flowというプラグインで楽に使えるようになる。 プラグインで隠蔽されていても、内部でやっているのはcheckout, pull, merge等の組み合わせだ。 上述の用語を理解していれば何が起きているのかはわかるだろう。