Aus Linux-Magazin 09/2024

Git in eigenen Programmen nutzen: Erste Schritte mit der Libgit2

© Volha Zaitsava / 123RF.com

Soll Ihr selbst gestricktes Programm ein Git-Repository manipulieren, lassen Sie sich am besten von der Libgit2 unter die Arme greifen. Der Einstieg in die schlanke Bibliothek fällt nicht schwer.

Mitunter soll eine Anwendung direkt Dateien in ein Git-Repository einchecken, die darin gespeicherten Informationen analysieren, die Inhalte in mehreren Branches gleichzeitig ändern oder sogar den Aufbau des Repositorys korrigieren. Vielleicht möchten Sie aber auch nur eine auf Ihr Projekt maßgeschneiderte Git-GUI schreiben. In all diesen Fällen nehmen Sie entweder die Bestandteile eines Git-Repositorys mühsam selbst auseinander oder greifen stattdessen kurzerhand zur Bibliothek Libgit2 [1]. Sie bietet zahlreiche Funktionen, die den Zugriff auf ein Git-Repo deutlich vereinfachen. Im harten Praxisalltag hat sie sich unter anderem im Hintergrund bei Github und Gitlab bewährt.

Der offerierte Werkzeugkasten enthält ausschließlich Low-Level-Funktionen, die teilweise direkt mit den Datenstrukturen eines Git-Repositorys arbeiten (Abbildung 1). Während auf der Shell das knackige Kommando »git add log.txt« eine Datei in die Staging Area schiebt, fallen dazu mit der Libgit2 mindestens fünf Funktionsaufrufe an. Im Gegenzug erhalten Sie äußerst weitreichende Manipulationsmöglichkeiten. Welche der über 175 bereitstehenden Funktionen dabei im Einzelnen wie helfen, müssen Sie sich jedoch aus einer unübersichtlichen Referenz und diversen, immerhin kommentierten Beispielen selbst zusammenreimen [2]. Wir führen Sie deshalb im Folgenden ausführlich durch die ersten Schritte mit der Libgit2. Dafür benötigen Sie lediglich Kenntnisse im Umgang mit den üblichen Git-Befehlen, also mit denen, die die Git-Dokumentation als Porcelain-Aktionen bezeichnet. Eine Einführung in das Versionskontrollsystem haben wir bereits früher veröffentlicht [3].

Abbildung 1: Auf ein Git-Repository greifen Sie auf drei Wegen zu: Die offiziellen Git-Tools bieten neben den bekannten High-Level-Befehlen (das Porcelain) auch Low-Level-Befehle (das Plumbing). An letzteren orientieren sich die Funktionen aus der Libgit2.

Abbildung 1: Auf ein Git-Repository greifen Sie auf drei Wegen zu: Die offiziellen Git-Tools bieten neben den bekannten High-Level-Befehlen (das Porcelain) auch Low-Level-Befehle (das Plumbing). An letzteren orientieren sich die Funktionen aus der Libgit2.

Schlankes C

Die in reinem C implementierte Libgit2 lässt sich in allen Anwendungen nutzen, die eine entsprechende Bibliothek importieren können. Dank ihrer extremen Beliebtheit existieren zudem Bindings an viele bekannte und weniger bekannte Programmiersprachen (Abbildung 2). Alle nachfolgenden Beispiele entstehen in einfach verständlichem C, die Nutzung in anderen Programmiersprachen fällt analog aus.

Abbildung 2: Zur Libgit2 existieren zahlreiche Bindings, die jedoch teilweise die API leicht verändern und mitunter sogar nicht mehr weiterentwickelt werden.

Abbildung 2: Zur Libgit2 existieren zahlreiche Bindings, die jedoch teilweise die API leicht verändern und mitunter sogar nicht mehr weiterentwickelt werden.

Dank ihrer hohen Portabilität und ihrer geringen Größe eignet sich die Libgit2 sogar für einige Embedded-Systeme. Darüber hinaus achtet das Libgit2-Team auf eine möglichst stabile API. Die Entwickler müssen ihre Bibliothek allerdings immer wieder an die Neuerungen und Vorgaben des Git-Projekts anpassen. Einerseits können sie daher zukünftige Schnittstellenänderungen nicht komplett ausschließen, andererseits finden deswegen einige neue Git-Funktionen erst verzögert Eingang in die Libgit2.

Der Quellcode der Libgit2 unterliegt der GNU GPLv2, was allerdings ihren Einsatz vor allem im kommerziellen Umfeld einschränken würde. Die Entwickler gewähren deshalb eine Linking Exception, dank derer Sie die Libgit2 mit jeder beliebigen Anwendung linken dürfen. Änderungen an der Bibliothek fallen weiter unter die GPL. Die bestens als Ausgangspunkt für eigenen Code geeigneten Beispiele in der Libgit2-Dokumentation unterliegen netterweise der sehr liberalen Lizenz CC0 1.0 Universal [4].

Schnell eingespielt

Die meisten Distributionen halten die Libgit2 in ihren Repositories vor. Möchten Sie die Bibliothek dennoch selbst übersetzen, beispielsweise für ein Embedded-System, benötigen Sie dazu lediglich CMake sowie einen C-Compiler, der den C99-Standard spricht. Des Weiteren brauchen Sie noch das Entwicklerpaket der OpenSSL-Bibliothek. Damit wickelt die Libgit2 die Kommunikation über HTTPS ab und berechnet die in Git-Repositories verwendeten SHA1- und SHA256-Hashes. Optional holen Sie die Libssh2 hinzu, mit der die Libgit2 SSH-Verbindungen aufbaut. In jedem Fall erfolgt die Übersetzung über den Aufruf aus Listing 1.

Listing 1

Libgit2 bauen

$ mkdir build && cd build && cmake .. && cmake --build .

Bitte beachten Sie: Die so erstellte Libgit2 ist nicht Thread-sicher, auf ein Libgit2-Objekt darf folglich immer nur ein einziger Thread zugreifen [5]. Um das zu ändern, spielen Sie vor der Kompilierung noch die Pthreads-Bibliothek ein. Sofern Sie die Libgit2 dynamisch linken und Ihr Programm weitergeben, sollten Sie immer davon ausgehen, dass die Libgit2 auf dem Zielsystem nicht Thread-sicher arbeitet. Dasselbe gilt, wenn Sie die Bibliothek aus einer Distribution verwenden.

Zugriff!

In Ihrem Programm müssen Sie als erste Amtshandlung die Libgit2 initialisieren. Das gelingt kurz und schmerzlos per »git_libgit2_init()«. Anschließend können Sie über »git_repository_open()« ein vorhandenes Git-Repository öffnen oder via »git_repository_init()« ein neues anlegen. Beide Funktionen erwarten als Parameter lediglich das Verzeichnis, in dem das Repository liegt beziehungsweise entstehen soll. Der Code aus Listing 2 versucht zunächst, das Repository unter »/tmp/logs/« zu öffnen. Falls das nicht gelingt, legt er dort ein neues Repo an.

Listing 2

Repo öffnen / neu anlegen

git_repository *repo = NULL;
if (git_repository_open(&repo, "/tmp/logs") < 0) {
  git_repository_init(&repo, "/tmp/logs", false);
}

Der Zeiger »repo« verweist anschließend auf ein Objekt, über das ab sofort andere Libgit2-Funktionen auf das Repository zugreifen können. Für dieses Objekt sind Sie selbst verantwortlich, Sie müssen es also insbesondere später wieder manuell aus dem Speicher entfernen. Analog erstellen viele andere Libgit2-Funktionen für Sie Objekte, die Sie nach dem Gebrauch aufräumen müssen – dazu später noch mehr.

