2015/05/27 16:24:50

Gitの最低限必要な基礎知識

目次(クリックするとジャンプします)
  • 1:Gitを導入したい
  • 2:Gitとは
  • 2.1:Gitは分散型バージョン管理システム
  • 2.2:Gitが作られた経緯
  • 3:Gitの概念
  • 3.1:リポジトリの意味
  • 3.2:分散型の所以リモートリポジトリとローカルリポジトリ
  • 3.2.1:リモートリポジトリへローカルリポジトリの内容を送る「プッシュ」
  • 3.2.2:リモートリポジトリの内容をローカルリポジトリに持ってくる「プル」
  • 3.2.3:ローカルリポジトリだけも管理は可能だが…
  • 3.3:リポジトリの中身
  • 3.4:履歴やファイルそのものである「Gitオブジェクト」
  • 3.5:Gitオブジェクトが作られる操作
  • 3.5.1:履歴を残す「コミット」
  • 3.5.2:コミットの準備「ステージング」
  • 3.6:Gitオブジェクトが格納されている場所
  • 3.6.1:objectsディレクトリ
  • 3.6.2:Gitオブジェクトファイルの名前
  • 3.7:履歴を形づくる「Gitオブジェクト」の役割
  • 3.7.1:Gitオブジェクトの関連性
  • 3.8:開発ストリームを意味する「ブランチ」
  • 3.8.1:ブランチの意味
  • 3.8.2:ブランチを作る
  • 3.8.3:ブランチをマージする
  • 3.8.4:マージ後のブランチの削除
  • 3.9:現在位置を特定する「リファレンス」と「ヘッド」
  • 3.9.1:リファレンス
  • 3.9.2:HEAD
  • 4:まとめ
  • 4.1:振り返りしてみる

Gitを導入したい

本記事はGitを導入しようと思うが、コマンドラインに慣れていない、バージョン管理システムを使うのは初めてだという人向けに書きました。

また主に1人でGitを使うことを想定しています。あまり範囲が広いと理解が困難になるので、説明は1人で使うにあたって必要最低限の要件に収めているつもりです。

Gitとは

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

バージョン管理システムは開発の履歴を管理することができる仕組みのこと。

管理対象はどんな種類のプログラミング言語・プロジェクトでも構わないです(はず)。だからwebサイト開発(html,css,js,phpなど)だろうが、アプリ開発(swift,Ruby,python等)だろうが、組み込み開発(C/C++,java等)だろうが、会計システム開発(cobol等)だろうが管理可能ですよ。

バージョン管理システムでプロジェクトを管理すると、どこでどのようにどんな変更・修正が加えられたのかが整理され、前のバージョンに容易に戻ることもできるので効率よく、かつソースコードに混乱をきたさなくて済むようになっています。

Gitは分散バージョン管理システムと呼ばれます。Gitではリポジトリ(後述)からローカルにリポジトリクローンを得ることで作業を進める。開発者ごとにリポジトリが分散するので分散型と呼ばれる所以です。Gitは分散型であるからこその恩恵を受けやすいバージョン管理システムです。

Gitが作られた経緯

GitはLinuxのカーネル開発を効率的に行う為に生み出されました。もともとLinuxのカーネル開発には別なシステムバージョン管理システムが使われていたのだそうです。

linux_git

しかし、既存のバージョン管理システムでは具合が悪かったようで、新たにバージョン管理システムを生み出す必要がでてきたようです。そこでLinuxの開発者であるリーナストーバル氏が新しいバージョン管理システムを開発するに至ります。

この辺の経緯はwikipediaに詳しくあるので参照をお勧めする。wikipedia Git

カーネル開発となるとプロジェクトも巨大で、もちろん1人ではできません。複数の凄腕たちがプロジェクトに参加することになります。 Linuxカーネル開発のようなオープンソース開発では全世界中に開発者がいることになります。

Gitは大人数で巨大なソースコードプロジェクトを効率的に共有する為に開発されたということになります。

カーネル開発というある意味最前線で稼働していること、GitHubなどのホスティングサービスが出現したこと、オープンソースであり個人での扱いやすさも相まって急速にGitの利用が広まったというのが現在の状況のようです。

Gitの概念

リポジトリの意味

リポジトリには倉庫とか保管所とかという意味があります。具体的にはファイル置場という解釈でよいかと思います。

Gitではさまざまな仕組みで履歴やファイルを格納してバージョンを管理する仕組みを提供しますが、それらは具体的にディレクトリ構造と複数の種類のファイルの関連性で実現されています。そのことをGitリポジトリというのです。

分散型の所以リモートリポジトリとローカルリポジトリ

Gitは分散型バージョン管理システムだと説明したが、その分散型について説明しておきたいと思います。

