Git 🌙
Chapters â–Ÿ 2nd Edition

A2.2 Annexe B: Embarquer Git dans vos applications - Libgit2

Libgit2

Une autre option Ă  votre disposition consiste Ă  utiliser Libgit2. Libgit2 est une mise en Ɠuvre de Git sans dĂ©pendance externe, qui se focalise sur une interface de programmation agrĂ©able Ă  utiliser depuis d’autres programmes. Vous pouvez la trouver sur https://libgit2.github.com.

Voyons d’abord à quoi ressemble l’API C. En voici un tour rapide :

// Ouvrir un depot
git_repository *repo;
int error = git_repository_open(&repo, "/path/to/repository");

// Dereferencer HEAD vers un commit
git_object *head_commit;
error = git_revparse_single(&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit*)head_commit;

// afficher quelques proprietes du commit
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);

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

Les deux premiĂšres lignes ouvrent un dĂ©pĂŽt Git. Le type git_repository reprĂ©sente un identificateur de dĂ©pĂŽt avec un cache en mĂ©moire. C’est la mĂ©thode la plus simple, quand vous connaissez le chemin exact vers le rĂ©pertoire de travail ou le rĂ©pertoire .git d’un dĂ©pĂŽt. Il y a aussi git_repository_open_ext qui inclut des options pour chercher, git_clone et ses dĂ©clinaisons pour crĂ©er un clone local d’un dĂ©pĂŽt distant et git_repository_init pour crĂ©er un dĂ©pĂŽt entiĂšrement nouveau.

Le second bloc de code utilise la syntaxe « rev-parse » (voir RĂ©fĂ©rences de branches pour plus de dĂ©tails) pour obtenir le commit sur lequel HEAD peut pointer. Le type retournĂ© est un pointeur sur git_object qui reprĂ©sente quelque chose qui existe dans la base de donnĂ©es des objets de Git pour un dĂ©pĂŽt. git_object est en fait une type « parent » pour diffĂ©rentes sortes d’objets ; l’agencement en mĂ©moire de chacun de ces types « enfants » est identique Ă  celui de git_object, donc on peut forcer la conversion vers le type dĂ©sirĂ© en toute sĂ©curitĂ©. Dans notre cas, git_object_type(commit) retournerait GIT_OBJ_COMMIT, il est donc permis de le convertir en un pointeur de git_commit.

Le bloc suivant montre comment accĂ©der aux propriĂ©tĂ©s d’un commit. La derniĂšre ligne utilise un type git_oid ; c’est la reprĂ©sentation d’une empreinte SHA-1 dans Libgit2.

De cet exemple, une structure générale commence à émerger :

  • Si vous dĂ©clarez un pointeur et que vous en passez une rĂ©fĂ©rence dans un appel Ă  Libgit2, cet appel renverra vraisemblablement un code de retour entier. Une valeur 0 indique un succĂšs ; toute valeur nĂ©gative est une erreur.

  • Si Libgit2 peuple un pointeur pour vous, vous ĂȘtes responsable de sa libĂ©ration.

  • Si Libgit2 retourne un pointeur const aprĂšs un appel, vous n’avez pas besoin de le libĂ©rer mais il deviendra invalide quand l’objet qui le possĂšde sera lui-mĂȘme libĂ©rĂ©.

  • Écrire en C est un exercice plutĂŽt douloureux.

Cette derniĂšre remarque signifie qu’il est fort peu probable que vous Ă©crirez du C pour utiliser Libgit2. Heureusement, il existe un certain nombre de liaisons vers d’autres langages qui rendent plus facile l’interaction avec des dĂ©pĂŽts Git depuis votre environnement et votre langage spĂ©cifiques. Voyons l’exemple ci-dessus rĂ©Ă©crit en utilisant le portage Ruby de Libgit2, appelĂ© Rugged et qui peut ĂȘtre trouvĂ© sur 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

Tout de suite, le code est moins verbeux. DĂ©jĂ , Rugged utilise des exceptions ; il peut lever ConfigError ou ObjectE pour signaler des conditions d’erreur. Ensuite, il n’y a pas de libĂ©ration explicite des ressources, puisque Ruby utilise un ramasse-miettes. Voyons un exemple lĂ©gĂšrement plus compliqué : crĂ©er un commit Ă  partir de rien.

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. CrĂ©er un nouveau blob qui contient le contenu d’un nouveau fichier.

  2. Peupler l’index avec l’arbre du commit HEAD et ajouter le nouveau fichier sous le chemin newfile.txt.

  3. Ceci crĂ©e un nouvel arbre dans la base de donnĂ©es des objets et l’utilise pour le nouveau commit.

  4. Nous utilisons la mĂȘme signature pour l’auteur et le validateur.

  5. Le message de validation.

  6. À la crĂ©ation d’un commit, il faut spĂ©cifier les parents du nouveau commit. on utilise le sommet de HEAD comme parent unique.

  7. Rugged (et Libgit2) peuvent en option mettre à jour la référence lors de la création du commit.

  8. La valeur retournée est une empreinte SHA-1 du nouvel objet commit que vous pouvez alors utiliser pour obtenir un objet Commit.

Le code Ruby est joli et propre, mais comme Libgit2 rĂ©alise le gros du travail, il tourne aussi plutĂŽt rapidement. Si vous n’ĂȘtes pas rubyiste, nous aborderons d’autres portages dans Autres liaisons.

