Gitリポジトリビジュアライザ ― Goslings

![goslings-logo.png](https://qiita-image-store.s3.amazonaws.com/0/110166/d16275f6-a229-e690-5779-6131c6b92303.png)

[Git Advent Calendar 2016](http://qiita.com/advent-calendar/2016/git)に[JGit](https://eclipse.org/jgit/)ネタで参加しようと思い立ち登録したはいいものの、JGitの使い方なんてものは既に[投稿されている](http://qiita.com/esplo/items/7bdb736eb0b8ad3b382a)し、JGitを使ったことはなかったのでノウハウなんか書けないし、需要もあるとは思えない。

さてどうしようかということで、以前から啓蒙している[Gitのオブジェクトモデル](http://qiita.com/kaitoy/items/ce6cd3426a16268389d9)を学べるアプリなんか面白いかと思い、JGitを使って__Goslings__というものを作った。ソースは[GitHub](https://github.com/kaitoy/goslings)に置いた。

# Goslingsとは
Goslingsは、Gitのリポジトリの中身をビジュアライズするWebアプリケーションだ。
作ってから気付いたが、リポジトリの中身って小さいプロジェクトでもかなり複雑で、ビジュアライズしても何が何だかわからない。
見ているとちょっと楽しいという効用があることは間違いないが、それ以外の使い道があったら誰か教えてほしい。

Goslingsという名前は、geese(雁)の子供を指すgoslingsという英単語からとった。

![geese.JPG](https://qiita-image-store.s3.amazonaws.com/0/110166/4e4affae-bf0f-0387-d2f8-060dfb3aa3f1.jpeg)

この写真は私のオフィスの駐車場で撮ったものだ。
春になるとこうしたgoslingsをたくさん見ることができ、心が癒される。
Git Objectsを見るアプリなので、GOで始まる名前にしようかと考えたところで、すぐにこのgoslingsが思い浮かんだ。親子で連なって歩く姿がちょうどGitのコミットグラフのようだし、ぴったりではないか。

ロゴは嫁に書いてもらった。かわいい。

# Goslingsの使い方
[GitHubに置いたプロジェクト]((https://github.com/kaitoy/goslings))は`gradlew build`一発でビルドでき、成果物として`goslings-server-0.0.1.jar`というアーカイブができる。
`goslings-server-0.0.1.jar`は[ここ](https://github.com/kaitoy/goslings/releases/download/v0.0.1/goslings-server-0.0.1.jar)からダウンロードもできる。
これを、`java -jar goslings-server-0.0.1.jar --server.port 80`という感じで実行するとGoslingsサーバが起動する。
これにモダンブラウザ(Chrome推奨)でアクセスすると、まずGitリポジトリのURIを入力するフォーム画面が開く。

![goslings-form.png](https://qiita-image-store.s3.amazonaws.com/0/110166/bddadb81-b54f-a348-1019-a1996cfb853f.png)

ここで、ローカルにあるリポジトリへのファイルシステム上のパス(e.g. `C:\repos\project-hoge\.git`)か、リモートにあるリポジトリのURL(e.g. `https://repos.foo.com/project-hoge.git`)を入力して`BROWSE`ボタンを[押下する](http://qiita.com/yaju/items/0ceb6a0343561b4d208e)と、そのリポジトリの中身を表示するビューに遷移する。

![graph.png](https://qiita-image-store.s3.amazonaws.com/0/110166/48db5f3f-08c8-83fe-0410-ecfd40c8973a.png)

このビューの見方は[以前書いた記事](http://qiita.com/kaitoy/items/ce6cd3426a16268389d9)のスライドと同じ。

初期状態ではコミットと参照とタグだけが表示されていて、コミットをダブルクリックするとツリーが表示され、さらにツリーをダブルクリックするとドリルダウンしていける。
ノードをシングルクリックするとそのコンテンツを参照できる。

# Goslingsのアーキテクチャとか
Goslingsのプロジェクト構成は以下の様になっている。

![project.png](https://qiita-image-store.s3.amazonaws.com/0/110166/de255752-b7c4-24c5-6982-36552fb9b285.png)

サーバ側(goslings-server)はJavaでできていて、Webアプリケーションフレームワークに[Spring Boot](https://projects.spring.io/spring-boot/)、Gitリポジトリからの情報取得にJGitを使っている。
ビルドツールは[Gradle](https://gradle.org/)。

クライアント側(goslings-client)はJavaScript(ES2015 + async/await)の[SPA](https://en.wikipedia.org/wiki/Single-page_application)で、禁[jQuery](https://jquery.com/)縛り。
[React](https://facebook.github.io/react/) + [Redux](https://github.com/reactjs/redux)というのをやってみたかったが、なんか大げさだしそこまで時間がとれなそうだったので、フレームワークなしで作った。ので、
「[You Don't Need jQuery](http://qiita.com/tatesuke/items/b9548dd484b01b139b74)」とにらめっこしながら書いた。
Gitのコミットグラフの描画には、[vis.js](http://visjs.org/)を使った。
[Stack Overflowの回答](http://stackoverflow.com/questions/7034/graph-visualization-library-in-javascript)から雰囲気で選んだけど、やりたかったことが全部できて、見た目もよかったのでよかった。
パッケージマネージャには話題の[Yarn](https://yarnpkg.com/)を、モジュールバンドラーには[webpack](https://webpack.github.io/)を使用。

クライアントもサーバと合わせてGradleで一括ビルドできるようにするため、[gradle-node-plugin](https://github.com/srs/gradle-node-plugin)を使った。gradle-node-pluginは[Node](https://nodejs.org/ja/)のダウンロードまでしてくれるので、これを使うとビルド前提条件としてNodeインストールが不要になる。
gradle-node-pluginはもともとYarnをサポートしていなかったので、実装して[プルリクエスト](https://github.com/srs/gradle-node-plugin/pull/151)を送ったらすぐにマージしてくれたので、この記事にも間に合った。ありがとう。

サーバは[Docker](https://www.docker.com/)で動かすためにステートレスに作ったつもりで、サーバの負荷に応じてコンテナを増やしたり減らしたり、簡単にスケールするようになっているはず。
つまりは、サーバはREST APIを提供するわけだが、クライアントがリクエストごとに違うコンテナにアクセスしたとしても何の問題も起こらないはず。

# JGitとは
ここで唐突にJGitについて書く。
この記事は[Git Advent Calendar](http://qiita.com/advent-calendar/2016/git)に投稿したものだし、JGitタグも付けたし、Goslingsの要素技術の中でもJGitだけはちゃんと触れておいた方がいいと思うのだ。

JGitはフリーライブラリでピュアJavaなGit実装である。
ピュアJavaというのは、Javaだけで作られているというだけでなく、Java以外に依存していないということだ。
要するに、CやShellでかかれたあの所謂[Git](https://git-scm.com/)に依存していないということ。
それでいて、`clone`やら`add`やら`commit`やら`reset`やら`merge`といった普段よく使う基本的な操作から、`reflog`や`submodule`といった高度目な操作までサポートしていて、なかなか多機能。

使い方は[クックブック](https://github.com/centic9/jgit-cookbook)のサンプルを見ると分かりやすい。
例えばリポジトリ内全てのログ(i.e. コミット)を集めて表示するには以下の様にする。

```java
try (
  Repository repo
    = new FileRepositoryBuilder()
        .setGitDir(new File("/path/to/git/repo/.git"))
        .readEnvironment()
        .findGitDir()
        .build();
  Git git = new Git(repo);
) {
  StreamSupport.stream(git.log().all().call().spliterator(), false)
    .forEach(System.out::println);
}
```

ぱっと見使いやすそうなライブラリだが、よくよく使ってみたら色んな:poop:が
見えてきた。

## クラス名が:poop:
初めに違和感をもったのは、コミットを表すクラスの名前が`RevCommit`であるところ。`C`で始まるクラスを探していて見つからなくてもやった。

## メソッド名も:poop:
以下は全てGitオブジェクトのIDを取得するコードだが、一貫性がなく分かりにくい。

```java
repo.findRef("HEAD").getObjectId(); // HEADが指すオブジェクトのID
commit.getTree().getId(); // ツリーオブジェクトのID。getObjectIdじゃないの?
repo.findRef("v1.0").getObjectId(); // v1.0というannotatedタグが指すタグオブジェクトのID。これはいいけど・・・
repo.findRef("v1.0").getPeeledObjectId(); // これはタグオブジェクトが指すオブジェクトのID。Peeledって何?
```

また、上の方のコードにも出てきた`Git`というクラス。これはgitコマンドを表すクラスのような雰囲気とAPIを備えているのだが、これを使ってブランチのリストを取得するコードは以下の様になる。

```java
git.branchList().call();
```

対応するGitコマンドは`git branch`なんだから、`git.branch().call()`でよかったんじゃなかろうか。

## メソッドの仕様まで:poop:
Gitの参照を表すクラスである`Ref`には`isSymbolic`というメソッドがあるが、これはシンボリック参照かどうかを返すわけではなく、オブジェクトを直接指しているかどうかを返す。つまり、デタッチした`HEAD`については`false`を返す。これはバグではなく、APIドキュメントに書いてある通りの挙動だ。バグであって欲しかった。

## そもそもクラス設計が:poop:
GitオブジェクトのIDを表す`ObjectId`というクラスがあるのだが、これがなんと各種Gitオブジェクトを表すクラス(e.g. 上記`RevCommit`)の基底クラスとなっている。
多分、ObjectIdが持つフィールドやメソッドを使いたいがためにオブジェクトクラスに継承させたんだろうが、IDはIDであってGitオブジェクトそのものではないのでこれはまずい。オブジェクト指向言語の初心者がやるような過ちだ。

## Gitへの理解すら:poop:
一番驚いたのが、JGitユーザガイドの「[Finding children of a commit](http://wiki.eclipse.org/JGit/User_Guide#Finding_children_of_a_commit)」という節。
コミットの子を探す? 妙なことするなぁとひっかかったが、案の定コミットの__親__を再帰的に探す操作の紹介だった。
<br>
JavaとGitの初心者が設計開発したんだろうか、JGit。

# Goslings Dockerコンテナ
Goslingsの話に戻る。

Goslingsサーバを実行するDockerコンテナイメージも作って[Docker Hub](https://hub.docker.com/)に上げた。[これだ](https://hub.docker.com/r/kaitoy/goslings/)。

作り方は簡単。
Docker Hubに行ってアカウントを作ってログインし、上のツールバーの「Create」から「Create Automated Build」を選択。
指示に従ってGitHubアカウントとのリンクを設定し、GoslingsリポジトリのAutomated Buildなるものを作る。
するとGitHubのGoslingsリポジトリのSettingsにDocker Hubサービスとの連携が登録され、リポジトリへのpushで`docker build`と`docker push`が走るようになる。
あとは`Dockerfile`を書いてプロジェクトのルートに置いてコミットしてプッシュしてやればいいだけ。

Goslingsの`Dockerfile`はこんな感じ。

```dockerfile:Dockerfile
#
# Dockerfile for Goslings
#

FROM java:8
MAINTAINER Kaito Yamada <kaitoy@pcap4j.org>

# Build Goslings.
RUN cd /usr/local/src/ && \
    git clone --recursive -b master git://github.com/kaitoy/goslings.git
WORKDIR /usr/local/src/goslings
RUN ./gradlew --no-daemon build --info 2>&1 | tee build.log

# Generate sample script. (/usr/local/src/goslings/build/script/start.sh)
RUN ./gradlew --no-daemon genScript
RUN chmod +x build/script/start.sh

ENTRYPOINT ["/bin/sh", "/usr/local/src/goslings/build/script/start.sh"]
```

# Amazon EC2 Container ServiceでGoslingsコンテナをホスト
AWSには[EC2 Container Service (ECS)](https://aws.amazon.com/ecs/)というDockerコンテナをホストして管理してくれるサービスがある。
EC2のインスタンス(のクラスタ)の上で動くサービスで、ECS自体は無料で、EC2インスタンスの使用料だけで利用できる。
私は今回初めてAWSのアカウントを作ったので、無料枠を利用してGoslingsコンテナをホストするECSインスタンスを作った。

Qiitaの記事たちに感謝。

* [「AWS is 何」を3行でまとめてみるよ](http://qiita.com/kohashi/items/1bb952313fb695f12577)
* [AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ](http://qiita.com/tmknom/items/303db2d1d928db720888)
* [初心者がAWSでミスって不正利用されて$6,000請求、泣きそうになったお話。](http://qiita.com/mochizukikotaro/items/a0e98ff0063a77e7b694)

ECSはDocker使ったことあるならほとんど迷わず使える感じ。
ひとつひっかかったのは、ECSインスタンスを作るときにEC2インスタンスも作られるので、事前にEC2インスタンスを作っておく必要はないということ。
AWSに慣れている人ならそりゃそうだろということかもしれないけど。

で、「[無料のドメインを取得する（2016年10月）](http://qiita.com/teekay/items/135dc67e39f24997019e)」に従って、[Freenom](http://www.freenom.com/ja/index.html)で`goslings.tk`ドメインを無料で取得し、これまた無料のFreenomネームサーバを利用し、GoslingsコンテナにアクセスするURLを整えた。それが

http://www.goslings.tk/

だ。Goslings as a Service、略してGaaS。
Gitリポジトリ何でもかんでも閲覧されると(ローカルにクローンしてるので)ディスク容量がいくらあっても足らないので、[私のGitHubアカウント](https://github.com/kaitoy)のリポジトリ(e.g. https://github.com/kaitoy/japanese-word-selection )以外は見れないように制限してある。

コミットグラフのビューが大分重いのは、影と物理シミュレーションを切れば大幅に改善するんだろうけど、それだと見て愉快じゃないし切らない。

# 終わりに
Advent Calendarのネタ作りという低いモチベーションで始めたGoslings開発だったが、いろいろ勉強になり、楽しめた。
学んだことは[自前のブログ](https://tbd.kaitoy.xyz/tags/goslings/)にぼちぼち書いていき、復習と備忘録としたい。