Chapters ▾ 2nd Edition

A2.2 Bilaga B: Bädda in Git i dina applikationer - Libgit2

Libgit2

Ett annat alternativ du har tillgängligt är att använda Libgit2. Libgit2 är en beroendefri implementation av Git, med fokus på ett trevligt API för användning i andra program. Du hittar det på https://libgit2.org.

Låt oss först titta på hur C‑API:t ser ut. Här är en blixtgenomgång:

// Open a repository
git_repository *repo;
int error = git_repository_open(&repo, "/path/to/repository");

// Dereference HEAD to a commit
git_object *head_commit;
error = git_revparse_single(&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit*)head_commit;

// Print some of the commit's properties
printf("%s", git_commit_message(commit));
const git_signature *author = git_commit_author(commit);
printf("%s <%s>\n", author->name, author->email);
const git_oid *tree_id = git_commit_tree_id(commit);

// Cleanup
git_commit_free(commit);
git_repository_free(repo);

De första raderna öppnar ett Git‑kodförråd. Typen git_repository representerar ett handtag till ett kodförråd med en mellanlagring i minnet. Det här är den enklaste metoden, när du känner till den exakta sökvägen till kodförrådets arbetskatalog eller .git‑mapp. Det finns också git_repository_open_ext med alternativ för sökning, git_clone och vänner för att göra en lokal klon av ett fjärrkodförråd, samt git_repository_init för att skapa ett helt nytt kodförråd.

Den andra kodbiten använder rev‑parse‑syntax (se Grenreferenser för mer om detta) för att hämta incheckningen som HEAD till slut pekar på. Den returnerade typen är en git_object‑pekare, som representerar något som finns i Git‑objektdatabasen för ett kodförråd. git_object är i själva verket en “förälder”‑typ för flera olika typer av objekt; minneslayouten för var och en av “barn”‑typerna är densamma som för git_object, så du kan säkert kasta till rätt typ. I detta fall skulle git_object_type(commit) returnera GIT_OBJ_COMMIT, så det är säkert att kasta till en git_commit‑pekare.

Nästa kodstycke visar hur du kommer åt incheckningens egenskaper. Den sista raden här använder typen git_oid; detta är Libgit2:s representation av en SHA‑1‑summa.

Av det här exemplet framträder ett par mönster:

  • Om du deklarerar en pekare och skickar en referens till den in i ett Libgit2‑anrop kommer det anropet sannolikt att returnera en heltalskod för fel. Ett värde på 0 betyder att allt gick bra; allt mindre är ett fel.

  • Om Libgit2 fyller i en pekare åt dig ansvarar du för att frigöra den.

  • Om Libgit2 returnerar en const‑pekare från ett anrop behöver du inte frigöra den, men den blir ogiltig när objektet den tillhör frigörs.

  • Att skriva C är lite smärtsamt.

Det sista betyder att det inte är särskilt troligt att du skriver C när du använder Libgit2. Lyckligtvis finns det en rad språkspecifika bindningar som gör det ganska enkelt att arbeta med Git‑kodförråd från ditt språk och din miljö. Låt oss titta på exemplet ovan skrivet med Ruby‑bindningarna för Libgit2, som heter Rugged och finns på https://github.com/libgit2/rugged.

repo = Rugged::Repository.new('path/to/repository')
commit = repo.head.target
puts commit.message
puts "#{commit.author[:name]} <#{commit.author[:email]}>"
tree = commit.tree

Som du ser är koden mycket mindre rörig. För det första använder Rugged undantag; det kan kasta saker som ConfigError eller ObjectError för att signalera fel. För det andra finns det ingen explicit frigöring av resurser, eftersom Ruby är skräpsamlande. Låt oss titta på ett lite mer komplicerat exempel: att skapa en incheckning från grunden

blob_id = repo.write("Blob contents", :blob) # (1)

index = repo.index
index.read_tree(repo.head.target.tree)
index.add(:path => 'newfile.txt', :oid => blob_id) # (2)

sig = {
    :email => "bob@example.com",
    :name => "Bob User",
    :time => Time.now,
}

commit_id = Rugged::Commit.create(repo,
    :tree => index.write_tree(repo), # (3)
    :author => sig,
    :committer => sig, # (4)
    :message => "Add newfile.txt", # (5)
    :parents => repo.empty? ? [] : [ repo.head.target ].compact, # (6)
    :update_ref => 'HEAD', # (7)
)
commit = repo.lookup(commit_id) # (8)
  1. Skapa en ny blob som innehåller innehållet i en ny fil.

  2. Fyll indexet med huvud‑incheckningens träd och lägg till den nya filen på sökvägen newfile.txt.

  3. Detta skapar ett nytt träd i ODB och använder det för den nya incheckningen.

  4. Vi använder samma signatur för både author‑ och incheckar‑fälten.

  5. Incheckningsmeddelandet.

  6. När du skapar en incheckning måste du ange den nya incheckningens föräldrar. Här används toppen av HEAD som enda förälder.

  7. Rugged (och Libgit2) kan valfritt uppdatera en referens när en incheckning skapas.

  8. Returvärdet är SHA‑1‑hashen för ett nytt incheckningsobjekt, som du sedan kan använda för att hämta ett Commit‑objekt.

Ruby‑koden är snygg och ren, men eftersom Libgit2 gör det tunga jobbet kommer den här koden också att gå ganska snabbt. Om du inte är Ruby‑användare tar vi upp några andra bindningar i Andra bindningar.

