tasuwo blog

Gitの基礎知識をまとめたよ

| Comments

Gitについてまとめる

GitHubを本格的に利用し始めたので.
基本的な知識は,公式ドキュメントを参照すれば身につくと思う.今回は必要最低限の知識をメモしておく.飽くまで自分用メモ.

Gitって何?というのは省略する.途中,よく使うコマンドをいくつかあげるけど,まずは読み飛ばして後から確認したほうがいいかもしれない.

1. はじめようGit

Gitのはじめ方は二種類ある.

  1. ローカルのディレクトリにGitを導入する
  2. サーバに公開されているGitプロジェクトを導入する

それぞれ以下のコマンドを用いる.

  1. git init
    • .gitディレクトリを生成する
    • リポジトリに必要な全てのファイルがその中に格納される
    • Gitディレクトリ,ステージングエリアが準備される
  2. git clone [url]
    • 既存のGitリポジトリのコピーを取得する
    • サーバが保持しているデータをほぼ全てコピーする

2.はひとまずおいておく.1.の話をしていきます.

2. Gitでずばりやることとは

Git は3つのデータ領域から構成される.

  • 作業ディレクトリ(ワークツリー)
    • 作業ファイルなどを保持しているディレクトリ
  • ステージングエリア(インデックス)
    • 次のコミットに何が含まれるかに関しての情報を蓄えたファイル
    • Gitディレクトリに含まれる
  • Gitディレクトリ
    • プロジェクトのためのメタデータとオブジェクトのデータベースがある所

フォルダ構成のイメージは↓こんなかんじ.

1
2
3
4
5
6
root          ;; 作業ディレクトリ
├ test.txt    ;; 作業ファイル
└ .git        ;; Gitディレクトリ
 ├ index     ;; ステージング・エリア
 ├ config
 ...

rootディレクトリでgit initを実行すると,上記のような.gitフォルダが作成されて,3つの領域が準備される,という仕組み.

ここで,作業ディレクトリはわかるけれど,ステージングエリアとGitディレクトリは一体何なのか.
Gitはバージョン管理システムだから,当然システムの状態を時系列順に保持しておく必要がある.その保存のための場所がGitディレクトリである.
ただ,作業が一区切りついた時に,ファイルA の変更は保存しておきたいけど,ファイルB の変更はまだ保存しなくていいな…なんてことがあると思う.こういう時のための準備用の領域として用意されてるのがステージングエリアである.
作業して色々なファイルを変更した後,ステージングエリアにどのファイルの変更内容を保存するか通知しておき,それを元にGitディレクトリに変更内容を保存する,という流れ.箇条書きにすると↓のようなかんじ.

  1. 作業ディレクトリで作業して,一区切りつく
  2. 保存しておきたいファイルをステージングエリアに通知する
  3. ステージングエリアを元にGitディレクトリにシステムの状態を保存する

そして,このデータ領域間のファイル操作のためにgitコマンドを用いる,というのがGitの基本.主に用いるコマンドは↓のようなかんじ.

  • git add <filepatter> ...
    • 作業ディレクトリからステージングエリアに対象ファイルがステージされる
    • ディレクトリは独自のツリー構造でステージされる
  • git commit -m <msg>
    • ステージングエリアの内容をGitディレクトリに登録する
    • 登録すると,変更内容に対して一意のコミットIDが発行される
    • 一番最近発行されたコミットIDがHEADとなる
  • git reset HEAD <file>
    • ステージングエリアGitディレクトリのHEAD状態に戻す
  • git checkout -- <file>
    • 作業ディレクトリステージングエリアの状態に戻す

上記のコマンドを用いた大まかな作業の流れは↓のようなかんじ

  1. 作業ディレクトリで作業する
  2. 作業内容をステージングエリアgit addする
  3. ステージングエリアの内容をGitディレクトリgit commitする
  4. 再び作業する
  5. 変更したファイルをステージングエリアgit addする
  6. Gitディレクトリgit commitする
  7. 〜繰り返し〜

3. Gitのデータ構造に関する予備知識

説明したこと.

  • 作業ディレクトリでファイルを編集する
  • ステージングエリアに変更内容を準備する
  • Gitディレクトリに変更内容を保存する
  • これらの作業はgitコマンドにより行う

次は,Gitがどういう仕組みでバージョン管理を行っているのかについて説明する.一口にバージョン管理とは言っても,ファイルの変更履歴の格納の仕方には色々ある.一般的なVCSは,各ファイルの基本バージョンからの変更・差分を時系列順に管理すると思う.

↓イメージ

Version1 Version2 Version3 Version4 Version5
file A 差分1 差分2
file B 差分1 差分2
file C 差分1 差分2 差分3

しかし,Gitの場合はデータをスナップショットの集合として考える.

  • スナップショットって何だよ…

    • つまりその時点のファイル丸ごとです
    • 例えば,以下のような 1〜1000000 を記述したファイルがあったとする
      1 2 3 ... 1000000
    • これをGitディレクトリに commit したとする
    • その後,以下のように内容を変更したとする
      1 2 3 ... 1000000 1000001
    • これをGitディレクトリに commit した場合,どのようなデータがGitディレクトリに格納されるか?
    • 一般的なVCSなら,1000001の箇所を差分として保持する
    • 一方,Gitは 1〜1000000 を記述したファイルと 1〜1000001 を記述した両方のファイルを保持する
  • それってリポジトリが肥大化しない?

コミット時に全てのファイルの状態のスナップショットをとり,そのスナップショットへの参照を格納する.ファイルに変更がない場合は,既に格納してある以前の同一ファイルへのリンクを格納する.

↓イメージ(括弧で囲んでいる場所は,過去のオブジェクトを参照している)

Version1 Version2 Version3 Version4 Version5
A A1 (A1) A2 (A2)
B (B) (B) B1 B2
C C1 C2 (C2) C3

また,Gitのデータはすべて,格納される前にチェックサムが取られる.

  • チェックサムって何だよ…
    • 誤り検出のためのものです
    • データの転送中に情報を失う,もしくは壊れたファイルを取得した場合にGitが検知できるようにするためのものです
    • とりあえず,各ディレクトリやファイルに対する一意の識別子を算出している,と思っておけば良い

Gitがチェックサムに用いる機構はSHA-1ハッシュと呼ばれる.16進数の文字で構成された40文字の文字列で,ファイルの内容もしくはGit内のディレクトリ構造を元に計算される.
↓みたいな.

1
24b9da6552252987aa493b52f8696cd6d3b00373

4. Gitディレクトリに格納されているcommitオブジェクトとは

説明したこと.

  • Gitは差分ではなく,スナップショット(ファイルまるごと)をGitディレクトリに保持している
  • Gitディレクトリで扱う各データからは,チェックサムという一意の識別子が算出され,利用される

スナップショットってどうなっているのか?チェックサムってどうやって使っているのか?について説明する.
git commitを実行すると,ステージングエリアの情報を元にGitディレクトリにシステムの状態が保存される.この時の具体的な処理が↓のようなかんじ.

  1. 全てのディレクトリ,ファイルのチェックサムを計算する
  2. 各ファイルの中身を表すblobオブジェクト,ディレクトリ構成に加え,blobオブジェクトとファイル名の対応関係を表すtreeオブジェクトを作成する
  3. メタデータとtreeオブジェクトへのポインタを含んだcommitオブジェクトを作成する

例えば,fileAfileBfileCをcommitしたとする.

1
2
$ git add fileA fileB fileC
$ git commit -m 'initial commit of my project'

すると,以下のような5つのオブジェクトが作成される.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 98ca9...           92ec2..             5d1d3..
|commit         |  |tree            |  |blob       |
-----------------  ------------------  -------------
|tree    |92ec2 |  |blob|5b1d3|fileA|  |fileAの内容 |
|author  |tasuwo|  |blob|911e7|fileB|
|comitter|tasuwo|  |blob|cda0a|fileC|  911e7..
                                       |blob       |
                                       -------------
                                       |fileBの内容 |

                                       cda0a..
                                       |blob       |
                                       -------------
                                       |fileCの内容 |

各テーブルの上部にある98ca9..とか92ec2..とかがチェックサム.これが各オブジェクトの一意の識別子になる.今回は3つのファイルをcommitしたので,各ファイルに対応する3つのblobオブジェクトが作成される.さらに,それをまとめたtreeオブジェクトが作成され,treeオブジェクトへのポインタと,著者やコミッターといったメタデータを保持したcommitオブジェクトが作成される.

commitを重ねると,commitオブジェクトは前回のcommitオブジェクトへのポインタを保持するようになる(下図において,commitは左から時系列順).

1
2
3
4
 98ca9..       34ac2..         f30ab..
|commit    |  |commit      |  |commit      |
------------  --------------  --------------
|...       |  |parent|98ca9|  |parent|34ac2|

5. ブランチ

説明したこと.

  • git commitすると,その時のシステムの状態がGitディレクトリに保存される
    • 各ファイルの内容はblobオブジェクトとして保持される
    • blobオブジェクトとファイル名の関係,ディレクトリ構成はtreeオブジェクトとして保持される
    • treeオブジェクトとメタデータはcommitオブジェクトとして保持される
      • commitオブジェクトを参照すれば,1回の commit における全てのデータにアクセスできる
  • commitオブジェクトは直前のcommitオブジェクトへのポインタを保持する
    • commitオブジェクトは時系列順に数珠繋ぎになっている

プロジェクトが進み,commit が重なるにつれて commitオブジェクト は増えて行く.しかし,保存するだけでは意味がない.バージョン管理システムなのだから,過去・現在のcommitオブジェクト間を行ったり来たりできる必要がある.しかし,そのためには特定のcommitオブジェクトを指定する仕組みが必要になる.そのために用意されているポインタがブランチである.
各commitオブジェクトはチェックサムで一意に識別できる.そのため,ブランチは特定のコミットを指すチェックサムだけを記録したシンプルなファイルである.
プロジェクトを進める上で必要不可欠なブランチが1つある.それは,自分の作業している現在位置を示すブランチである.Git 開始時のデフォルトでは,この役割を担うmasterブランチが用意されている.
また,ブランチは複数作成できる.しかしその場合,現在位置を示すブランチがどれなのかわからなくなってしまう.そこで,自分が今現在作業しているブランチを指し示すHEADという特別ポインタが用意されている.HEADとHEADで示されたブランチは,commitにあわせて自動的に指し示す先が変化する.

masterブランチを使っていて,3回ほどcommitした場合のポインタの状態は↓のようなかんじ(commitは左から時系列順).

1
2
3
4
5
                HEAD
               master
|commit| |commit| |commit|

ブランチ操作のためのgitコマンドとしては↓のようなものがある.

  • git branch <new_branch>[<start_point>]
    • ブランチを作成する
    • Gitディレクトリでは,ブランチが出来る(HEADの位置は変わらない)
    • ステージングエリアでは,何も起こらない
  • git checkout <branch>
    • ブランチを切り替える
    • Gitディレクトリでは,HEADの位置が変わる
    • ステージングエリアでは,HEADに合わせて内容が復元される
  • git merge <branch>
    • ブランチをマージする
    • Gitディレクトリでは,2つのブランチをマージしたcommitが発行される
      • 新たなcommitは,マージ元の2つのcommitを親とする
    • ステージングエリアでは,結合したブランチの変更内容が反映される

6. リモートでの作業

説明したこと.

  • commitオブジェクトはブランチによって指定できる
  • 現在作業しているブランチはHEADによって示される

最後にリモートリポジトリとの操作についてまとめる.
Gitはローカルでの操作だけでなく,リモートリポジトリから作業環境をひっぱってきたり,逆にローカルの編集内容をリモートに反映させたりできる.リモートリポジトリからデータを引っ張ってくることをクローンという.一番最初に説明した,Git を導入する2つの方法の内の2つ目である.
クローンは以下のように行う.

1
$ git clone git://github.com/schacon/grit.git mygrit

上記コマンドは,github.com/schacon/grit.gitからデータを引っ張ってきてディレクトリmygritに格納する.

また,プロジェクトを進めていく上で,リモートサーバ(上記の例で言うならgithub.com/schacon/grit.git)とデータのやり取りを頻繁に行う.しかし,1つのプロジェクトに対して1つのリモートサーバとは限らない.

例えば,本ブログはその雛形としてOctopressというプロジェクトをcloneしている.しかし,記事などのデータの送信先は自分のリポジトリである.なので,

  • ブログの雛形を引っ張ってきたリポジトリ
  • 自分のブログを公開するためのリポジトリ

の2つのリモートリポジトリとやり取りを行っている.

だからどうなのだ,というと,つまり,

  • プロジェクトを進めていく上では,複数のリモートリポジトリとやりとりする可能性があって,
  • その時に各リモートリポジトリを一々URLで指定するのは面倒

ということ.
なので,リモートリポジトリには名前をつけることができる.ちなみに,プロジェクトをクローンした場合は,クローン元のサーバにはoriginという名前がデフォルトでつけられる.

設定されたリモートサーバはコマンドgit remoteで確認できる.-vオプションを指定すると,名前に対応するURLを表示する.

1
2
3
4
5
6
7
$ cd mygrit
$ git remote
origin

$ git -v remote
origin  git://github.com/schacon/grit.git (fetch)
origin  git://github.com/schacon/grit.git (push)

その他,よく使うコマンドは↓みたいなかんじ.

  • git remote add [shortname] [url]
    • リモートリポジトリの追加
  • git fetch [remote-name]
    • リモートプロジェクトの中から作業ディレクトリにデータを引き出す
    • ローカルの環境でマージされたり,作業内容を書き換えることはないため,自分でマージをする必要がある.
  • git push [remote-name] [branch-name]
    • 指定サーバにプロジェクトをプッシュする
    • git push origin master
  • git remote rename
    • リモートを参照する名前を変更できる
  • git remote rm
    • リモートの山椒を削除する
    • サーバが移動したとか,特定のミラーを使わなくなったとか,プロジェクトからメンバーが抜けたとか

7. まとめ

Git の基礎知識についてまとめた.

  • 作業ディレクトリで作業して,ステージングエリアに変更内容を準備(add)して,Gitディレクトリに保存(commit)する
  • Gitディレクトリには commitオブジェクト が時系列順に保存される
    • 特定の commitオブジェクト をブランチで指定できる
    • デフォルトではmasterブランチが用意される
    • 現在作業しているブランチはHEADで示される
  • リモートリポジトリとやりとりする際,やりとり先のサーバには名前をつける
    • デフォルトではoriginという名前がつく

これらの知識を踏まえれば,様々なページで説明されているgitの関連知識やコマンドが理解しやすくなる…と思う.説明がド下手なので,この記事は何回も修正することになることが予想される.

公式
Gitレポジトリはパッチの集積ではなくてスナップショットの集積である。
アリスとボブになりきってgitをちゃんと理解したい!
git push の反対は git pull ではない
git fetchの理解からgit mergeとpullの役割

Comments