中央集権的な仕組みのバージョン管理システムではリポジトリは一つしか存在しません。開発者全員でそのリポジトリにアクセスすることになります。

普通このような共有するリポジトリはサーバー上などに設置されるので、リモートリポジトリと呼ばれます。リモートリポジトリはマスターのような扱いになります。

Gitの場合でもリモートリポジトリが存在するのは変わらないが、リポジトリのコピーをローカルにもってくることができます。そのコピー作業をクローンといい、ローカルにもってきたリポジトリはローカルリポジトリと呼ばれます。

ローカルリポジトリで各開発者が納得のいくまで作業ができるようになっており、マスターとなるリモートリポジトリを不用意に汚すことを回避できるようになっています。

リモートリポジトリへローカルリポジトリの内容を送る「プッシュ」

ローカルリポジトリで作業を進めることになるので、作業が進めば進むほど、リモートリポジトリとローカルリポジトリの内容には差異がでてきます。

Gitではローカルリポジトリの内容をリモートリポジトリに同期することができます。その操作の事をプッシュといいます。プッシュを行うとローカルリポジトリとリモートリポジトリは同じ内容になるという訳です。

リモートリポジトリの内容をローカルリポジトリに持ってくる「プル」

複数の開発者で作業を進めていると誰かがプッシュをしているかもしれません。だからリモートリポジトリの更新された内容をローカルに持ってくることもできます。その操作をプルといいます。

誰かがプッシュをすれば、リモートリポジトリには自分以外の人の変更点が付け加えられていることになります。その変更点をローカルにも取り込んでおかないと、開発の整合性が保てなくなってしまうかもしれません。

このようにGitではリモートリポジトリとローカルリポジトリの間でやり取りをしながら開発作業を進めていくことになります。

ローカルリポジトリだけも管理は可能だが…

ただ、1人で開発する場合はローカルリポジトリだけでもバージョン管理が可能です。1人しかいなければ共有するリモートリポジトリが必要ないからです。

しかしながらバックアップ的観点からしても、1人開発のときでもリモートとローカルの活用は有意義なことだと思います。

リポジトリの中身

リポジトリの実体はディレクトリとファイルだと説明しました。

Gitではリポジトリと作るとその場所に.gitというディレクトリが拵えられます。このフォルダの中にいろいろな情報やファイルがGitで定められている方法に従って格納されるようになっています。

Gitディレクトリの各ディレクトリもしくはファイルの大まかな意味を説明したのが以下です。作業を進めるとさらにファイルや各ディレクトリに子ディレクトリが出来たりします。(※以下はNetBeansIDEでjGitを使用した場合のディレクトリ・ファイル構成となります。Gitそのものとは若干構成が異なるかもしれない)

名前 種類 意味
.git ディレクトリ ルートディレクトリ
branches ディレクトリ ブランチの情報を格納
hooks ディレクトリ フックスクリプトを格納
logs ディレクトリ リポジトリ操作のログを格納
objects ディレクトリ Gitオブジェクトを格納
refs ディレクトリ リファレンスを格納
config ファイル リポジトリの設定要件
index ファイル コミット予定のファイル表
HEAD ファイル 現在の作業対象ブランチを示すhead

 Gitを使うようになれば追々調べたりすることになると思いますが、今の段階ではすべてのディレクトリやファイルの意味を理解しなくてもいいです。とりあえず大事だと思われる部分を説明していきたいと思います。

履歴やファイルそのものである「Gitオブジェクト」

Gitでは開発履歴やプロジェクトファイルをオブジェクトファイルとして格納します。そのことを総称してGitオブジェクトと言います。

Gitオブジェクトは4種類のみです。

名前 役割
commitオブジェクト 履歴情報をファイル化したもの。コミットという操作をすると作られます。
Blobオブジェクト プロジェクトファイルをBinary Large Objectという方式でファイルにしたものです。ステージングもしくはコミットという操作をすると作られます。
Treeオブジェクト ディレクトリ構造をファイル化したもの。コミットという操作をすると作られます。
Tagオブジェクト 名前付けに使うタグの情報をファイル化したもの。タグを制作すると作られます

4種類しかないですが、これらのオブジェクトファイルの絶妙な関連性でうまいこと開発履歴が管理できるようになっているのです。

Gitオブジェクトが作られる操作

履歴を残す「コミット」

履歴を残す操作をコミットといいます。

開発作業が進むと区切りの必要性が出てきます。一定の作業量や、機能搭載の目途など履歴を残したほうがいいなというタイミングはいろいろな哲学があるみたいですが(なるべく整理されたコミットをすることが望まれる)、コミットすることはまさに履歴を残していくことに他ならないので、Gitを使う上でもっとも根幹をなす操作の一つです。