Avancerad funktionalitet

Libgit2 har ett par förmågor som ligger utanför kärn‑Git. Ett exempel är insticksbarhet: Libgit2 låter dig tillhandahålla anpassade bakändar för flera typer av operationer, så att du kan lagra saker på ett annat sätt än standard‑Git gör. Libgit2 tillåter anpassade bakändar för konfiguration, ref‑lagring och objektdatabasen, bland annat.

Låt oss titta på hur detta fungerar. Koden nedan är hämtad från uppsättningen bakändsexempel som Libgit2‑teamet tillhandahåller (som finns på https://github.com/libgit2/libgit2-backends). Så här sätts en anpassad bakänd för objektdatabasen upp:

git_odb *odb;
int error = git_odb_new(&odb); // (1)

git_odb_backend *my_backend;
error = git_odb_backend_mine(&my_backend, /*…*/); // (2)

error = git_odb_add_backend(odb, my_backend, 1); // (3)

git_repository *repo;
error = git_repository_open(&repo, "some-path");
error = git_repository_set_odb(repo, odb); // (4)

Observera att fel fångas upp, men hanteras inte. Vi hoppas att din kod är bättre än vår.

  1. Initiera en tom objektdatabas (ODB)‑frontänd som fungerar som behållare för de bakändar som gör det egentliga arbetet.

  2. Initiera en anpassad ODB‑bakänd.

  3. Lägg till bakänd till frontänd.

  4. Öppna ett kodförråd och ställ in det att använda vår ODB för att slå upp objekt.

Men vad är då git_odb_backend_mine? Det är konstruktorn för din egen ODB‑implementation, och du kan göra vad du vill där, så länge du fyller i git_odb_backend‑strukturen korrekt. Så här skulle det kunna se ut:

typedef struct {
    git_odb_backend parent;

    // Some other stuff
    void *custom_context;
} my_backend_struct;

int git_odb_backend_mine(git_odb_backend **backend_out, /*…*/)
{
    my_backend_struct *backend;

    backend = calloc(1, sizeof (my_backend_struct));

    backend->custom_context = …;

    backend->parent.read = &my_backend__read;
    backend->parent.read_prefix = &my_backend__read_prefix;
    backend->parent.read_header = &my_backend__read_header;
    // …

    *backend_out = (git_odb_backend *) backend;

    return GIT_SUCCESS;
}

Den mest subtila begränsningen här är att my_backend_struct`s första medlem måste vara en git_odb_backend‑struktur; detta säkerställer att minneslayouten är vad Libgit2‑koden förväntar sig. Resten är godtyckligt; denna struktur kan vara så stor eller liten som du behöver.

Initieringsfunktionen allokerar minne för strukturen, sätter upp den anpassade kontexten och fyller sedan i de medlemmar i parent‑strukturen som den stödjer. Ta en titt på filen include/git2/sys/odb_backend.h i Libgit2‑källkoden för en komplett uppsättning anropssignaturer; ditt specifika användningsfall hjälper dig att avgöra vilka av dessa du vill stödja.

Andra bindningar

Libgit2 har bindningar för många språk. Här visar vi ett litet exempel med några av de mer kompletta bindningspaketen i skrivande stund; bibliotek finns för många andra språk, inklusive C++, Go, Node.js, Erlang och JVM, i olika mognadsgrader. Den officiella samlingen av bindningar finns genom att bläddra i kodförråden på https://github.com/libgit2. Koden vi skriver returnerar incheckningsmeddelandet från incheckningen som HEAD till slut pekar på (ungefär som git log -1).

LibGit2Sharp

Om du skriver en .NET‑ eller Mono‑applikation är LibGit2Sharp (https://github.com/libgit2/libgit2sharp) det du söker. Bindningarna är skrivna i C#, och stor omsorg har lagts på att kapsla in de råa Libgit2‑anropen i CLR‑API:er som känns inbyggda. Så här ser vårt exempelprogram ut:

new Repository(@"C:\path\to\repo").Head.Tip.Message;

För skrivbordsapplikationer i Windows finns det till och med ett NuGet‑paket som hjälper dig att komma igång snabbt.

objective-git

Om din applikation körs på en Apple‑plattform använder du sannolikt Objective‑C som implementationsspråk. Objective-Git (https://github.com/libgit2/objective-git) är namnet på Libgit2‑bindningarna för den miljön. Exempelprogrammet ser ut så här:

GTRepository *repo =
    [[GTRepository alloc] initWithURL:[NSURL fileURLWithPath: @"/path/to/repo"] error:NULL];
NSString *msg = [[[repo headReferenceWithError:NULL] resolvedTarget] message];

Objective-git är fullt interoperabelt med Swift, så var inte rädd om du har lämnat Objective‑C bakom dig.

pygit2

Bindningarna för Libgit2 i Python heter Pygit2 och finns på https://www.pygit2.org. Exempelprogrammet:

pygit2.Repository("/path/to/repo") # open repository
    .head                          # get the current branch
    .peel(pygit2.Commit)           # walk down to the commit
    .message                       # read the message

Vidare läsning

En fullständig genomgång av Libgit2:s förmågor ligger utanför den här bokens omfattning. Om du vill ha mer information om Libgit2 självt finns API‑dokumentation på https://libgit2.org och en uppsättning guider på https://libgit2.org/docs. För de andra bindningarna, titta i den medföljande README:n och testerna; där finns ofta små handledningar och hänvisningar till vidare läsning.