Fonctionnalité avancée

Libgit2 a certaines capacitĂ©s qui ne sont pas disponibles dans Git natif. Un exemple est la possibilitĂ© de greffons : Libgit2 vous permet de fournir des services « d’arriĂšre-plan » pour diffĂ©rents types d’opĂ©rations, pour vous permettre de stocker les choses d’une maniĂšre diffĂ©rente de Git. Libgit2 autorise des services d’arriĂšre-plan pour la configuration, le stockage des rĂ©fĂ©rences et la base de donnĂ©es d’objets, entre autres.

Voyons comment cela fonctionne. Le code ci-dessous est empruntĂ© Ă  un ensemble d’exemples de services fourni par l’équipe Libgit2 (qui peut ĂȘtre trouvĂ© sur https://github.com/libgit2/libgit2-backends). Voici comment un service d’arriĂšre-plan pour une base de donnĂ©es d’objets peut ĂȘtre crĂ©Ă©e :

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

(Notez que les erreurs sont capturées, mais ne sont pas gérées. Nous espérons que votre code est meilleur que le nÎtre).

  1. Initialise une enveloppe d’interface d’une base de donnĂ©es d’objets vide (ODB) qui agit comme un conteneur pour les tĂąches de fond qui feront le vrai travail.

  2. Initialise une tĂąche de fond ODB.

  3. Ajoute la tñche de fond dans l’enveloppe.

  4. Ouvre le dépÎt et le paramÚtre pour utiliser notre ODB pour rechercher les objets.

Mais qu’est-ce que ce git_odb_backend_mine ? HĂ© bien, c’est le constructeur de notre propre rĂ©alisation de l’ODB et nous pouvons la faire comme nous voulons tant que la structure git_odb_backend est correctement renseignĂ©e. Voici Ă  quoi elle pourrait ressembler :

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

La contrainte la plus subtile ici est que le premier membre de my_backend_structure doit ĂȘtre une structure git_odb_backend ; cela assure que la disposition en mĂ©moire correspond Ă  ce qu’attend le code de Libgit2. Le reste est totalement arbitraire ; cette structure peut ĂȘtre aussi grande ou petite que nĂ©cessaire.

La fonction d’initialisation alloue de la mĂ©moire pour la structure, initialise le contexte spĂ©cifique, puis remplit les membres de la structure parent qu’elle supporte. RĂ©fĂ©rez-vous au fichier include/git2/sys/odb_backend.h dans les sources de Libgit2 pour l’intĂ©gralitĂ© des signatures d’appels ; votre cas d’utilisation particulier vous permettra de dĂ©terminer lesquelles vous souhaitez supporter.

Autres liaisons

Libgit2 dispose de liaisons vers de nombreux langages. Nous allons montrer ici un petit exemple en utilisant quelques-unes des liaisons les plus abouties au moment de la rĂ©daction de ce livre ; des bibliothĂšques existent pour de nombreux autres langages qui incluent C++, Go, Node.js, Erlang et la JVM Ă  diffĂ©rents stades de maturitĂ©. La collection officielle de liaisons peut ĂȘtre trouvĂ©e en parcourant les dĂ©pĂŽts sur https://github.com/libgit2. Le code que nous allons Ă©crire retournera le message de validation du commit finalement pointĂ© par HEAD (git log -1 en quelque sorte).

LibGit2Sharp

Si vous Ă©crivez une application .NET ou Mono, LigGit2Sharp (https://github.com/libgit2/libgit2sharp) est tout ce que vous cherchez. Les liaisons sont Ă©crites en C# et une grande attention a Ă©tĂ© portĂ©e Ă  envelopper les appels directs Ă  Libgit2 avec une interface de programmation naturelle en C#. Voici Ă  quoi notre programme d’exemple ressemble :

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

Pour les applications graphiques Windows, il existe mĂȘme un paquet NuGet qui vous permettra de dĂ©marrer vos dĂ©veloppements rapidement.

objective-git

Si votre application tourne sur une plateforme Apple, vous avez de grandes chances d’utiliser Objective-C comme langage de programmation. Objective-Git (https://github.com/libgit2/objective-git) est le nom de la liaison de Libgit2 pour cet environnement. Le programme d’exemple ressemble à ceci :

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

Objective-git est totalement interopĂ©rable avec Swift, donc n’ayez crainte si vous avez abandonnĂ© Objective-C.

pygit2

La liaison avec Libgit2 en Python s’appelle Pygit2 et elle peut ĂȘtre trouvĂ©e sur https://www.pygit2.org/. Notre programme d’exemple :

pygit2.Repository("/chemin/du/depot") # ouvre le depot
    .head                             # recupere la branche en cours
    .peel(pygit2.Commit)              # descend au commit
    .message                          # lit le message

Pour aller plus loin

Bien sĂ»r, un traitement complet des capacitĂ©s de Libgit2 est hors du cadre de ce livre. Si vous souhaitez plus d’information sur Libgit2 elle-mĂȘme, la documentation de programmation se trouve sur https://libgit2.github.com/libgit2 et un ensemble de guides sur https://libgit2.github.com/docs. Pour les autres liaisons, cherchez dans le README et dans les tests ; il y a souvent des petits didacticiels et des pointeurs sur d’autres documents.

scroll-to-top