Das »false« im dritten Parameter von »git_repository_init()« weist darauf hin, dass es sich beim angegebenen Pfad um das Arbeitsverzeichnis (Working Directory) handelt. Dort erstellt die Libgit2 das versteckte Verzeichnis ».git« mit den eigentlichen Git-Daten. Das Ergebnis entspricht einem »git init« im angegebenen Ordner. Bei einem »true« würde hingegen der Inhalt des Ordners ».git« direkt im angegebenen Pfad abgelegt.

Fehlerauswertung

Alle Libgit2-Funktionen liefern im Erfolgsfall den Rückgabewert »0«, bei einem Fehler hingegen einen negativen Wert. Das obige Beispiel nutzt dieses Verhalten aus. Wenn »git_repository_open()« das Repo nicht öffnen kann und daher eine Zahl kleiner als 0 liefert, versucht »git_repository_init()« ein neues Repository zu erstellen. Welcher Fehler im Einzelnen vorliegt, ermittelt das Kommando »git_error_last()« (Listing 3).

Listing 3

Fehlerauswertung

const git_error *e = git_error_last();
printf("Error: %s", e->message);

Der Befehl »git_error_last()« liefert stets den jeweils zuletzt von einer Libgit2-Funktion gemeldeten Fehler. Den wiederum schiebt die Funktion in die Struktur »git_error«. Dort steckt die Fehlermeldung in der Member-Variablen »message«, die im Beispiel aus Listing 3 »printf()« auf den Bildschirm druckt. In Ihrem eigenen Programm sollten Sie immer konsequent die Rückgabewerte aller Libgit2-Funktionen abfangen und gegebenenfalls mit »git_error_last()« auswerten. Der Übersichtlichkeit zuliebe verzichten jedoch alle Codebeispiele in diesem Artikel auf eine Fehlerbehandlung.

Vorgemerkt

Nachdem das Programm auf ein Repository zugreifen kann, fügt es ihm in der Regel neue Dateien hinzu oder ändert dort vorhandene Exemplare. Das gelingt mit den Dateioperationen aus der Standardbibliothek. Ein einfaches Beispiel zeigt Listing 4, das die Datei »/tmp/logs/log.txt« öffnet und dort das aktuelle Datum und die Zeit ablegt.

Listing 4

Zeitstempel im Log

FILE *file;
file = fopen("/tmp/logs/log.txt", "a+");
time_t t = time(NULL);
struct tm tm = *localtime(&t);
fprintf(file,
  "Timestamp: %d-%02d-%02d %02d:%02d:%02d\n",
  tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
  tm.tm_hour, tm.tm_min, tm.tm_sec);
fclose(file);

Auf der Kommandozeile würden Sie jetzt mit »git add« die veränderten Dateien in die Staging Area schieben. Im Hintergrund setzt Git dabei die Dateien auf den Index. Genau dasselbe passiert beim Einsatz der Libgit2. Zunächst holt »git_repository_index()« den Index des aktuellen Repositorys (Listing 5, erste Zeile). Danach zeigt »index« auf den aktuellen Index. Dem fügen Sie die geänderte Datei hinzu (zweite Zeile). Angeben müssen Sie hier nur den Dateinamen im Working Directory, nicht den kompletten Pfad zur Datei.

Listing 5

Index

git_index *index = NULL; git_repository_index(&index, repo);
git_index_add_bypath(index, "log.txt");
git_index_write(index);
git_index_free(index);

Wenn Sie mehrere Dateien auf einmal in den Index schreiben möchten, sollten Sie in der Dokumentation einen Blick auf die Funktionen »git_index_update_all()« und »git_index_add_all()« werfen. Letztere ergänzt alle Dateien, deren Namen einem vorgegebenen Muster folgen. Dagegen synchronisiert »git_index_update_all()« den Index mit dem Working Directory. Dabei wirft die Funktion außerdem Dateien aus dem Index, die im Working Directory gelöscht wurden.

