Chapters ▾ 2nd Edition

A2.3 Bilaga B: Bädda in Git i dina applikationer - JGit

JGit

Om du vill använda Git inifrån ett Java‑program finns ett fullfjädrat Git‑bibliotek som heter JGit. JGit är en relativt komplett implementation av Git skriven i Java och används i stor utsträckning i Java‑världen. JGit‑projektet ligger under Eclipse‑paraplyet, och dess hem finns på https://projects.eclipse.org/projects/technology.jgit.

Kom igång

Det finns flera sätt att koppla ditt projekt till JGit och börja skriva kod mot det. Det enklaste är förmodligen att använda Maven – integrationen görs genom att lägga till följande kodsnutt i taggen <dependencies> i din pom.xml‑fil:

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

version kommer sannolikt att ha gått vidare när du läser detta; se https://repo1.maven.org/maven2/org/eclipse/jgit/org.eclipse.jgit/ för uppdaterad information. När detta steg är klart hämtar och använder Maven automatiskt de JGit‑bibliotek du behöver.

Om du hellre vill hantera binära beroenden själv finns förbyggda JGit‑binärer på https://projects.eclipse.org/projects/technology.jgit/downloads. Du kan bygga in dem i ditt projekt genom att köra ett kommando som detta:

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

Lågnivådel

JGit har två grundläggande API‑nivåer: lågnivådel och användardel. Terminologin kommer från Git självt, och JGit är uppdelat i ungefär samma typer av områden: användar‑API:er (porcelain) är ett vänligt gränssnitt för vanliga användaråtgärder (sådant en normal användare skulle använda Gits kommandoradsverktyg för), medan lågnivå‑API:erna (plumbing) används för att interagera direkt med kodförrådets lågnivåobjekt.

Startpunkten för de flesta JGit‑sessioner är klassen Repository, och det första du vill göra är att skapa en instans av den. För ett filsystemsbaserat kodförråd (ja, JGit tillåter andra lagringsmodeller) görs detta med 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();

Byggaren har ett flytande API för att ange allt den behöver för att hitta ett Git‑kodförråd, oavsett om ditt program vet exakt var det ligger eller ej. Den kan använda miljövariabler (.readEnvironment()), starta från en plats i arbetskatalogen och söka (.setWorkTree(…).findGitDir()), eller helt enkelt öppna en känd .git‑katalog som ovan.

När du har en Repository‑instans kan du göra alla möjliga saker med den. Här är ett snabbt smakprov:

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

Det händer ganska mycket här, så låt oss gå igenom det steg för steg.

Den första raden hämtar en pekare till referensen master. JGit hämtar automatiskt den faktiska master‑refen, som ligger på refs/heads/master, och returnerar ett objekt som låter dig hämta information om referensen. Du kan få namnet (.getName()) och antingen målobjektet för en direkt referens (.getObjectId()) eller referensen som en symbolisk ref pekar på (.getTarget()). Ref‑objekt används också för att representera tagg‑refs och objekt, så du kan fråga om taggen är “peeled”, vilket betyder att den pekar på det slutliga målet i en (potentiellt lång) kedja av taggobjekt.

Den andra raden hämtar målet för master‑referensen, som returneras som en ObjectId‑instans. ObjectId representerar SHA‑1‑hashen för ett objekt, som kanske finns eller inte finns i Gits objektdatabas. Den tredje raden är liknande men visar hur JGit hanterar rev‑parse‑syntaxen (för mer om detta, se Grenreferenser); du kan skicka vilken objektspecificerare som helst som Git förstår, och JGit returnerar antingen en giltig ObjectId för objektet eller null.

De nästa två raderna visar hur man laddar det råa innehållet i ett objekt. I det här exemplet anropar vi ObjectLoader.copyTo() för att strömma objektets innehåll direkt till stdout, men ObjectLoader har också metoder för att läsa typ och storlek på ett objekt, samt returnera det som en byte‑array. För stora objekt (där .isLarge() returnerar true) kan du anropa .openStream() för att få ett InputStream‑liknande objekt som kan läsa rådata utan att dra in allt i minnet på en gång.

De följande raderna visar vad som krävs för att skapa en ny gren. Vi skapar en RefUpdate‑instans, konfigurerar vissa parametrar och anropar .update() för att trigga ändringen. Direkt efter detta följer koden som tar bort samma gren. Observera att .setForceUpdate(true) krävs för att detta ska fungera; annars returnerar .delete()‑anropet REJECTED, och inget händer.

Det sista exemplet visar hur du hämtar värdet user.name från Git‑konfigurationsfilerna. Denna Config‑instans använder kodförrådet vi öppnade tidigare för lokal konfiguration, men upptäcker automatiskt de globala och systemomfattande konfigurationsfilerna och läser värden från dem också.

Det här är bara ett litet smakprov av hela lågnivå‑API:t; det finns många fler metoder och klasser. Inte heller visat här är hur JGit hanterar fel, vilket sker genom undantag. JGit‑API:er kastar ibland vanliga Java‑undantag (som IOException), men det finns också en uppsättning JGit‑specifika undantagstyper (som NoRemoteRepositoryException, CorruptObjectException och NoMergeBaseException).

Användardel

Lågnivå‑API:erna är ganska kompletta, men det kan vara krångligt att kedja ihop dem för att uppnå vanliga mål, som att lägga till en fil i indexet eller göra en ny incheckning. JGit erbjuder en uppsättning API:er på högre nivå för att hjälpa till med detta, och ingångspunkten till dessa API:er är klassen Git:

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

Klassen Git har en bra uppsättning builder-liknande metoder på hög nivå som kan användas för att konstruera ganska komplexa beteenden. Låt oss titta på ett exempel – att göra något i stil med 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());
}

Det här är ett vanligt mönster med Git‑klassen; metoderna returnerar ett kommandoobjekt som låter dig kedja metodanrop för att sätta parametrar, vilka körs när du anropar .call(). I det här fallet frågar vi fjärrkodförrådet origin efter taggar, men inte heads. Notera också användningen av ett CredentialsProvider‑objekt för autentisering.

Många andra kommandon finns tillgängliga via Git‑klassen, inklusive men inte begränsat till add, blame, commit, clean, push, rebase, revert och reset.

Vidare läsning

Det här är bara ett litet smakprov av JGits fulla förmågor. Om du är intresserad och vill lära dig mer finns här var du kan leta efter information och inspiration: