Git 🌙
Chapters â–Ÿ 2nd Edition

A2.3 Annexe 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 https://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 https://www.eclipse.org/jgit/download 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 https://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 :

scroll-to-top