Git
Chapters ▾ 2nd Edition

A2.2 Appendix B: Pag-embed ng Git sa iyong Mga Aplikasyon - Libgit2

Libgit2

Ibang opsyon sa iyong paraan ay ang paggamit ng Libgit2. Libgit2 ay isang dependency-free na pagsasagawa ng Git, na may pagtuon sa pagkakaroon ng magandang API para magamit sa loob ng ibang mga programa. Makikita mo ito sa http://libgit2.github.com.

Una, tingnan natin kung ano ang hitsura ng C API. Narito ang isang ipoipong paglalakbay:

// Buksan ang isang repositoryo
git_repository *repo;
int error = git_repository_open(&repo, "/path/to/repository");

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

// I-print ang ilan sa mga katangian ng 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);

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

Ang unang pares ng mga linya ay nagbukas ng isang Git na repositoryo. Ang git_repository na uri ay kumakatawan sa isang pamamahala sa isang repositoryo na may cache sa memorya. Ito ang pinakasimpleng paraan, para kapag alam mo ang eksaktong path sa gumaganang direktoryo ng repositoryo o .git na folder. Mayroon ding git_repository_open_ext na kinabibilangan ng mga opsyon para sa paghahanap, git_clone at mga kaibigan para sa paggawa ng isang lokal na clone ng isang remote na repositoryo, at git_repository_init para sa paglikha ng isang ganap na bagong repositoryo.

Ang pangalawang tipak ng code ay gumagamit ng rev-parse na syntax (tingnan ang Mga Reperensiya ng Branch para sa higit pa nito) upang makuha ang commit na ang HEAD na sa kalaunan. Ang binalik na uri ay isang git_object pointer, na kumakatawan sa isang bagay na umiiral sa database ng Git object para sa isang repository. git_object ay talagang isang “parent” na uri para sa maraming iba’t-ibang mga uri ng mga bagay; ang layout ng memorya para sa bawat isa sa mga uri ng “child” ay kapareho ng para sa git_object, para maaari mong isumite ng ligtas ang tama. Sa kasong ito, ang git_object_type(commit) ay babalik sa GIT_OBJ_COMMIT, kaya ligtas itong isumite sa isang git_commit pointer.

Ang susunod na bahagi ay nagpapakita kung paano ma-access ang mga katangian ng commit. Ang huling linya dito ay gumagamit ng isang uri ng git_oid; ito ang representasyon ng Libgit2 para sa SHA-1 hash.

Mula sa sample na ito, ang ilang mga anyo ay nagsimula na lumitaw:

  • Kung ipinapahayag mo ang isang pointer at magpasa ng isang reperensiya nito sa isang Libgit2 call, ang tawag na iyon ay malamang na babalik ang isang error code ng integer. Ang halaga na 0 ay nagpapahiwatig ng tagumpay; Ang anumang mas mababa ay mali.

  • Kung pinarami ang pointer ng Libgit2 para sa iyo, ikaw ay responsable para sa pagbabakanti nito.

  • Kung ang Libgit2 ay magbabalik ng isang const na pointer mula sa isang tawag, hindi mo kailangang palayain ito, ngunit magiging imbalido kapag ang bagay kinabibilangan nito ay napalaya.

  • Ang pagsusulat ng C ay napakahirap.

Ang huling ibig sabihin nito ay hindi posible na magsusulat ka ng C kapag gumagamit ng Libgit2. Sa kabutihang palad, may ilang mga bindings na tukoy sa wika na magagamit na medyo madaling gamitin sa Git na mga repositoryo mula sa iyong partikular na wika at kapaligiran. Tingnan natin ang halimbawa sa itaas na isinulat gamit ang mga binding ng Ruby para sa Libgit2, na pinangalanang Rugged, at matatagpuan sa 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

Gaya ng makikita mo, ang code ay cluttered na mas mababa. Una, ang Rugged ay gumagamit ng mga eksepsiyon; maaari itong magtanghal ng mga bagay tulad ng ConfigError o ObjectError upang mai-signal ang mga kondisyon ng mali. Pangalawa, walang malinaw na pagbabakante ng mga mapagkukunan, dahil ang Ruby ay garbage-collected. Tingnan natin ang isang bahagyang mas kumplikadong halimbawa: paggawa ng isang commit mula sa simula

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. Lumikha ng isang bagong blob, na naglalaman ng mga nilalaman ng isang bagong file.

  2. Paramihin ang index na may mga head commit tree, at idagdag ang bagong file sa path na newfile.txt.

  3. Lumilikha ito ng bagong tree sa ODB, at ginagamit ito para sa bagong commit.

  4. Ginagamit namin ang parehong lagda para sa parehong mga may-akda at mga patlang ng committer.

  5. Ang mensahe ng commit.

  6. Kapag gumagawa ng isang commit, kailangan mong tukuyin ang mga parent ng bagong commit. Ginagamit nito ang tip ng HEAD para sa nag-iisang parent.

  7. Ang Rugged (at Libgit2) ay maaaring opsyonal na i-update ang isang reperensya kapag gumagawa ng isang commit.

  8. Ang halaga ng pagbalik ay ang SHA-1 hash ng isang bagong commit na bagay, na maaari mong gamitin para makakuha ng isang Commit na bagay.

Ang Ruby na code ay maganda at malinis, ngunit dahil ang Libgit2 ay gumagawa ng mabigat na proseso, ang code na ito ay gagana ng masmabilis, din. Kung ikaw ay hindi isang rubyist, hinahawakan namin ang ilang iba pang mga bindings sa Iba pang mga Binding.

Advanced na Functionality

Ang Libgit2 ay may ilang mga kakayahan na nasa labas ng lawak ng core Git. Ang isang halimbawa ay pluggability: Ang Libgit2 ay nagbibigay-daan sayo na magbigay ng kustom na “backends” para sa ilang mga uri ng operasyon, kaya maaari kang mag-imbak ng mga bagay sa ibang paraan kaysa sa stock na Git. Pinapayagan ng Libgit2 ang mga kustom na backend para sa kompigurasyon, ref storage, at database ng bagay, bukod sa iba pang mga bagay.

Tingnan natin kung paano ito gumagana. Ang code sa ibaba ay hiniram mula sa hanay ng mga halimbawa ng backend na ibinigay ng koponan ng Libgit2 (na maaaring matagpuan sa https://github.com/libgit2/libgit2-backends). Narito kung paano naka-set up ang isang kustom backend para sa database ng bagay:

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)

(Tandaan na ang mga mali ay nakuha, ngunit hindi napangasiwa. Inaasahan namin na ang iyong code ay masmahusay kaysa sa atin.)

  1. Magsimula ng walang laman na database ng bagay (ODB) “frontend,” na kung saan ay gawin bilang isang lalagyan para sa “backends” kung saan ay ang mga gumagawa ng totoong gawain.

  2. Magpasimula ng kustom na backend ng ODB.

  3. Idagdag ang backend sa frontend.

  4. Magbukas ng repositoryo, at itakda ito upang gamitin ang aming ODB upang maghanap ng mga bagay.

Ngunit ano ang git_odb_backend_mine bagay na ito? Eh, iyan ang tagapagbuo para sa iyong sariling pagsasagawa ng ODB, at maaari mong gawin ang anumang nais mo doon, hangga’t pinupuno mo nang wasto ang istraktura ng git_odb_backend. Narito kung ano ang hitsura nito:

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

Ang subtlest na pagpigil dito ay ang my_backend_struct's unang miyembro ay dapat na isang git_odb_backend na istraktura; tinitiyak nito na ang layout ng memorya ay kung ano ang inaasahan ng code ng Libgit2. Ang natitirang bahagi nito ay di-makatwirang; ang istraktura na ito ay maaaring maging malaki o maliit hangga’t kailangan mo ito.

Ang initialization na function ay naglalaan ng memorya para sa istraktura, nagtatakda ng kustom na konteksto, at pagkatapos ay pumupuno sa mga miyembro ng istraktura ng parent na sinusuportahan nito. Tingnan ang file na include/git2/sys/odb_backend.h sa source ng Libgit2 para sa isang kumpletong hanay ng mga lagda ng tawag; ang iyong partikular na kaso ng paggamit ay tutulong na matukoy kung alin sa mga ito nais mong suportahan.

Iba pang mga Binding

Ang Libgit2 ay may mga bindings para sa maraming wika. Narito nagpapakita kami ng isang maliit na halimbawa gamit ang ilan sa mga maskumpletong binding packages tulad ng pagsulat na ito; Ang mga library ay umiiral para sa maraming iba pang mga wika, kabilang ang C++, Go, Node.js, Erlang, at ang JVM, lahat sa iba’t ibang yugto ng kapanahunan. Ang opisyal na koleksyon ng mga binding ay matatagpuan sa pamamagitan ng pag-browse sa mga repositoryo sa https://github.com/libgit2. Ang code na isusulat namin ay ibabalik ang mensahe ng commit mula sa commit na kalaunan ay itinuturo ng HEAD (parang tulad ng git log -1).

LibGit2Sharp

Kung nagsusulat ka ng isang .NET o Mono na aplikasyon, LibGit2Sharp (https://github.com/libgit2/libgit2sharp) ang hinahanap mo. Ang mga binding ay nakasulat sa C #, at mahusay na pag-aalaga upang i-wrap ang mga raw Libgit2 call na may mga native-feeling CLR API. Narito kung ano ang hitsura ng aming halimbawa ng programa:

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

Para sa mga aplikasyon ng desktop sa Windows, mayroong kahit isang package ng NuGet na tutulong sa iyo na makapagsimula nang mabilis.

objective-git

Kung ang iyong aplikasyon ay tumatakbo sa isang platform ng Apple, malamang ay gumagamit ka ng Objective-C bilang iyong language sa pagsasagawa. Ang Objective-Git (https://github.com/libgit2/objective-git) ay ang pangalan ng Libgit2 bindings para sa kapaligiran na iyon. Mukhang ganito ang halimbawa ng programa:

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

Objective-git ay ganap na nakaka-interoperable sa Swift, kaya huwag matakot kung nalipasan mo ang Objective-C.

pygit2

Ang mga binding para sa Libgit2 sa Python ay tinatawag na Pygit2, at matatagpuan sa http://www.pygit2.org/. Ang aming programa ng halimbawa:

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

Karagdagang Pagbabasa

Siyempre, ang buong pagtrato sa mga kakayahan ng Libgit2 ay nasa labas ng lawak ng aklat na ito. Kung gusto mo ng higit pang impormasyon sa Libgit2 mismo, mayroong dokumentasyon ng API sa https://libgit2.github.com/libgit2, at isang pangkat ng mga gabay sa https://libgit2.github.com/docs. Para sa iba pang mga binding, suriin ang bundled README at mga pagsusulit; may mga madalas na maliit na mga pagtuturo at mga payo sa karagdagang pagbabasa doon.