コミットでcommitオブジェクト(ファイル)が生成格納されます。また同時にTreeオブジェクトが生成格納されます。

コミットの準備「ステージング」

ステージングを行うとblobオブジェクト(ファイル)が生成格納されます(ステージングを行わずコミットした場合はコミット時にblobオブジェクトが生成格納される)。

コミットはステージングを行わなくとも直に行うことができますが、コミットの前にステージングする方が一般的なようです。

ステージングはコミットする予定のファイルをblobオブジェクトファイルにして、先にリポジトリへアップロードします。

また.gitディレクトリ直下のindexというファイルに、予定に上がったblobオブジェクトファイルのID(後述)が記述されます。

このindexファイルを確認することで、コミットを正確に行うことができるという訳です。「しまった今回のコミットに必要だったファイルを含めていなかった」などということを回避することができます。

Gitオブジェクトが格納されている場所

objectsディレクトリ

Gitオブジェクトはobjectsディレクトリに格納されます。各オブジェクトは基本的に英数字名のフォルダに一つのファイルが一つのオブジェクトとして格納されています。

objects-dir

Gitオブジェクトファイルの名前

各オブジェクトファイルには長い英数字の名前付けられています。この長いファイル名はファイルの内容を元にSHA-1という関数で計算され付けられます。

Gitオブジェクトのファイル名はIDやハッシュ値と呼ばれ、オブジェクトを一意に特定するために使われる。つまりファイル名がユニークになるというわけです。

object-id

この仕組みにより、内容が異なるオブジェクトファイルは同じIDになりません。逆に同じ内容のオブジェクトファイルはどこでオブジェクトが生成されても必ず同じIDになるようになっています。

履歴を形づくる「Gitオブジェクト」の役割

先ほどの章で簡単に各Gitオブジェクトの意味を紹介しましたが、もう少し詳しく見ていきたいと思います。

Gitオブジェクトの関連性

実はオブジェクトファイルを見ただけではどの種類のオブジェクトなのか見分けがつきません。ファイルを開いてみてもバイナリだから(バイナリエディタなどで見ないと)よくわからないのです。

ごちゃまぜに格納されているようなものです。見分けがつかないのにどのように整理をしているのでしょうか?

Gitオブジェクトは各個に与えられた役割により、他のオブジェクトと関連性を構築しています。これは別に難しいことではなく、ちょっとした伝言ゲーム的な管理方法になっています。前の章で説明したオブジェクトのIDが重要な要素を握っています。

まず重要なのがcommitオブジェクトです。前の章でcommitオブジェクトはコミットという操作で作られると説明しました。先に説明したとおりコミットとは履歴を残す操作のことです。

だからcommitオブジェクトがある時点での履歴そのものと言えます。commitオブジェクトファイルには以下のような事が書かれています。

commit-object

そうcommitオブジェクトファイルにはコミットに関係しているオブジェクトファイルのIDが記載されているだけなのです。

頼りない感じがしますが、commitオブジェクトファイルの内容から、コミットに関係するTreeオブジェクトとBlobオブジェクトを特定することができるようになっているという寸法です。

commitオブジェクトと他のオブジェクトとの関連性を図にしてみました。

git-object

commitオブジェクトは自分の親の(前の)commitオブジェクトのIDを持っています。

そしてディレクトリ構造情報が書かれているTreeオブジェクトのIDを持っています。

さらにTreeオブジェクトはディレクトリとディレクトリに属するBlobオブジェクトファイルの情報持っています。


これでcommitオブジェクトは自分と連続性のあるcommitオブジェクトを辿ることができ、かつ自分に関係しているオブジェクトを特定することができるのです。

つまりcommitオブジェクト→Treeオブジェクト→Blobオブジェクトという参照の流れがGitの履歴管理の基本と言えます。IDが一意で定まるので危険なく単純な伝言ゲームで管理が出来てしまいます。

イメージとしてはツリー構造と捉えるといいかもしれません。

object-tree

コミットがある時点での履歴、コミットのつながりが開発履歴の流れという事になります。重要な概念なのでしっかり理解できるようにしておきたいと思います。

開発ストリームを意味する「ブランチ」

ブランチの意味

Gitにはもう一つ重要な概念があります。それはブランチです。遅めの朝食のことではありません。綴りはbranchで意味は「分岐」です。Git上ではブランチを開発ストリームとします。

リポジトリを初期化し、最初のコミットがされるとmasterブランチが作られます。masterは元になる開発ストリームです。ブランチはコミットをぶら下げておく竿のようなものとイメージしてもらうといいかもしれません。

branch-master

ブランチを作る

