Git
Chapters ▾ 2nd Edition

A2.3 Anhang B: Git in deine Anwendungen einbetten - JGit

JGit

Wenn du Git aus einem Java-Programm heraus verwenden möchtest, gibt es eine voll funktionsfähige Git-Bibliothek mit der Bezeichnung JGit. Dabei handelt es sich um eine vergleichsweise vollständige Implementierung von Git, die ausschließlich in Java geschrieben wurde und in der Java-Community weit verbreitet ist. Das JGit-Projekt ist unter dem Dach von Eclipse angesiedelt, und seine Homepage ist unter https://www.eclipse.org/jgit/ zu finden.

Die Einrichtung

Es gibt eine Reihe von Möglichkeiten, dein Projekt mit JGit zu verbinden und damit Code zu schreiben. Die wahrscheinlich einfachste ist die Verwendung von Maven – die Integration wird durch das Hinzufügen des folgenden Snippets zum <dependencies> Tag (dt. Abhängigkeiten) in deiner pom.xml Datei erreicht:

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

Die version wird höchstwahrscheinlich schon weiter fortgeschritten sein, wenn du das hier liest. Unter https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit findest du aktuelle Informationen zum Repository. Sobald dieser Schritt abgeschlossen ist, wird Maven automatisch die von dir benötigten JGit-Bibliotheken herunterladen und verwenden.

Wenn du die binären Abhängigkeiten lieber selbst verwalten möchtest, sind vorkompilierte JGit-Binärdateien unter https://www.eclipse.org/jgit/download erhältlich. Du kannst diese in deinem Projekt einbauen, indem du bspw. folgenden Befehl ausführst:

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 (Basisbefehle)

JGit hat zwei grundsätzliche API-Ebenen: Basis und Standard (plumbing und porcelain). Die Terminologie dafür stammt von Git direkt und JGit ist in etwa die gleichen Bereiche unterteilt. Standardbefehl-APIs bieten ein benutzerfreundliches Front-End für allgemeine Funktionen auf Benutzerebene (die Art von Aktionen, für die ein normaler Benutzer das Git-Befehlszeilen-Tool verwenden würde). Die Basisbefehl-APIs dienen der direkten Interaktion mit Repository-Objekten auf der unteren Anwendungsebene.

Der Ausgangspunkt für die meisten JGit-Sitzungen ist die Klasse Repository. Das erste, was du tun solltest, ist davon eine Instanz zu erstellen. Für ein dateisystem-basiertes Repository (ja, JGit erlaubt andere Speichermodelle) wird das mit dem FileRepositoryBuilder erreicht:

// 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();

Der Builder verfügt über ein flexibles API, um alle notwendigen Funktionen zum Auffinden eines Git-Repositorys bereitzustellen, unabhängig von der Frage, wo dein Programm sich genau befindet. Er kann Umgebungsvariablen verwenden (.readEnvironment()), von einem Ort im Arbeitsverzeichnis starten und suchen (.setWorkTree(…).findGitDir()) oder einfach, wie oben beschrieben, ein bekanntes .git Verzeichnis öffnen.

Sobald du eine Repository Instanz eingerichtet hast, kannst du alles Erdenkliche damit machen. Hier ist eine kurze Aufstellung:

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

Hier gibt es eine Menge zu sagen, lasse uns die Abschnitte nacheinander durchgehen.

Die erste Zeile erhält einen Pointer auf die Referenz master. JGit erfasst automatisch den aktuellen master Ref, der bei refs/heads/master liegt und gibt ein Objekt zurück, mit dem du Informationen über die Referenz fetchen kannst. Du kannst den Namen (.getName()) und entweder das Zielobjekt einer direkten Referenz (.getObjectId()) oder die Referenz, auf die eine symbolische Referenz zeigt (.getTarget()), erhalten. Ref-Objekte werden auch zur Darstellung von Tag-Refs und -Objekten verwendet, so dass du abfragen kannst, ob der Tag „gepeelt“ ist, d.h. ob er auf das endgültige Ziel einer (potenziell langen) Kette von Tag-Objekten zeigt.

Die zweite Zeile ermittelt das Ziel der master Referenz, die als ObjectId-Instanz zurückgegeben wird. ObjectId repräsentiert den SHA-1-Hash eines Objekts, der in der Objektdatenbank von Git möglicherweise vorhanden sein könnte. Die dritte Zeile ist vergleichbar, zeigt aber, wie JGit die Rev-Parse-Syntax behandelt (mehr dazu in Branch Referenzen). Du kannst jeden beliebigen Objektbezeichner übergeben, den Git versteht und JGit gibt entweder eine gültige ObjectId für dieses Objekt oder null zurück.

Die nächsten beiden Zeilen zeigen, wie der Rohinhalt eines Objekts geladen wird. In diesem Beispiel rufen wir ObjectLoader.copyTo() auf, um den Inhalt des Objekts direkt nach stdout zu übertragen. Der ObjectLoader verfügt jedoch auch über Funktionen, um den Typ und die Größe eines Objekts zu lesen und es als Byte-Array zurückzugeben. Für größere Objekte ( bei denen true den Wert .isLarge() zurückgibt) kannst du .openStream() aufrufen, um ein InputStream-ähnliches Objekt zu erhalten, das die Rohdaten des Objekts lesen kann, ohne alles auf einmal in den Arbeitsspeicher zu ziehen.

Die nächsten paar Zeilen beschreiben, was man für die Erstellung eines neuen Branchs benötigt. Wir generieren eine RefUpdate-Instanz, konfigurieren einige Parameter und rufen .update() auf, um die Änderung anzustoßen. Direkt danach folgt der Code zum Löschen desselben Branches. Beachte, dass .setForceUpdate(true) erforderlich ist, damit das funktioniert. Ansonsten gibt der Aufruf von .delete() den Wert REJECTED zurück, und es passiert nichts.

Die letzten Beispielzeilen zeigen, wie der Wert user.name aus den Git-Konfigurationsdateien abgerufen werden kann. Diese Config-Instanz verwendet das Repository, das wir zuvor für die lokale Konfiguration geöffnet haben, erkennt auch die Dateien der Global- und System-Konfiguration. Sie übernimmt automatisch die Werte aus diesen Dateien.

Das ist nur ein kleiner Ausschnitt der vollständigen API für die Basisbefehle. Es sind noch viele weitere Methoden und Klassen verfügbar. Auch die Art und Weise, wie JGit Fehler behandelt, wird hier nicht aufgezeigt. Das geschieht nämlich über die Verwendung von Exceptions. JGit-APIs werfen manchmal Standard-Java-Exceptions aus (wie IOException), aber es gibt eine Vielzahl von JGit-spezifischen Exception-Typen, die ebenfalls zur Verfügung stehen (wie z.B. NoRemoteRepositoryException, CorruptObjectException und NoMergeBaseException).

Porcelain (Standardbefehle)

Die Basisbefehl-APIs sind fast komplett. Es kann allerdings umständlich sein, sie hintereinander aufzureihen, um allgemeine Aufgaben zu erfüllen, wie das Hinzufügen einer Datei zum Index oder ein neuer Commit. JGit bietet einen erweiterten Satz von APIs, die dabei helfen können. Der Ausgangspunkt für diese APIs ist die Klasse Git:

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

Die Git-Klasse verfügt über einen feinen Satz von high-level Builder-Style-Methoden, die zur Erstellung einiger ziemlich komplexer Verhaltensweisen verwendet werden können. Schauen wir uns ein Beispiel an – wir wollen so etwas wie git ls-remote machen:

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());
}

Das ist ein typisches Muster mit der Git-Klasse. Die Methoden geben ein Befehlsobjekt zurück, mit dem du Methodenaufrufe verketten kannst, um Parameter zu setzen, die beim Aufruf von .call() ausgeführt werden. Hier befragen wir den origin Remote nach Tags, nicht nach Heads. Beachten Sie auch die Verwendung des Objekts CredentialsProvider zur Authentifizierung.

Viele andere Befehle sind über die Git-Klasse verfügbar, einschließlich, aber nicht beschränkt auf add, blame, commit, clean, push, rebase, revert und reset.

Weiterführende Informationen

Das ist lediglich ein kleiner Ausschnitt der umfassenden Funktionalität von JGit. Wenn du Interesse hast und mehr erfahren möchtest, findest du hier Informationen und Anregungen:

scroll-to-top