Git
Chapters ▾ 2nd Edition

A2.2 Ek bölüm B: Git’i Uygulamalarınıza Gömmek - Libgit2

Libgit2

Elinizdeki diğer bir seçenek de Libgit2’yi kullanmaktır. Libgit2, diğer programlarda kullanım için güzel bir API’ye sahip olmaya odaklanan, Git’in bağımlılık içermeyen bir uygulamasıdır. Bunu https://libgit2.org adresinde bulabilirsiniz.

Öncelikle C API’nin neye benzediğine bir göz atalım. İşte kısa bir gezinti:

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

İlk birkaç satır Git reposunu açar. git_repository türü, önbellekte bulunan bir repoya yönelik tanıtıcıyı temsil eder. Bir reponun çalışma dizinine veya .git klasörüne giden tam yolu bildiğinizde, bu en basit yöntemdir. Ayrıca, arama seçeneklerini içeren git_repository_open_ext, uzak bir reponun yerel klonunu oluşturmak için git_clone ve arkadaşları ve tamamen yeni bir repo oluşturmak için ise git_repository_init bulunmaktadır.

İkinci kod parçası, HEAD işaret ettiği katkıyı almak için rev-parse sözdizimini kullanır (bu konu hakkında daha fazla bilgi için Dal Referansları bakınız). Döndürülen tür, bir repodaki Git nesnesi veritabanında var olan bir şeyi temsil eden bir git_object işaretçisidir. git_object aslında birkaç farklı türde nesnenin bir üst türüdür; her bir alt türü için bellek düzeni git_object ile aynı olduğu için, doğru olan bir türe güvenli bir şekilde dönüştürebilirsiniz. git_object_type(commit) burada GIT_OBJ_COMMIT döndüreceği için, git_commit işaretçisine dönüştürmek güvenlidir.

Bir sonraki parça, katkının özelliklerine nasıl erişileceğini gösterir. Buradaki son satırda kullanılan git_oid türü, bu Libgit2’nin SHA-1 karmasının temsilidir.

Bu örnekten birkaç model ortaya çıkar:

  • Eğer bir işaretçi bildirir, ve onu bir Libgit2 çağrısına bir referans olarak geçirirseniz; o çağrı muhtemelen, tamsayı olan bir hata kodu döndürecektir. Bir 0 değeri başarıyı, daha küçük herhangi bir değer ise bir hatayı gösterir.

  • Eğer Libgit2 bir işaretçiyi sizin için doldurursa, onu serbest bırakmak sizin sorumluluğunuzdadır.

  • Libgit2 bir çağrıdan bir const işaretçi döndürürse, onu serbest bırakmanız gerekmez, ancak ait olduğu nesne serbest bırakıldığında geçersiz hale gelir.

  • C yazmak biraz sıkıntılıdır.

Sonuncu madde, Libgit2’yi kullanırken C yazmanızın pek olası olmadığı anlamına gelir. Neyse ki, belirli dil ve ortamını kullanırken Git repolarıyla çalışmayı oldukça kolaylaştıran, bir dizi özel dil bağlamı mevcuttur. Yukarıdaki örneği kullanarak, Libgit2 için https://github.com/libgit2/rugged adresinde bulabileceğiniz Ruby bağlamı olan, "Rugged" kullanarak yazılmış örneğe bir göz atalım.

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

Görüldüğü gibi, kod çok daha temiz. İlk olarak, Rugged istisnaları kullanır; ConfigError veya ObjectError gibi şeyler fırlatabilir ve hata durumlarını belirtebilir. İkinci olarak, Ruby’nin çöp toplama (garbage-collection) özelliği olduğundan, kaynakları açıkça serbest bırakma işlemi yoktur. Biraz daha karmaşık bir örneğe bakalım: sıfırdan bir katkı oluşturma

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. Yeni bir dosyanın içeriğini içeren bir blob oluşturun.

  2. İndex’i baş katkının ağacıyla doldurun ve yeni dosyayı newfile.txt yoluna ekleyin.

  3. Bunu yapmak ODB’de yeni bir ağaç oluşturur ve bunu yeni katkı için kullanır.

  4. Hem yazar hem de işleyici alanları için aynı imzayı kullanıyoruz.

  5. Katkı mesajı.

  6. Bir katkı oluştururken, yeni katkının öncellerini belirtmelisiniz. Bu, tek öncel için HEAD’in ucu kullanır.

  7. Rugged (ve Libgit2) bir katkı yaparken isteğe bağlı olarak bir referansı güncelleyebilir.

  8. Dönüş değeri, yeni bir katkı nesnesinin SHA-1 karmasıdır ve ardından bir Commit nesnesini almak için kullanabilirsiniz.

Ruby kodu güzel ve temizdir, ancak işin ağır kısmını Libgit2 yaptığından, bu kod da oldukça hızlı çalışacaktır. Eğer bir rubyci değilseniz, Diğer Bağlamlar bölümünde diğer bağlantılara da değiniyoruz.

Gelişmiş İşlevsellik

Libgit2’nin çekirdek Git’in kapsamının dışında birkaç yeteneği daha vardır. Örneğin takılabilirlik: Libgit2 bazı işlemler için özel ``backend`` sağlamanıza izin verir, böylece birşeyleri stok Git’in yaptığından farklı bir şekilde depolayabilirsiniz. Libgit2 yapılandırma, referans depolama ve nesne veritabanı dahil olmak üzere birçok şey için özel backendlere izin verir.

Bunu nasıl çalıştığını inceleyelim. Aşağıdaki kod, Libgit2 ekibi tarafından sağlanan backend örneklerinin bir parçasıdır (bu örnekler https://github.com/libgit2/libgit2-backends adresinde bulunabilir). İşte nesne veritabanı için özel bir backendin kurulumu:

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)

(Burada hata yakalanır ancak işlenmez. Umarım sizin kodunuz bizimkinden daha iyidir.)

  1. Boş bir nesne veritabanı (ODB) ``frontend`` 'i başlatın; gerçek işi yapan ``backend`` 'ler için bu bir konteyner işi görecektir.

  2. Özel bir ODB backend’i başlatın.

  3. Backend’i frontend’e ekleyin.

  4. Bir repoyu açın ve nesneleri aramak için ODB’mizi kullanacak şekilde ayarlayın.

Peki bu git_odb_backend_mine zımbırtısı nedir? Bu sizin kendi ODB uygulamanızın kurucusudur ve içinde istediğiniz her şeyi yapabilirsiniz; yeter ki git_odb_backend yapısını uygun şekilde doldurun. İşte nasıl görünebileceği:

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

Buradaki en gözden kaçabilecek kısıtlama, my_backend_struct 'ın ilk elemanının bir git_odb_backend yapısı olması gerekliliğidir; bu, bellek düzeninin Libgit2 kodunun beklediği gibi olmasını sağlar. Gerisi keyfidir; bu yapı, ihtiyacınıza göre istediğiniz kadar büyük veya küçük olabilir.

Başlatma fonksiyonu, yapı için bellek ayırır, özel bağlamı kurar ve sonra desteklediği üst yapının elemanlarını doldurur. Libgit2 kaynaklarında include/git2/sys/odb_backend.h dosyasına bakarak tam bir çağrı imza setini görebilirsiniz; kullanım durumunuz, hangi çağrıları desteklemek isteyeceğinizi belirlemenize yardımcı olacaktır.

Diğer Bağlamlar

Libgit2 birçok dil için bağlamlara sahiptir. Bu yazının yazıldığı an itibariyle kapsamlı sayılabilecek bağlam paketlerinden birkaçını kullanarak küçük bir örnek gösteriyoruz; C++, Go, Node.js, Erlang ve JVM dahil olmak üzere birçok başka dil için farklı gelişmişlik seviyelerinde kütüphaneler bulunmaktadır. Resmi bağlam koleksiyonunu https://github.com/libgit2 adresindeki repolara göz atarak bulabilirsiniz. Yazacağımız kod, sonunda HEAD tarafından işaret edilen katkının mesajını döndürecektir (git log -1 gibi).

LibGit2Sharp

Eğer bir .NET veya Mono uygulaması yazıyorsanız, LibGit2Sharp (https://github.com/libgit2/libgit2sharp) aradığınız şeydir. Bağlamlar C# dilinde yazılmıştır ve ham Libgit2 çağrılarıyla uyumlu hissettiren CLR API’leriyle sarmak için büyük özen gösterilmiştir. Örnek programımızın şöyle görünür:

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

Windows masaüstü uygulamaları için, hızlı başlamanıza yardımcı olacak bir NuGet paketi bile vardır.

objective-git

Uygulamanız bir Apple platformunda çalışıyorsa, muhtemelen uygulama dili olarak Objective-C kullanıyorsunuzdur. Objective-Git (https://github.com/libgit2/objective-git) bu ortam için Libgit2 bağlamlarının adıdır. Örnek program şu şekildedir:

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

Objective-git Swift ile tamamen uyumludur, bu yüzden Objective-C’yi geride bıraktıysanız endişelenmeyin.

pygit2

Libgit2 için Python bağlamları Pygit2 olarak adlandırılır ve https://www.pygit2.org adresinde bulunur. Örnek programımız:

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

İleri Okumalar

Tabii ki Libgit2’nin yeteneklerine dair kapsamlı bir açıklama bu kitabın hedefi dışındadır. Libgit2 hakkında daha fazla bilgi istiyorsanız, API belgeleri https://libgit2.github.com/libgit2 adresinde bulunabilir ve kılavuzlar https://libgit2.github.com/docs adresindedir. Diğer bağlamlar için, paketlenmiş README belgesine ve testlere bakın; genellikle orada küçük örnekler ve daha fazla okuma için yönlendirmeler bulunmaktadır.

scroll-to-top