Git
Chapters ▾ 2nd Edition

A2.3 Appendix B: Gitをあなたのアプリケーションに組み込む - JGit

JGit

JavaのプログラムからGitを使いたい場合、十分な機能を備えたGitのライブラリであるJGitが利用できます。 JGitは、ネイティブJavaによるGitの実装です。Gitのほぼ全機能を備えており、Javaコミュニティで広く使われています。 JGitはEclipse傘下のプロジェクトで、ホームページは http://www.eclipse.org/jgit です。

セットアップする

JGitをあなたのプロジェクトへ追加して、コードを書き始めるには、いくつかの方法があります。 おそらく最も簡単なのはMavenを使う方法です。次のスニペットを pom.xml の <dependencies> タグに追加すれば、統合が行えます。

<dependency>
    <groupId>org.eclipse.jgit</groupId>
    <artifactId>org.eclipse.jgit</artifactId>
    <version>3.5.0.201409260305-r</version>
</dependency>

皆さんがこれを読んでいる時には、おそらく version の番号はもっと進んでいるでしょうから、 http://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit で最新のリポジトリの情報を確認してください。 このステップが完了したら、以降は必要なJGitライブラリの取得と使用をMavenが自動的に行ってくれます。

バイナリの依存関係を自前で管理したい場合は、ビルド済みのJGitのバイナリが http://www.eclipse.org/jgit/download から取得できます。 JGitをプロジェクトへ組み込むには、次のようなコマンドを実行します。

javac -cp .:org.eclipse.jgit-3.5.0.201409260305-r.jar App.java
java -cp .:org.eclipse.jgit-3.5.0.201409260305-r.jar App

Plumbing

JGitのAPIには大きく2 つのレベルがあり、それぞれ配管(Plumbing)および磁器(Porcelain)と呼ばれています。 これらはGit由来の用語で、JGitでもだいたいGitと同じように区分けされています。Porcelain APIは、使いやすいフロントエンドで、一般的なユーザレベルの処理(普通のユーザがGitのコマンドラインツールを使って行うような処理)を行います。一方、Plumbing APIでは、低レベルなリポジトリオブジェクトを直接操作します。

JGitセッションでは多くの場合、Repository クラスを開始点とします。この場合、まず最初に行いたい処理は Repository クラスのインスタンスの作成です。 ファイルシステムベースのリポジトリなら(そう、JGitでは他のストレージモデルも扱えます)、これは FileRepositoryBuilder を使って行います。

// 新しくリポジトリを作成する。存在するパスを指定すること
Repository newlyCreatedRepo = FileRepositoryBuilder.create(
    new File("/tmp/new_repo/.git"));

// 既存のリポジトリを開く
Repository existingRepo = new FileRepositoryBuilder()
    .setGitDir(new File("my_repo/.git"))
    .build();

FileRepositoryBuilder は洗練されたAPIが備わっており、リポジトリの場所が分かっているにしろいないにしろ、Gitのリポジトリを見つけるのに必要な処理はすべて提供されています。 ここでは、環境変数を使う (.readEnvironment())、作業ディレクトリ中のどこかを起点として検索をする(.setWorkTree(…).findGitDir())、上の例のように単に既知の .git ディレクトリを開く、といった方法が使用できます。

Repository インスタンスを取得したら、そこを起点にあらゆる種類の処理が行えます。 簡単なサンプルプログラムを次に示します。

// 参照を取得する
Ref master = repo.getRef("master");

// 参照の指すオブジェクトを取得する
ObjectId masterTip = master.getObjectId();

// Rev-parse文法を使う
ObjectId obj = repo.resolve("HEAD^{tree}");

// オブジェクトの生の内容をロードする
ObjectLoader loader = repo.open(masterTip);
loader.copyTo(System.out);

// ブランチを作成する
RefUpdate createBranch1 = repo.updateRef("refs/heads/branch1");
createBranch1.setNewObjectId(masterTip);
createBranch1.update();

// ブランチを削除する
RefUpdate deleteBranch1 = repo.updateRef("refs/heads/branch1");
deleteBranch1.setForceUpdate(true);
deleteBranch1.delete();

// 設定値
Config cfg = repo.getConfig();
String name = cfg.getString("user", null, "name");

ここでは数多くの処理が行われています。1セクションずつ順に見て行きましょう。