Sobald die geänderten Dateien im Index vermerkt sind, können Sie ihn zurückspeichern und das Index-Objekt geordnet aus dem Speicher werfen (Listing 5, Zeilen 3 und 4).

Wer bin ich?

Nachdem der Index befüllt wurde, steht ein Commit an. Dabei merkt sich Git, wer den Commit wann abgesetzt hat. Die Daten sammelt die Libgit2 in einer entsprechenden (Daten-)Struktur, der Signature. Bevor Sie einen Commit anstoßen können, müssen Sie eine solche Signature anlegen.

In Listing 6 erstellt »git_signature_now()« eine neue Signature, auf die anschließend »sig« zeigt. Die Signature umfasst den Benutzernamen »Tux«, der über die E-Mail-Adresse »tux@example.com« zu erreichen ist. In Ihrem Programm können Sie stattdessen Ihr Unternehmen oder den Namen der Anwendung hinterlegen.

Listing 6

Signature

git_signature *sig = NULL;
git_signature_now(&sig, "Tux", "tux@example.com");

Jede Signature enthält zudem einen Zeitstempel. Wie das »now« in »git_signature_now()« andeutet, verwendet die Funktion die aktuelle Zeit – in der Regel wünschenswert. Alternativ zu »git_signature_now()« setzen Sie »git_signature_default()« ein, das wie Git auf der Kommandozeile die Benutzerdaten aus der Repository-Konfiguration klaubt und ebenso die aktuelle Zeit übernimmt.

Mit Köpfchen

Jeder Commit besitzt immer mindestens einen Vorgänger, sofern es sich nicht um den allerersten Commit handelt. Für einen anstehenden neuen Commit müssen Sie zunächst die passenden Vorgänger aus dem Repository herauskramen. Das klingt komplizierter, als es ist: In den meisten Fällen zeigt die Referenz »HEAD« auf den momentan letzten Commit. Um den direkt als Vorgänger des neuen Commits verwenden zu können, müssen Sie ihn mit einem schlanken Funktionsaufruf aus dem Repository beschaffen (Listing 7, erste Zeile).

Listing 7

Commits

*parent = NULL; git_revparse_single(&parent, repo, "HEAD");
if( git_revparse_single(&parent, repo, "HEAD") == GIT_ENOTFOUND ) {
  printf("HEAD not found!\n");
}

Dabei zeigt »parent« jetzt auf ein Objekt, das den »HEAD« repräsentiert. Sollte kein »HEAD« existieren, etwa wegen eines noch leeren Repositorys, liefert die Funktion »git_revparse_single()« ein »GIT_ENOTFOUND« zurück, und der Zeiger ist weiterhin »NULL«. Das können Sie unter anderem zur Fehlerabfrage nutzen, wie ab Zeile 2 demonstriert.

Sofern das Repository noch leer ist, müssen Sie die Verarbeitung keineswegs abbrechen, sondern belassen den Zeiger »parent« erst einmal auf dem Vorgabewert »NULL«. Anstelle des »HEAD« erlaubt »git_revparse_single()« zudem eine beliebige gültige Reference. Die Funktion liefert daraufhin das dazu passende Objekt aus dem Repository zurück. Anstelle von »git_revparse_single()« angeln Sie sich einen älteren Commit zusätzlich über die im Kasten “Alternative Köpfe” zusammengefassten Möglichkeiten.

Alternative Köpfe

Für einen neuen Commit benötigen Sie dessen Vorgänger. Meist handelt es sich dabei um den »HEAD«, also den letzten Commit. An ein passendes Objekt gelangen Sie entweder über »git_revparse_single()« oder die sogenannte OID (Listing 8, erste zwei Zeilen).