最初に説明したとおりブランチは分岐ということで、開発ストリームを分岐することができます。

ブランチは安全にかつスマートに日々の開発を進める為にも使えるので使用頻度の高い機能です。(ブランチという言葉は分割するという動詞的な意味で使われる場合も、ブランチされたストリーム自体を指すこともあるので注意)

branch-branch1/p>

たとえばmasterブランチからnewブランチを分岐させたとします。分岐させた後はそれぞれのブランチで作業(コミット等)が進むので、互いのブランチに影響を与えません。

branch-commit

これを活用すればmasterブランチを汚さずに開発を進めることができます。

ブランチをマージする

分岐できるなら統合もできます。Gitではブランチを統合する際にはマージ(merge)という機能をつかいます。開発の流れを一本化するのです。

branch-marge

マージすると、それぞれのブランチでのコミットが共有されるようになります。例ではnewブランチでのコミットがmasterブランチと関連を持つようになります。(マージしてもnewブランチは別途削除の操作をしない限り無くなるわけではない)

マージしたことも履歴として残すべきなので、通常マージコミットというコミットが作られます。このマージコミット以前はmasterブランチもnewブランチも同じコミットを共有しているということになります。

マージ後のブランチの削除

マージした後にマージ元のブランチを削除してもコミットが失われることはありません。

branch-marge-del1

現在位置を特定する「リファレンス」と「ヘッド」

いままでGitオブジェクトとブランチを説明してきました。今度は作業対象を指し示すリファレンス(reference)とヘッド(HEAD)についてです。

Gitでは多数のブランチやコミットが存在することになります。自分が今どこを対象に作業を行っているかわからなくなると大変です。その為の目印がリファレンスとヘッドなのです。

リファレンス

リファレンスとは各ブランチにひとつづつ存在する、ブランチの先頭のコミットを指し示す情報ファイルです。このファイルには単純に先頭のcommitオブジェクトのIDが記述されています。(正確に言うとフォルダ名+ID)

ref

コミットがされる度にリファレンスファイルは書き換えられ、常にそのブランチの先頭コミットを指し示すようになっています。

リファレンスファイルは.git→refs→headsの中に、ブランチの名前がファイル名となって格納されています。

ref-commit

HEAD

さらにリファレンスを参照するHEADという特殊なリファレンスがリポジトリに一つ存在しています。HEADは現在の作業対象となっているブランチを指し示すリファレンスです。

head

HEADファイルは.gitディレクトリ直下にHEADというファイル名で格納されています。

具体的には作業対象のブランチのリファレンスを指し示しています。HEADファイルには単純にリファレンスファイルのパスが記述されているだけです。

作業しているブランチを切り替えることを「〇○ブランチにチェックアウトする」と言います。チェックアウトとするとHEADファイルはチェックアウト先のブランチのリファレンスを指し示すことになります。

head-checkout

リファレンスとHEADのおかげでどこが先頭か、今はどこが作業対象なのかが判るようになっています。

まとめ

振り返りしてみる

今回の記事について振り返ってみる

  • Gitは分散型バージョン管理システムである
  • GitはLiunxのカーネル開発の為に生み出された
  • リポジトリとはファイル置場である。
  • Gitリポジトリの実体は.gitディレクトリをルートとするディレクトリ構造とファイル群
  • 開発履歴やプロジェクトファイルを意味するGitオブジェクトファイルは4種類
  • Gitオブジェクトの名前はハッシュ値でIDとして活用される
  • Gitオブジェクトはコミット・ステージングなどの操作で生成される
  • commitオブジェクトは履歴そのもので、内容を辿ればそのコミットに関係するオブジェクトを特定することができる
  • 開発ストリームをブランチとして分岐させる事が出来る
  • 分岐後、コミットは各ブランチにて行われ関連性を持たない
  • ブランチはマージ(統合)する事が出来る
  • マージされた後のブランチは削除されてもコミットが消えることはない
  • ブランチの先頭を指し示すのがリファレンス
  • 作業対象のブランチを指し示すのがHEAD

ざっとGitの概念を説明してきた。最初にも言及したとおり必要最低限のつもりですが、実のところまだ言及したりない部分があります。Gitにはもっと多くの要素がありますが、実際に使ってみながらでないと説明しにくい点も多いです。

おそらく初めてGitに関するこの手の記事を読まれた方は「なんのこっちゃ」と思うところも多いのではないでしょうか?

厳密に言えば間違っている部分があるかもしれないので、鵜呑みにはしないでほしいですが、なるべく簡単に理解できるよう図をたくさん作る努力はしまシタ。

「なんのこっちゃ」が「なんだこのことか」に変っていただけたら嬉しいです。