イッヌハブ をつくってみよう

イラスト担当:[嫁](http://www.sawayunagi.net)

![イッヌハブ](https://qiita-image-store.s3.amazonaws.com/0/33963/4c7724a4-1986-1888-27fa-b218e714a21d.png)

![イッヌハブは、いぬたちがそれぞれにじまんのリードを持ちより、みんなでほめあう場です。](https://qiita-image-store.s3.amazonaws.com/0/33963/8e4fc797-d744-68e8-7833-3e752bcc3fb3.png)

![ほかのいぬのステキなリードがほしいときは...がまんしておフロに入ると、ごほうびに神様が複製を作ってくれる決まりになっています。この決まりは、「イッヌハブ・オフロー」と呼ばれています。](https://qiita-image-store.s3.amazonaws.com/0/33963/a7cc423a-8cc0-8a58-3ca6-20eacc616001.png)

![リードのつづきをあみこんで、もっとステキなリードを作ることもできます。このことをアミッコと呼んでいます。世界中のいぬたちが、たくさんのアミッコをつみたててステキなリードを共有しているんですよ。](https://qiita-image-store.s3.amazonaws.com/0/33963/84dd66c5-9b02-739e-4962-75ae99097f26.png)

![イッヌハブは、平和な場ですが、それでも、ときどき、うまくいかないことがあるのを、遠くからブルドッグのチームが見守っています。](https://qiita-image-store.s3.amazonaws.com/0/33963/96e048df-ffef-d6ab-91f6-4b39f95890f0.png)

![たとえば、昔のアミッコがほつれているのが見つかると、ブルがそこまで出かけていって、だまって座りこんで近くのいぬに圧をかけてきます。](https://qiita-image-store.s3.amazonaws.com/0/33963/0e2fbf61-2bc5-f94c-962a-d5f6d9d37c1c.png)

![これを「ブルリクエスト」といいます。ほうっておくとブルリクエストはたまるいっぽうです。すこしずつでいいからなおしていきましょうね。](https://qiita-image-store.s3.amazonaws.com/0/33963/ca6af2eb-9a1a-373c-5ca2-1e9dac67fc90.png)












































































































































































































# イッヌハブはつくれる？

さて、なんだかQiita運営から「内容がないよう」「なので明日夕方には[利用規約](http://qiita.com/terms)に基づいて削除しますぞ」と言われてしまいました。
良い勉強の機会なので、もしもこういうGitHubっぽいサービス（GitHubクローン）を作るとしたらどうやるのかな？というのをサクッと考えてデモを作ってみようと思います。

# 既存のGitHubクローンの実装

いぬ用に限らない汎用的なGitHubクローンは、既にいろいろな言語で出ています。

* [GitBucket](https://github.com/gitbucket/gitbucket) (Scala実装)
* [GitLab](https://gitlab.com/) (Ruby実装)
* [Gitonomy](http://gitonomy.com/) (PHP実装)
* [Gogs](https://gogs.io/) (Go実装)

いずれもオープンソースなので、少し実装を参考にしてみましょう。

## Gitリポジトリーへのアクセス方法の違い

これらGitHubクローンは、どうやってGitリポジトリーのデータをパースしているのでしょうか。
大きく2つの方式に分かれるようです。

* 実行環境のgitコマンドを実行して、得られた出力をパースする（つまりgitコマンドのラッパー）
    * GitLab
        * GitLab自作の[gitlab_git](https://gitlab.com/gitlab-org/gitlab_git)というライブラリーを利用しています。
    * Gitonomy
        * Gitnomy自作の[gitlib](https://github.com/gitonomy/gitlib)というライブラリーを利用しています。
    * Gogs
        * Gogs自作の[git-module](https://github.com/gogits/git-module)というライブラリーを利用しています。
* gitコマンドに依存せず、独自のGit実装によりGitリポジトリーをパースする
    * GitBucket
        * [JGit](https://eclipse.org/jgit/)というpure JavaなGit実装ライブラリーを利用しています。

どっちかいうと、私はJGitのようにgitコマンドに依存しない実装の方が好きですね。gitコマンドのラッパーを使う方式だと、運用中にコマンドとラッパーのバージョン不整合が起こりやすそうで心配というのもあるのですが、gitコマンドをexecして別プロセスで動いてしまったものを間違いなく扱う自信があまり無い、というのも強いです。

とはいえ、いずれも利用実績の多いライブラリーです。言語を問わずに自分のコードからGitリポジトリーにアクセスできるというのは、クローンサービスを作るうえでは非常に心強いですね。
今回はあまり時間が無いので、私の数少ない引き出しで対応するためにPHP + CodeIgniter + gitlibで挑戦してみようと思います。

## gitlibを使ってGitリポジトリーにアクセスしてみる

試しに、Gitリポジトリーの中の各コミットの情報を取得して、Webブラウザー上で表示できるかやってみましょう。
[gitlibのリファレンスマニュアル](http://gitonomy.com/doc/gitlib/master/)を見ながらやっていきます。

たとえば次のような、いぬたちが作り始めたGitリポジトリーがあったとします。
masterブランチ1本だけで、コミットも2回だけです。
（GitのコミットメッセージにはUnicode絵文字が使えますから、きっと犬の文字3種がよく使われるでしょう）
![sourcetree.png](https://qiita-image-store.s3.amazonaws.com/0/33963/05fd53ca-3937-ac0d-7c27-1485c88ed0e6.png)

これに、gitlibでアクセスしてみます。
[Repository](http://gitonomy.com/doc/gitlib/master/api/repository/) オブジェクトからmasterブランチの [Branch](http://gitonomy.com/doc/gitlib/master/api/branch/) オブジェクトを取り出して、そこから [Commit](http://gitonomy.com/doc/gitlib/master/api/commit/) オブジェクトをiterableっぽく取得することも可能なのですが、どうも良い感じのコードが書けませんでした。
代わりに、 [Log](http://gitonomy.com/doc/gitlib/master/api/log/) オブジェクトからCommitオブジェクトの配列を取得してみます。

```php
$repository = new Gitonomy\Git\Repository('/path/to/repository');
$commit_informations = array();

$paths = array();
$offset = 0;
$limit = 10;
$log = $repository->getLog('master', $paths, $offset, $limit);
$commits = $log->getCommits();
foreach ($commits as $commit)
{
    $commit_informations[] = array(
        'hash' => $commit->getHash(),
        'committer_date' => $commit->getCommitterDate()
                                ->setTimeZone(new DateTimeZone('Asia/Tokyo'))
                                ->format('Y-m-d H:i:s'),
        'message' => $commit->getMessage()
    );
}
```

これをViewに出力すると...

![gitlib01.PNG](https://qiita-image-store.s3.amazonaws.com/0/33963/9f2202ed-ee3f-6a64-d7ce-6b3b701c47fe.png)

おっ 何やら取得できますね～
コミット日付がなんだかずれちゃうのは謎ですが...
ともかく、Gitリポジトリーの内容をWebアプリから参照できるようになりました。


# イッヌハブ アミッコ履歴表示の実装

サンプル実装がほどほどにできましたので、ちょっとだけイッヌハブらしい実装をやってみましょう。
Gitの「コミット」のように、イッヌハブ上でいぬがリードの続きを継ぎ足して新しいリードを紡いでいく想像上の動作を「アミッコ」と表現しました。（ダジャレとしてはかなり苦しかったのが残念）
アミッコの現実的な表現として、リポジトリー内にあるリードの画像をWeb画面上でGitのリビジョングラフの線として利用する、というのをやってみましょう。
アミッコのサンプルとして使うのは次の画像ファイル8個です。

![gitlib02.PNG](https://qiita-image-store.s3.amazonaws.com/0/33963/668589ce-b993-ae59-3bf8-ee3bf8032e7c.png)

画像ファイル名は `amikko.png` で固定して、次のように同じファイル名でひたすらコミットし続けた状態のリポジトリーを作っておきます。

![gitlib03.png](https://qiita-image-store.s3.amazonaws.com/0/33963/f2e9e0c1-b8f4-ddd5-8f74-e2ef9e653321.png)


## BlobオブジェクトとTreeオブジェクト

Gitでは、リポジトリーから取得できるファイル実体のことをblobと呼んでいます。blobデータには識別子としてのファイル名が無く、SHA1ハッシュによってのみデータを一意に決めることができます。ファイル名は別途treeデータにより管理されています。
gitlibのオブジェクト構造もこれをなぞったものになっており、まず [Tree](http://gitonomy.com/doc/gitlib/master/api/tree/) オブジェクトを取得し、その情報にしたがって [Blob](http://gitonomy.com/doc/gitlib/master/api/blob/) オブジェクトを取得することで、リポジトリー内のファイル実体を扱えるようになっています。

では、先ほどのコードを少し変えて、 `amikko.png` とコミットメッセージだけ取り出した状態にしましょう。

```php
$repository = new Gitonomy\Git\Repository('/path/to/repository');
$commit_informations = array();

$paths = array('amikko.png');
$offset = 0;
$limit = 10;
$log = $repository->getLog('master', $paths, $offset, $limit);
$commits = $log->getCommits();
foreach ($commits as $commit)
{
    $blob = $commit->getTree()->getEntry('amikko.png');
    $commit_informations[] = array(
        'amikko' => $blob->getContent(),
        'message' => $commit->getMessage()
    );
}
```

`getContent()` で取り出したデータは、この場合ではPNG画像のバイナリーデータそのものです。Web画面に表示する方法として、ここではかんたんにBASE64エンコードしてから `<img>` 要素のdataスキームに埋め込んでしまいます。
リード画像の結合点に○記号をつけて出力してみると...

![gitlib04.png](https://qiita-image-store.s3.amazonaws.com/0/33963/ad0d1091-0c1e-4bdc-d1ca-23a13a85827a.png)

おお！リポジトリー内の歴代の `amikko.png` がリビジョングラフのように並びましたね！


# イッヌハブ アミッコ投稿の実装

よしよし、ではいよいよアミッコを投稿する機能の実装だぞ、と思いきや...

## なんとgitlibはコミット未対応！！！

ここに来て衝撃の事実が発覚するわけですが、gitlabにはcommit、reset、pushなどの、リポジトリーに変更を与える類のAPI実装が2016年10月時点でありませんでした。うむむ...Gitonomyはどうやっているんだろう...
その代わりに、Repositoryクラスのメソッドで、run()というGitコマンドを直接何でもexecできるものがあります。これで最低限commitくらいはできるか確認してみましょう。


## なんでもできてしまうrun()

run()は、任意のgitコマンドを実行することができます。なんでもできます！！！
一応gitlibの方でエラーハンドリングをしているので、素でexecするよりは1億倍マシと言えるでしょう。

```php
$repository->run(addやcommitなどのコマンド文字列, 引数文字列の配列);
```

addからcommitの流れであれば、次のような感じで動かすことができます。

```php
$repository = new Gitonomy\Git\Repository('/path/to/repository');

// sourceからtargetに上書きします
file_put_contents(
    '/path/to/repository/target.jpg',
    file_get_contents('/source.jpg')
);
$repository->run('add', array('target.jpg'));
$repository->run('commit', array('-m "target.jpg"'));
```

さて、ほんとうにコミットされたのでしょうか...

![05.png](https://qiita-image-store.s3.amazonaws.com/0/33963/1621ec45-1713-1cd4-395e-079bc389eab5.png)

おお！コミットされていますね！

これで、

* クライアントは、アミッコ（画像）をサーバーにアップロードする
* サーバーは、アップロードされたアミッコ（画像）をGitリポジトリーに登録する
* サーバーは、Gitリポジトリーに登録されているアミッコ（画像）の履歴を一覧表示する

という、イッヌハブのごく基本的な機能を実装できるようになりました！


# サーバーを立てて動かしてみよう

さて、ここまではWindows PC上で動作確認して記事を書いています。
いよいよ、技術デモとしてWeb上に公開するため、AWSのLinuxサーバーにデプロイしましょう。
私はリッチじゃないのでt2.microで様子を見ます。
なお、ドメインは先ほど取得しました。 現在、 http://innuhub.com/ で、アミッコを表示するデモが見られます。投稿までは今日中に間に合いませんでした…

## そういえばソースコードは...

[GitHubで公開](https://github.com/hiroshitoda/InnuHub) しました！コーディングうまくないので恥ずかしい…composer使いたい…


# イッヌハブ作れました。今回のところは以上です。

ここまでやってもこの記事削除されちゃうのかな！！わかんない！！！

不足した機能とか見てくれは、この後ゆっくりいじっていきます。