Der Befehl »git_reference_name_to_id()« ermittelt für die im letzten Parameter angegebene Reference das zugehörige Objekt im Repository und liefert dann den Hash-Wert dieses Objekts zurück. Ihn bezeichnet die Libgit2 als Object ID oder kurz OID. Im obigen Beispiel kramt die Funktion die OID für den »HEAD« hervor. Da der »HEAD« recht häufig erforderlich ist, gibt es für ihn eine eigene Funktion, die allerdings lediglich eine Reference auf ihn liefert (Zeile 3 und 4). Die OID zu einer Reference verrät netterweise das Kommando »git_reference_target()« (Zeile 5 und 6).

Dabei lauert jedoch eine kleine Stolperfalle: »git_reference_target()« funktioniert nur, wenn es sich um eine direkte und keine symbolische Reference handelt. Ob das der Fall ist, findet eine kleine Fallunterscheidung heraus (Zeile 7 bis 9).

Das Kommando »git_reference_type()« liefert »GIT_REFERENCE_DIRECT« zurück, sofern es sich um eine direkte Referenz handelt. Nur in dem Fall wird »git_reference_target()« aktiv. Sollte es sich bei Ihrer Reference um eine symbolische Referenz handeln, können Sie von »git_reference_resolve()« die passende direkte Reference ermitteln lassen.

Wie auch immer Sie an die OID gelangen – der Aufruf »git_commit_lookup()« liefert anschließend das zum zugehörigen Commit passende Objekt und lässt »HEAD« darauf zeigen (Zeile 10 und 11).

Listing 8

Alternative Köpfe

git_oid head_id;
git_reference_name_to_id (&head_id, repo, "HEAD");
git_reference *head;
git_repository_head (&head, repo);
git_oid head_id;
head_id = git_reference_target(head);
if (git_reference_type(head) == GIT_REFERENCE_DIRECT) {
  head_id = git_reference_target(head);
}
git_object *head = NULL;
git_commit_lookup(&head, repo, &parent_id);

Unabhängig von der verwendeten Methode sollten Sie nun einen oder mehrere Vorgänger für Ihren neuen Commit in der Hand halten. Die Vorgänger sammeln Sie in einem neuen Array (Listing 9).

Listing 9

Vorgänger sammeln

const git_commit *parents[] = {(git_commit*)parent};

Im bisherigen Beispiel bildet der »HEAD« den einzigen Vorgänger, folglich landet nur der Zeiger auf sein Objekt im Array.

Baumschule

Jetzt wird es noch einmal etwas komplizierter. Ein Commit besteht nicht einfach aus einer Liste mit allen geänderten Dateien. Stattdessen merkt sich Git die zu einem Commit gehörenden Dateien samt ihren Dateinamen in einer speziellen verschachtelten Datenstruktur (Abbildung 3).

Abbildung 3: Der &raquo;HEAD&laquo; ist ein Alias (Reference) f&uuml;r den letzten Commit. Jeder Commit besitzt Metadaten wie den Autor. Dar&uuml;ber hinaus verweist er auf einen Tree, der wiederum die eigentlichen Inhalte verwaltet. Jedes Objekt erh&auml;lt eine eigene unverwechselbare&nbsp;ID.

Abbildung 3: Der »HEAD« ist ein Alias (Reference) für den letzten Commit. Jeder Commit besitzt Metadaten wie den Autor. Darüber hinaus verweist er auf einen Tree, der wiederum die eigentlichen Inhalte verwaltet. Jedes Objekt erhält eine eigene unverwechselbare ID.

Um einen neuen Commit ausführen zu können, müssen Sie zunächst einen Blick in die Staging Area werfen und passend zu deren Inhalt einen Tree erstellen. Dazu muss anfangs wieder der aktuelle Zustand des Index her, den das bereits bekannte Kommando aus der ersten Zeile von Listing 10 holt. Die Funktion »git_index_write_tree()« (Zeile 3) baut einen passenden Tree für die Daten im Index und parkt dann in »tree_id« die einzigartige Identifikationsnummer des Baums.

