章 ▾ 第2版

A2.3 付録B: アプリケーションへのGit組み込み - JGit

JGit

Javaプログラム内からGitを使用したい場合、JGitと呼ばれるフル機能のGitライブラリがあります。JGitはJavaでネイティブに書かれた比較的にフル機能のGit実装であり、Javaコミュニティで広く使われています。JGitプロジェクトはEclipseの傘下にあり、その本拠地はhttps://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は進んでいる可能性が高いです。最新のリポジトリ情報についてはhttps://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgitを確認してください。このステップが完了すると、Mavenは必要なJGitライブラリを自動的に取得して使用します。

バイナリ依存関係を自分で管理したい場合は、https://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には、PlumbingとPorcelainという2つの基本的なAPIレベルがあります。これらの用語はGit自体に由来しており、JGitもほぼ同じ種類の領域に分けられます。Porcelain APIは、一般的なユーザーレベルのアクション(通常のユーザーがGitコマンドラインツールで行うようなこと)のための使いやすいフロントエンドであり、Plumbing APIは、低レベルのリポジトリオブジェクトと直接やり取りするためのものです。

ほとんどのJGitセッションの出発点はRepositoryクラスであり、まずそのインスタンスを作成することになります。ファイルシステムベースのリポジトリの場合(JGitは他のストレージモデルもサポートしています)、これはFileRepositoryBuilderを使用して実現されます

// Create a new repository
Repository newlyCreatedRepo = FileRepositoryBuilder.create(
    new File("/tmp/new_repo/.git"));
newlyCreatedRepo.create();

// Open an existing repository
Repository existingRepo = new FileRepositoryBuilder()
    .setGitDir(new File("my_repo/.git"))
    .build();

このビルダーには、プログラムがGitリポジトリの正確な場所を知っているかどうかにかかわらず、Gitリポジトリを見つけるために必要なすべてのものを提供する流暢なAPIがあります。環境変数(.readEnvironment())を使用したり、作業ディレクトリ内の場所から検索を開始したり(.setWorkTree(…).findGitDir())、あるいは上記のように既知の.gitディレクトリを開いたりすることができます。

Repositoryインスタンスを取得したら、それを使ってあらゆる種類の操作を行うことができます。いくつかの簡単な例を挙げます

// Get a reference
Ref master = repo.getRef("master");

// Get the object the reference points to
ObjectId masterTip = master.getObjectId();

// Rev-parse
ObjectId obj = repo.resolve("HEAD^{tree}");

// Load raw object contents
ObjectLoader loader = repo.open(masterTip);
loader.copyTo(System.out);

// Create a branch
RefUpdate createBranch1 = repo.updateRef("refs/heads/branch1");
createBranch1.setNewObjectId(masterTip);
createBranch1.update();

// Delete a branch
RefUpdate deleteBranch1 = repo.updateRef("refs/heads/branch1");
deleteBranch1.setForceUpdate(true);
deleteBranch1.delete();

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

ここにはかなりの処理が含まれているので、セクションごとに見ていきましょう。

最初の行は、masterリファレンスへのポインタを取得します。JGitは自動的にrefs/heads/masterに存在する実際のmasterリファレンスを取得し、そのリファレンスに関する情報をフェッチできるオブジェクトを返します。名前(.getName())、直接リファレンスのターゲットオブジェクト(.getObjectId())、またはシンボリックリファレンスが指すリファレンス(.getTarget())を取得できます。Refオブジェクトはタグのリファレンスやオブジェクトを表すためにも使用され、タグが「剥がされている(peeled)」かどうか、つまり(潜在的に長い)タグオブジェクトの連鎖の最終ターゲットを指しているかどうかを尋ねることができます。

2行目はmasterリファレンスのターゲットを取得し、これはObjectIdインスタンスとして返されます。ObjectIdはオブジェクトのSHA-1ハッシュを表し、Gitのオブジェクトデータベースに存在する可能性も、存在しない可能性もあります。3行目も同様ですが、JGitがrev-parse構文をどのように処理するかを示しています(詳細についてはブランチリファレンスを参照してください)。Gitが理解する任意のオブジェクト指定子を渡すことができ、JGitはそのオブジェクトに対する有効なObjectId、またはnullを返します。

次の2行は、オブジェクトの生のコンテンツをロードする方法を示しています。この例では、ObjectLoader.copyTo()を呼び出してオブジェクトのコンテンツを直接標準出力にストリームしますが、ObjectLoaderにはオブジェクトのタイプとサイズを読み取ったり、バイト配列として返したりするメソッドもあります。大きなオブジェクトの場合(.isLarge()trueを返す場合)、.openStream()を呼び出して、生のオブジェクトデータをすべて一度にメモリにロードすることなく読み取ることができるInputStreamのようなオブジェクトを取得できます。

次の数行は、新しいブランチを作成するために必要なことを示しています。RefUpdateインスタンスを作成し、いくつかのパラメータを設定し、.update()を呼び出して変更をトリガーします。これに直接続いて、同じブランチを削除するコードがあります。.setForceUpdate(true)がこの操作を行うために必要であることに注意してください。そうしないと、.delete()呼び出しはREJECTEDを返し、何も起こりません。

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

これはPlumbing APIのほんの一部にすぎません。他にも多くのメソッドやクラスが利用可能です。また、JGitがエラーを処理する方法(例外の使用)もここには示されていません。JGit APIは標準的なJava例外(例: IOException)をスローすることがありますが、JGit固有の例外タイプも多数提供されています(例: NoRemoteRepositoryExceptionCorruptObjectExceptionNoMergeBaseException)。

Porcelain

Plumbing APIはかなり完成されていますが、ファイルをインデックスに追加したり、新しいコミットを作成したりといった一般的な目標を達成するためにそれらを連結するのは煩雑になることがあります。JGitはこれを支援するためのより高レベルのAPIセットを提供しており、これらのAPIへのエントリーポイントはGitクラスです

Repository repo;
// construct repo...
Git git = new Git(repo);

Gitクラスには、かなり複雑な動作を構築するために使用できる、優れた高レベルのビルダースタイルのメソッドセットがあります。例として、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()を呼び出すときに実行されます。このケースでは、originリモートにタグを要求していますが、ヘッドは要求していません。また、認証のためにCredentialsProviderオブジェクトを使用していることにも注目してください。

Gitクラスを通じて、addblamecommitcleanpushrebaserevertresetなど、他にも多くのコマンドが利用可能です。

さらに読む

もし興味があり、さらに学びたいのであれば、以下の情報源とインスピレーションを探してみてください

  • 公式のJGit APIドキュメントはhttps://www.eclipse.org/jgit/documentationで見つけることができます。これらは標準のJavadocなので、お気に入りのJVM IDEにローカルでインストールすることも可能です。

  • https://github.com/centic9/jgit-cookbookにあるJGit Cookbookには、JGitで特定のタスクを実行する方法の多くの例が記載されています。

scroll-to-top