Git
Chapters ▾ 2nd Edition

A2.3 Appendix B: Embarquer Git dans vos applications - JGit

JGit

Si vous voulez utiliser Git depuis un programme Java, il existe une bibliothèque complète appelée JGit. JGit est une réalisation relativement complète de Git écrite nativement en Java etelle est largement utilisée dans la communauté Java. Le projet JGit est développé sous l’égide d’Eclipse et son site se trouve sur http://www.eclipse.org/jgit.

Mise en place

Il y a différents moyens de connecter votre projet à JGit et de commencer à l’utiliser dans votre code. La manière probablement la plus facile consiste à utiliser Maven – on réalise l’intégration en ajoutant la section suivante sous la balise <dependencies> de votre fichier pom.xml :

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

La version aura très certainement évolué lorsque vous lirez ces lignes ; vérifiez http://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit pour une information de version mise à jour. Une fois cette étape accomplie, Maven va automatiquement récupérer et utiliser les bibliothèques JGit dont vous aurez besoin.

Si vous préférez gérer les dépendances binaires par vous-même, des binaires JGit pré-construits sont disponibles sur http://www.eclipse.org/jgit/download. Vous pouvez les inclure dans votre projet en lançant une commande telle que :

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

Plomberie

JGit propose deux niveaux généraux d’interfaçage logiciel : plomberie et porcelaine. La terminologie de ces niveaux est directement calquée sur celle de Git lui-même et JGit est partitionné globalement de la même manière : les APIs de porcelaine sont une interface de haut niveau pour des interactions de niveau utilisateur (le genre de choses qu’un utilisateur normal ferait en utilisant la ligne de commande), tandis que les APIs de plomberie permettent d’interagir directement avec les objets de bas niveau du dépôt.

Le point de départ pour la plupart des sessions JGit est la classe Repository et la première action consiste à créer une instance de celle-ci. Pour un dépôt basé sur un système de fichier (hé oui, JGit permet d’autre modèles de stockage), cela passe par l’utilisation d’un FileRepositoryBuilder :

// Creer un nouveau depot
Repository newlyCreatedRepo = FileRepositoryBuilder.create(
    new File("/tmp/new_repo/.git"));
newlyCreatedRepo.create();

// Ouvrir un depot existant
Repository existingRepo = new FileRepositoryBuilder()
    .setGitDir(new File("my_repo/.git"))
    .build();

Le constructeur contient une API souple qui fournit tout ce dont il a besoin pour trouver un dépôt Git, que votre programme sache ou ne sache pas exactement où il est situé. Il peut utiliser des variables d’environnement (.readEnvironment()), démarrer depuis le répertoire de travail et chercher (setWorkTree(…).findGitDir()) ou juste ouvrir un répertoire .git connu, comme ci-dessus.

Une fois muni d’une instance de Repository, vous pouvez faire toutes sortes de choses avec. En voici un échantillon rapide :

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

// acceder a l'objet pointe par une reference
ObjectId masterTip = master.getObjectId();

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

// Charger le contenu brut d'un objet
ObjectLoader loader = repo.open(masterTip);
loader.copyTo(System.out);

// Creer une branche
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");

Il y a pas mal de choses mises en œuvre ici, donc nous allons détailler chaque section.

La première ligne récupère un pointeur sur la référence master. JGit récupère automatiquement la référence master réelle qui se situe dans refs/heads/master et retourne un objet qui vous permet de récupérer des informations sur cette référence. Vous pouvez récupérer le nom (.getName()) ou bien l’objet cible de la référence directe (.getObjectId()), ou encore la référence pointée par une référence symbolique (.getTarget()). Les objets de référence sont aussi utilisés pour représenter les références d’étiquette ou des objets, donc vous pouvez demander si l’étiquette est « pelée », ce qui signifie qu’elle pointe sur une cible finale d’une série (potentiellement longue) d’objets étiquettes.

La seconde ligne donne la cible de la référence master qui est retournée comme une instance d’ObjectId. ObjectId représente l’empreinte SHA-1 d’un objet qui peut exister ou non dans la base de données des objets de Git. La troisième ligne est similaire, mais elle montre comment JGit gère la syntaxe « rev-parse » (pour plus d’information, voir Références de branches). Vous pouvez passer n’importe quel spécificateur d’objet que Git comprend et JGit retournera un ObjectId valide ou bien null.

Les deux lignes suivantes montrent comment charger le contenu brut d’un objet. Dans cet exemple, nous appelons ObjectLoader.copyTo() pour rediriger le contenu de l’objet directement sur la sortie standard, mais ObjectLoader dispose aussi de méthodes pour lire le type et la taille d’un objet et le retourner dans un tableau d’octets. Pour les gros objets (pour lesquels .isLarge() renvoie true), vous pouvez appeler .openStream() pour récupérer un objet similaire à InputStream qui peut lire l’objet brut sans le tirer intégralement en mémoire vive.

Les quelques lignes suivantes montrent ce qui est nécessaire pour créer une nouvelle branche. Nous créons une instance de RefUpdate, configurons quelques paramètres et appelons .update() pour déclencher la modification. Le code pour effacer cette branche suit juste après. Notez que .setForceUpdate(true) est nécessaire pour que cela fonctionne ; sinon l’appel à .delete() retourne REJECTED et il ne se passera rien.

Le dernier exemple montre comment récupérer la valeur user.name depuis les fichiers de configuration de Git. Cette instance de Config utilise le dépôt que nous avons ouvert plus tôt pour la configuration locale, mais détectera automatiquement les fichiers de configuration globale et système et y lira aussi les valeurs.

Ceci n’est qu’un petit échantillon de toute l’API de plomberie ; il existe beaucoup d’autre méthodes et classes. Ici, nous ne montrons pas non plus comment JGit gère les erreurs, au moyen d’exceptions. Les APIs JGit lancent quelques fois des exceptions Java standard (telles que IOException), mais il existe aussi une liste de types d’exception spécifiques à JGit (tels que NoRemoteRepositoryException, CorruptObjectException et NoMergeBaseException).

Porcelaine

Les APIs de plomberie sont plutôt complètes, mais il peut s’avérer lourd de les enchaîner pour des activités fréquentes, telles que l’ajout de fichier à l’index ou la validation. JGit fournit un ensemble de plus haut niveau d’APIs pour simplifier celles-ci et le point d’entrée pour ces APIs est la classe Git :

Repository repo;
// construit le depot...
Git git = new Git(repo);

La classe Git propose un joli ensemble de méthodes de haut niveau de style builder qui peuvent être utilisées pour construire des comportements assez complexes. Voyons un exemple, tel que recréer quelque chose comme 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());
}

C’est la structure habituelle avec la classe Git ; les méthodes renvoient un objet commande qui permet d’enchaîner les appels de paramétrage qui sont finalement exécutés par l’appel à .call(). Dans notre cas, nous interrogeons le dépôt distant origin sur ses étiquettes, et non sur ses sommets de branches. Notez aussi l’utilisation d’un objet CredentialsProvider pour l’authentification.

De nombreuses autres commandes sont disponibles au travers de la classe Git, dont entre autres add, blame, commit, clean, push, rebase, revert et reset.

Pour aller plus loin

Tout ceci n’est qu’un mince échantillon de toutes les capacités de JGit. Si vous êtes intéressé et souhaitez en savoir plus, voici des liens vers plus d’information et d’inspiration :