Listing 10

Tree

git_repository_index(&index, repo);
git_oid tree_id;
git_index_write_tree(&tree_id, index);
git_index_free(index);
git_tree *tree;
git_tree_lookup (&tree, repo, &tree_id);

Git vergibt intern für jedes gespeicherte Objekt automatisch eine solche Nummer, mit der sich das jeweilige Objekt eindeutig identifizieren und wiederfinden lässt. Technisch handelt es sich dabei um einen Hash-Wert, den die Libgit2 als Object ID oder kurz OID bezeichnet. Damit können Sie jetzt auch den Tree als weiterverwendbares Objekt holen (Zeile 5 und 6). Nachdem »git_tree_lookup()« seine Arbeit erledigt hat, zeigt »tree« auf das zur »tree_id« passende Tree-Objekt.

Commit absetzen

Mit den bis hierhin zusammengetragenen Daten lässt sich jetzt endlich der Commit absetzen. Die zuständige Funktion »git_commit_create()« mutiert dank ihrer vielen Parameter zum in Listing 11 zu bestaunenden Bandwurm.

Listing 11

Commit absetzen

git_oid commit_id;
git_commit_create(&commit_id, repo, "HEAD", sig, sig, NULL, "Commit Message", tree, 1, parents);

Ganz hinten steht das Array »parents« mit den vorhergehenden Commits. Die Zahl davor gibt die Anzahl dieser Eltern-Commits an. Im Beispiel geht nur der »HEAD« voraus, folglich gibt es nur »1« Vorgänger. Sollte das Repository noch leer sein, existieren logischerweise keine Vorgänger. In dem Fall übergeben Sie bei der Anzahl eine »0«, »parents« darf dann zudem »NULL« sein.

In jedem Fall zeigt »tree« auf den Baum. Da es sich bei der Commit Message um einen normalen String handelt (»const char *«), kann Ihr Programm ihn vorher passend zusammenbauen. Diese Commit Message erwartet »git_commit_create()« standardmäßig in der Zeichenkodierung UTF-8. Verwenden Sie eine andere Zeichenkodierung, geben Sie diese als String anstelle der »NULL« an, etwa »”ISO-8859-15″«.

Als fünften Parameter erwartet »git_commit_create()« die Signature mit dem Namen des Commiters und dem Zeitstempel des Commits. Der vierte Parameter verrät den Autor und die Erstellungszeit des Commits. Das Beispiel aus Listing 11 verwendet der Einfachheit halber für beide Angaben die vorbereitete Signatur »sig«.

Kopf und Lagerhalle

Im dritten Parameter erwartet »git_commit_create()« eine Referenz auf einen Commit, die nach der Behandlung durch »git_commit_create()« auf den neuen Commit verweist. In den meisten Fällen soll der »HEAD« wie im Beispiel auf den neuen Commit zeigen. Möchten Sie stattdessen eine Reference auf einen anderen Commit verwenden, muss dessen erster Vorgänger der letzte Commit des Branches sein.

Der zweite Parameter zeigt auf das Repository. Mit diesen Informationen führt »git_commit_create()« den Commit aus. Dabei entsteht eine neue Commit-ID, die in der Variablen »commit_id« landet. Seit der Version 1.8 kennt die Libgit2 zusätzlich die Funktion »git_commit_create_from_stage()«. Sie führt direkt für alle Änderungen in der Staging Area einen Commit. Das entspricht weitestgehend dem Kommandozeilenbefehl »git commit -m “Commit Message“«.

Rauswurf

Zum Abschluss gilt es, aufzuräumen und sämtliche Datenstrukturen wieder freizugeben. Dazu bietet die Libgit2 erneut passende Funktionen. Dabei fliegen nacheinander der Tree, die Signature, das Objekt mit dem vorherigen Commit und das Repository aus dem Speicher (Listing 12, erste vier Zeilen). Abschließend müssen Sie noch die Libgit2 geordnet herunterfahren (letzte Zeile).