最初の行では master 参照へのポインタを取得しています。 JGitは refs/heads/master にある 実際の master参照を自動的に取得してオブジェクトを返します。このオブジェクトを使えば、参照についての情報を取得できます。 ここでは、名前 (.getName()) と、直接参照のターゲットオブジェクト (.getObjectId()) またはシンボリック参照の指す参照 (.getTarget()) のいずれかを取得できます。 参照オブジェクトは、タグ参照やオブジェクトを表すのにも使われるので、タグが “peeled” か問い合わせられるようになっています。つまり、参照がタグオブジェクトの(ひょっとすると長い)列の最後のターゲットを指しているか問い合わせることができます。

2行目では、 master 参照の指す先を取得して、ObjectIdインスタンスの形で返します。 ObjectIdはGitのオブジェクトデータベース中にある(または、データベース中にない)オブジェクトのSHA-1ハッシュを表しています。 3行目は似たような処理ですが、ここではJGitがrev-parse文法(詳細は ブランチの参照 を参照)を処理する方法を示しています。Gitが解釈できる任意のオブジェクト指定子を渡すことができ、JGitはそのオブジェクトのvalidなObjectIdか null のどちらかを返します。

次の2行はオブジェクトの生の内容をロードする方法を示しています。 このサンプルでは ObjectLoader.copyTo() を使ってオブジェクトの内容を標準出力へ直接流し込んでいますが、ObjectLoaderにはオブジェクトの型やサイズを返すメソッド、オブジェクトの内容をbyte型配列として返すメソッドもあります。 大きいオブジェクト(.isLarge()true を返すようなオブジェクト)に対しては、 .openStream() を使えば、InputStream のようなオブジェクトを取得でき、データ全体をメモリ上に置くことなく、生のデータを読み込めます。

次の数行は、新しいブランチを作成するために必要な処理を示しています。 ここではRefUpdateのインスタンスを作成し、パラメータを設定した上で、.update() を呼んで変更を適用しています。 続く数行は同じブランチを削除するコードです。 なお、この処理では .setForceUpdate(true) が必須です。さもなくば、 .delete() を呼んでも REJECTED が返り、何も変更されません。

最後の例は、Gitの設定ファイルから user.name の値を取得する方法を示しています。 このConfigインスタンスは、ローカル設定のために前に開いたリポジトリを使用しますが、グローバル設定ファイルやシステム設定ファイルからも自動的に値を読み込みます。

ここで示したサンプルは、Plumbing APIのごく一部であり、利用可能なメソッドやクラスは他にもたくさんあります。 ここで取り上げなかった内容としては、他にJGitのエラー処理があります。エラー処理は例外を通じて行われます。 JGitのAPIからthrowされる例外には、Java標準の例外(IOException など)の他にも、JGit固有の各種例外(NoRemoteRepositoryException, CorruptObjectException, NoMergeBaseException など)があります。

Porcelain

Plumbing APIは網羅的ではありますが、その機能を繋ぎ合わせて一般的な作業(インデックスにファイルを追加したり、新しくコミットを作成したり)を遂行するのは、場合によっては面倒です。 JGitは、この点を手助けする高いレベルのAPIを提供しています。これらのAPIへのエントリポイントは、 Git クラスです。

Repository repo;
// repoオブジェクトの作成……
Git git = new Git(repo);

Gitクラスは、洗練された高レベルの builder スタイルのメソッドを備えています。これは、非常に複雑な処理を組み立てる際に利用できます。 それでは例を見てみましょう。ここでは git ls-remote のような処理を行っています。

CredentialsProvider cp = new UsernamePasswordCredentialsProvider("username", "p4ssw0rd");
Collection<Ref> remoteRefs = git.lsRemote()
    .setCredentialsProvider(cp)
    .setRemote("origin")
    .setTags(true)
    .setHeads(false)
    .call();
for (Ref ref : remoteRefs) {
    System.out.println(ref.getName() + " -> " + ref.getObjectId().name());
}

これはGitクラスを使うときによくあるパターンです。メソッドがコマンドオブジェクトを返すので、パラメータを設定するメソッドチェーンを繋げていき、最後に .call() メソッドを呼び出すとそれらがまとめて実行されます。 このケースでは、タグを取得する際に、HEADではなく`origin`リモートを要求しています。 また、CredentialsProvider オブジェクトを使って認証を行っていることにも注意してください。

Gitクラスからは addblamecommitcleanpushrebaserevertreset を含め、他にも多くのコマンドが使用できます。

参考文献

この節で示したのは、JGitの機能のごく一部です。 興味が湧いた、もっと知りたいということなら、情報は次の場所から探せます。