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

  • 13
    Like
  • 0
    Comment

goslings-logo.png

Git Advent Calendar 2016JGitネタで参加しようと思い立ち登録したはいいものの、JGitの使い方なんてものは既に投稿されているし、JGitを使ったことはなかったのでノウハウなんか書けないし、需要もあるとは思えない。

さてどうしようかということで、以前から啓蒙しているGitのオブジェクトモデルを学べるアプリなんか面白いかと思い、JGitを使ってGoslingsというものを作った。ソースはGitHubに置いた。

Goslingsとは

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

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

geese.JPG

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

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

Goslingsの使い方

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

goslings-form.png

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

graph.png

このビューの見方は以前書いた記事のスライドと同じ。

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

Goslingsのアーキテクチャとか

Goslingsのプロジェクト構成は以下の様になっている。

project.png

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

クライアント側(goslings-client)はJavaScript(ES2015 + async/await)のSPAで、禁jQuery縛り。
React + Reduxというのをやってみたかったが、なんか大げさだしそこまで時間がとれなそうだったので、フレームワークなしで作った。ので、
You Don't Need jQuery」とにらめっこしながら書いた。
Gitのコミットグラフの描画には、vis.jsを使った。
Stack Overflowの回答から雰囲気で選んだけど、やりたかったことが全部できて、見た目もよかったのでよかった。
パッケージマネージャには話題のYarnを、モジュールバンドラーにはwebpackを使用。

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

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

JGitとは

ここで唐突にJGitについて書く。
この記事はGit Advent Calendarに投稿したものだし、JGitタグも付けたし、Goslingsの要素技術の中でもJGitだけはちゃんと触れておいた方がいいと思うのだ。

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

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

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を取得するコードだが、一貫性がなく分かりにくい。

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を備えているのだが、これを使ってブランチのリストを取得するコードは以下の様になる。

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」という節。
コミットの子を探す? 妙なことするなぁとひっかかったが、案の定コミットのを再帰的に探す操作の紹介だった。


JavaとGitの初心者が設計開発したんだろうか、JGit。

Goslings Dockerコンテナ

Goslingsの話に戻る。

Goslingsサーバを実行するDockerコンテナイメージも作ってDocker Hubに上げた。これだ

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

Goslingsの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)というDockerコンテナをホストして管理してくれるサービスがある。
EC2のインスタンス(のクラスタ)の上で動くサービスで、ECS自体は無料で、EC2インスタンスの使用料だけで利用できる。
私は今回初めてAWSのアカウントを作ったので、無料枠を利用してGoslingsコンテナをホストするECSインスタンスを作った。

Qiitaの記事たちに感謝。

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

で、「無料のドメインを取得する(2016年10月)」に従って、Freenomgoslings.tkドメインを無料で取得し、これまた無料のFreenomネームサーバを利用し、GoslingsコンテナにアクセスするURLを整えた。それが

http://www.goslings.tk/

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

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

終わりに

Advent Calendarのネタ作りという低いモチベーションで始めたGoslings開発だったが、いろいろ勉強になり、楽しめた。
学んだことは自前のブログにぼちぼち書いていき、復習と備忘録としたい。