Listing 12

Aufräumen und Freigeben

git_tree_free(tree);
git_signature_free(sig);
git_object_free(parent);
git_repository_free(repo);
git_libgit2_shutdown();

Fazit

Je mehr Sie sich mit den Konzepten und Abläufen unter der Haube von Git auskennen, desto leichter fällt Ihnen die Programmierung mit der Libgit2. Die Bibliothek benennt obendrein die Funktionen sehr sprechend und gruppiert sie in der API-Referenz [6] passend. So beginnen etwa die Namen aller Funktionen, die einen Branch manipulieren, mit »git_branch«. In der Referenz finden Sie außerdem rechts unten im Menü unter Examples einige kommentierte Beispiele, die als Anregung dienen können. Alle Anweisungen in diesem Artikel fasst zudem noch einmal Listing 13 zu einem kompletten Programm zusammen, das Sie als Ausgangspunkt für eigene Experimente verwenden können. (csi)

Listing 13

Einfacher Commit mit der Libgit2

#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include "git2.h"
int main() {
  git_libgit2_init();
// Repo öffnen und anlegen --------------
  bool repo_isempty = false;
  git_repository *repo = NULL;
  if( git_repository_open(&repo, "/tmp/logs") < 0 ) {
    const git_error *e = git_error_last();
    printf("Error: %s", e->message);
    git_repository_init(&repo, "/tmp/logs", false);
    repo_isempty = true;
  }
// Neue Datei erstellen -----------------
  FILE *file;
  file = fopen("/tmp/logs/log.txt", "a+");
  time_t t = time(NULL);
  struct tm tm = *localtime(&t);
  fprintf(file, "Timestamp: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
  fclose(file);
// Datei in die Staging Area bringen ----
  git_index *index = NULL;
  git_repository_index(&index, repo);
  git_index_add_bypath(index, "log.txt");
  git_index_write(index);
  git_index_free(index);
// Signature erstellen ------------------
  git_signature *sig = NULL;
  git_signature_now(&sig, "Tux", "tux@example.com");
// HEAD holen
  git_object *parent = NULL;
  if( git_revparse_single(&parent, repo, "HEAD") == GIT_ENOTFOUND ) {
    printf("HEAD not found!\n");
  }
  const git_commit *parents[] = {(git_commit*)parent};
// Tree zusammenstellen -----------------
  git_repository_index(&index, repo);
  git_oid tree_id;
  git_index_write_tree(&tree_id, index);
  git_index_free(index);
  git_tree *tree;
  git_tree_lookup(&tree, repo, &tree_id);
// Commit ausführen ---------------------
  git_oid commit_id;
  if (repo_isempty) {
    // Wenn das Respository leer ist, gibt es keine Eltern:
    git_commit_create(&commit_id, repo, "HEAD", sig, sig, NULL, "Commit Message", tree, 0, NULL);
  } else {
    git_commit_create(&commit_id, repo, "HEAD", sig, sig, NULL, "Commit Message", tree, 1, parents);
  }
// Aufräumen ----------------------------
  git_tree_free(tree);
  git_signature_free(sig);
  git_object_free(parent);
  git_repository_free(repo);
  git_libgit2_shutdown();
}

Infos

  1. Libgit2: https://libgit2.org
  2. Libgit2-Dokumentation: https://libgit2.org/docs/
  3. Git-Einführung: Tim Adler, “Ablageordnung”, LM 07/2024, S. 14, https://www.lm-online.de/50783
  4. Creative Commons CC0 1.0 Universal: https://github.com/libgit2/libgit2/blob/main/examples/COPYING
  5. Hinweise zum Threading: https://github.com/libgit2/libgit2/blob/main/docs/threading.md
  6. API-Referenz zur Libgit2: https://libgit2.org/libgit2/#HEAD
DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben