今日も適当ダイアリー

PHP や Javascript や Symfony、BEAR.Sunday などのWeb周りのことを中心に。それ以外のことも気まぐれに投稿します。

Git のおさらい

8/21(日)にタイレルシステムズさんに会場提供いただいて行われた TDDBC Tokyo 1.7 for PHP に参加してきました。
参加登録に出遅れ補欠だったのですが、@ さんから、

LT してくださる方を 1 名補欠の方の中から募集します。LT してくださる方は LT 枠として、本編にも参加していただけるようにします。題材は広く PHP や TDD に関することなら何でも良い、つまり、開発に関することなら何でも良い、とします。

との言葉をいただき、参加したかったので、LT表明をして参加させていただけることになりました。

そこで、「Gitのおさらい」というLTをしてきましたので、その内容をまとめておきます。
なお、勉強会本編の感想は別記事でアップする予定です。
下にあるスライドと併せてお読みください

Git

分散バージョン管理システム

リポジトリ複数ある

集中型バージョン管理システムCVSSubversionなど)では、開発者は、サーバー上などにある唯一のリポジトリに対してチェックアウトやコミットを行います。

それに対して、分散バージョン管理システムでは、開発者は、サーバー上などにあるリポジトリから、リポジトリをコピー(clone)し、そのコピー(ローカルリポジトリ)に対してコミットやブランチなどの作業を行います。

つまり、マスターリポジトリの他に、Aさんのローカルリポジトリ、Bさんのローカルリポジトリ……のように、複数リポジトリが存在します。(システム的にはそれぞれのリポジトリは優劣はなく、マスターリポジトリが無くても構わない。)

気軽にコミットできる

ローカルリポジトリへのコミットは他人に影響しないため、気軽にコミットしたり、ブランチを切って実験的な試みをすることができます。(心理的な障壁がとても少ない)

集中型バージョン管理ではコミットすると、プロジェクトメンバーの全員にその変更が伝わるので、ちょっとした作業とか、汚い状態ではなかなかコミットし辛かったですよね。(それに無駄にリビジョン番号が上がってくのも。。。)

速い

コミットやブランチなどの日常的に行う操作がローカル内で完結し、いちいちリモートのリポジトリに問い合わせる必要がないので、集中型-よりも速いです。

マスターリポジトリへの同期や変更の反映はコミットとは別の任意のタイミングで行える

マスターリポジトリとの同期(fetchやpull)や変更の反映(push)などは、任意のタイミングで行えます。

なので、実験的な試みをローカルリポジトリ内で育てていって、ある程度固まったタイミングでマスターリポジトリに反映させたりできますし、もちろん、ネットワークの環境がない場合でも、ローカルリポジトリへのコミットなどの作業を行えます。

オブジェクトについて

Gitではプロジェクトの状態などを記録するために、データをオブジェクトという単位が使われています。また、そのオブジェクトにはいくつかの種類がありますが、すべて下記の形式で保存されています。

オブジェクトのヘッダーとデータを結合し、その「SHA-1ハッシュ」を取った値(16進数40文字)がオブジェクト名(オブジェクトIDと呼ばれることも)となり、このオブジェクト名をキーとして、オブジェクトDBにデータが(実際には圧縮されて)保存されます。

Gitでは、このオブジェクト名がコミットやファイルなどを一意に特定する際に用いられます。

オブジェクトの型は下記の4つのようです。

blob オブジェクト

ファイルデータを格納します。ファイル名や権限情報などは保持せずに、データ部にファイルの内容がそのまま入ります。

例:

tree オブジェクト

1つ以上のblobオブジェクトやtreeオブジェクト(サブディレクトリ)を参照することで、ディレクトリ/ファイル構成を格納します。
ファイル/ディレクトリ名やファイル権限なども保持します。

例:

commit オブジェクト

コミット情報を格納します。下記のようなデータが保持されます。

tree ルートディレクトリのtreeオブジェクト名
parent (この)コミットが作られる元となったコミット(ひとつ前のコミット)。マージの際などのように、元となるコミットが複数ある場合は、parentも複数ある。

下記の例のようにparent項目が無い場合は、前のコミットが無いルートコミット(最初のコミット)
author Author情報
comitter Comitter情報
- コミットコメント

例:

tagオブジェクト

コメント付き、署名付きタブを格納します。
($ git tag stable-1 などとして作られる軽量タグの場合は、オブジェクトは作られません。)

インデックスについて

Gitではリポジトリとワークツリー(作業ディレクトリ)の間に、インデックスという領域があります。

インデックスは、ステージングエリアと呼ばれることもあり、プロジェクト全体のスナップショット(treeオブジェクトを生成するための情報)、ツリー間のコンフリクト情報や3-wayマージのためのツリー情報などを保持しています。

コミットやチェックアウトなど、リポジトリとワークツリー間のやりとりにはインデックスが経由されます。

2.チェックアウト(checkout)を行うと、リポジトリの内容がインデックスとワークツリーに展開されます。
4.編集後、インデックスへの反映(add)を行うことで、ワークツリーのスナップショットがインデックスに記録されます。
5.コミット(commit)を行うと、インデックスの内容がリポジトリにコミットされます。(ワークツリーの状態がコミットされるのではない)

下記の図のように、インデックスへの反映(add)後にワークツリーを再編集して、コミット(commit)した場合、再編集した内容ではなく、インデックスへの反映(add)時の状態がコミットされます。

再編集した内容をコミットしたい場合は、下記の図のように、再度インデックスへの反映(add)を行う必要があります。


プロジェクトの開始

新しいプロジェクトを作る
$ cd path/to/project
$ git init
既存のプロジェクトを取得
$ git clone git://path/to/project
$ cd project

インデックス-ワークツリー間に対する操作

indexにファイルのスナップショットを追加
$ git add target.php  # 対象ファイルを指定

$ git add -u          # 変更されたファイルの追加
indexからの削除
$ git rm --cached target.php # ワークツリーのファイルは残す

$ git rm target.php          # ワークツリーのファイルも削除
ステータスの確認
$ git status
index-ワークツリーのdiff
$ git diff
indexからチェックアウト
$ git checkout target.php
indexへの変更を無かったことに
$ git reset HEAD target.php

リポジトリ-インデックス間に対する操作

リポジトリ-インデックスのdiff
$ git diff --cached
インデックスの内容をコミットする
$ git commit
コミットをやり直す

直前のコミットを無効にして、新しいcommitオブジェクトを生成する。

$ git commit --amend
コミットを取り消し、その内容をindexに戻す
$ git reset --soft HEAD^

リポジトリ-ワークツリー間に対する操作

リポジトリ-ワークツリーのdiff
$ git diff HEAD
変更されたファイルをコミットする

git add -u && git commit と同じ

$ git commit -a
ワークツリーを指定したコミット時点の状態に戻し、コミットする
$ git revert a5102ef4
コミットを取り消し、ワークツリーの内容も書き換える
$ git reset --hard HEAD^
ブランチの内容をチェックアウト
$ git checkout master
ブランチを作り、その内容をチェックアウト

git branch branch1 && git checkout branch1 と同じ

$ git checkout -b branch1
コミット間をマージし、結果をワークツリーに反映

マージ結果を自動でコミット

$ git merge new_function

マージ結果をコミットしない
$ git merge --no-commit new_function

リポジトリ内での操作

ログを見る
$ git log
コミット間のdiff
$ git diff HEAD^..HEAD
ブランチの確認
$ git branch
ブランチの作成
$ git branch experimental
ブランチの削除
$ git branch -d experimental
$ git branch -D experimental  # 強制削除

リモートリポジトリへの操作

リモートブランチの確認
$ git branch -r
リモートブランチを複製元リポジトリの最新バージョンの状態に更新
$ git fetch origin
リモートブランチの内容をローカルブランチにマージ
$ git merge origin/master
リモートリポジトリから変更をプル

git fetch origin && git merge origin/master と同じ

$ git pull origin master
リモートブランチの削除
$ git push origin :experimental
リモートリポジトリに変更をプッシュ
$ git push origin master

まとめ

オブジェクトとインデックスを理解して、よりgitを直感的に使いこなしましょう!

スライド

参考

入門Git

入門Git

http://progit.org/book/ja/

反省

  • Gitは「ぎっと」と呼びます。どうしても「じっと」と読んでしまう自分に反省。
  • 10分で収まる内容じゃなかった。。。