Chapters ▾ 2nd Edition

A2.2 پیوست B: گنجاندن گیت در برنامه‌های شما (Embedding Git in your Applications) - کتابخانهٔ گیت به زبان C (Libgit2)

کتابخانهٔ گیت به زبان C (Libgit2)

گزینهٔ دیگر در اختیار شما استفاده از Libgit2 است. Libgit2 پیاده‌سازی بدون وابستگی از گیت است که تمرکز آن بر ارائهٔ یک API خوب برای استفاده در برنامه‌های دیگر است. می‌توانید آن را در https://libgit2.org بیابید.

ابتدا نگاهی به شکل API در زبان C می‌اندازیم. در اینجا یک مرور سریع آمده است:

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

دو خط اول یک مخزن گیت را باز می‌کنند. نوع git_repository نمایانگر یک دسته‌گانه (handle) به مخزنی است که یک حافظهٔ نهان در رم دارد. این ساده‌ترین روش است، برای زمانی که مسیر دقیق پوشهٔ کاری مخزن یا فولدر .git را می‌دانید. همچنین git_repository_open_ext وجود دارد که شامل گزینه‌هایی برای جستجوست، و توابعی مانند git_clone برای ساخت یک نسخهٔ محلی از یک مخزن راه‌دور، و git_repository_init برای ایجاد یک مخزن کاملاً جدید.

بخش دوم کد از نحو rev-parse استفاده می‌کند (برای اطلاعات بیشتر رجوع کنید به ارجاعات شاخه‌ها (Branch References)) تا کمیتی که HEAD در نهایت به آن اشاره می‌کند را بگیرد. نوع بازگشتی اشاره‌گری به git_object است، که چیزی را نشان می‌دهد که در پایگاه دادهٔ شئ‌های گیت یک مخزن وجود دارد. git_object در واقع نوع «والد» برای چندین نوع شئ مختلف است؛ چیدمان حافظه برای هر یک از انواع «فرزند» همانند git_object است، لذا می‌توان با اطمینان آن را به نوع مناسب تبدیل کرد. در این مورد git_object_type(commit) مقدار GIT_OBJ_COMMIT را بازمی‌گرداند، بنابراین امن است که آن را به اشاره‌گر git_commit تبدیل کنیم.

قسمت بعدی نشان می‌دهد چگونه به ویژگی‌های کمیت دسترسی داشته باشیم. خط آخر از نوع git_oid استفاده می‌کند؛ این نمایش Libgit2 برای هش SHA-1 است.

از این نمونه، چند الگو شروع به آشکار شدن کرده‌اند:

  • اگر اشاره‌گری را اعلام کنید و مرجعی به آن را به یک فراخوانی Libgit2 بدهید، آن فراخوانی احتمالاً یک کد خطای عددی برمی‌گرداند. مقدار 0 نشان‌دهندهٔ موفقیت است؛ هر مقدار کمتر از آن خطا محسوب می‌شود.

  • اگر Libgit2 برایتان یک اشاره‌گر را مقداردهی کند، مسئول آزادسازی آن بر عهدهٔ شماست.

  • اگر Libgit2 از یک فراخوانی اشاره‌گر const برگرداند، نیازی به آزادسازی آن نیست، اما آن اشاره‌گر زمانی که شی‌ای که متعلق به آن است آزاد شود نامعتبر خواهد شد.

  • نوشتن به زبان C کمی دردسر دارد.

مورد آخر یعنی احتمال اینکه هنگام استفاده از Libgit2 خودتان بخواهید C بنویسید خیلی کم است. خوشبختانه، تعدادی binding مخصوص زبان‌های مختلف موجود است که کار با مخازن Git را از زبان و محیط شما نسبتاً ساده می‌کنند. بیایید همان مثال بالا را که با bindingهای Ruby برای Libgit2 نوشته شده — که Rugged نام دارد و در 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

همان‌طور که می‌بینید، کد خیلی کم‌پلوتر (تمیزتر) است. اولاً، Rugged از استثناها استفاده می‌کند؛ مثلاً می‌تواند خطاهایی مانند ConfigError یا ObjectError را برای نشان‌دادن شرایط خطا پرتاب کند. ثانیاً، نیازی به آزادسازی صریح منابع نیست، چون Ruby دارای جمع‌آوری زباله است. بیایید نگاهی به مثال کمی پیچیده‌تر بیندازیم: ساختن یک commit از صفر

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. یک blob جدید ایجاد کنید که شامل محتوای یک فایل جدید است.

  2. ایندکس را با درخت commitِ HEAD مقداردهی کنید و فایل جدید را در مسیر newfile.txt اضافه کنید.

  3. این یک درخت جدید در ODB ایجاد می‌کند و آن را برای commit جدید استفاده می‌کند.

  4. برای فیلدهای author و committer از یک امضای یکسان استفاده می‌کنیم.

  5. پیام commit.

  6. هنگام ایجاد یک commit باید والد(های) commit جدید را مشخص کنید. این مثال از نوک HEAD به عنوان والدِ یگانه استفاده می‌کند.

  7. Rugged (و Libgit2) می‌توانند به‌صورت اختیاری هنگام ساختن commit یک reference را به‌روزرسانی کنند.

  8. مقدار بازگشتی، هش SHA-1 یک شیء commit جدید است که می‌توانید از آن برای گرفتن یک شیٔ Commit استفاده کنید.

     کد روبی تمیز و خوب است، و چون Libgit2 کار سنگین را انجام می‌دهد، این کد هم نسبتاً سریع اجرا خواهد شد.
    اگر برنامه‌نویس روبی نیستید، به سایر بایندینگ‌ها در <<_libgit2_bindings>> هم اشاره کرده‌ایم.

قابلیت‌های پیشرفته (Advanced Functionality)

Libgit2 چند قابلیت دارد که فراتر از محدودهٔ اصلی گیت هستند. یک مثال، افزونه‌پذیری است: Libgit2 به شما اجازه می‌دهد برای چند نوع عملیات، «backend»های سفارشی فراهم کنید، تا بتوانید داده‌ها را به شکلی متفاوت از گیت استاندارد ذخیره کنید. Libgit2 امکان تعریف backendهای سفارشی برای پیکربندی، ذخیرهٔ ref و پایگاه دادهٔ آبجکت‌ها و موارد دیگر را فراهم می‌کند.

بیایید ببینیم این چگونه کار می‌کند. کد زیر از مجموعه مثال‌های backend که تیم Libgit2 ارائه داده‌اند گرفته شده است (قابل مشاهده در https://github.com/libgit2/libgit2-backends). در اینجا نحوهٔ راه‌اندازی یک backend سفارشی برای پایگاه دادهٔ آبجکت‌ها نشان داده شده است:

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)

توجه: خطاها گرفته می‌شوند ولی رسیدگی نمی‌شوند. امیدواریم کد شما بهتر از ما باشد.

  1. یک «frontend» خالی برای پایگاه دادهٔ آبجکت (ODB) مقداردهی اولیه کنید که به عنوان ظرفی برای «backend»ها عمل می‌کند—همان backendها هستند که کار واقعی را انجام می‌دهند.

  2. یک backend سفارشی برای ODB مقداردهی اولیه کنید.

  3. backend را به frontend اضافه کنید.

  4. یک مخزن باز کنید و آن را طوری تنظیم کنید که از ODB ما برای جستجوی آبجکت‌ها استفاده کند.

اما این git_odb_backend_mine چیست؟ این تابع سازندهٔ پیاده‌سازی ODB مخصوص شماست، و شما می‌توانید هر کاری که می‌خواهید در آن انجام دهید، تا وقتی که ساختار git_odb_backend را به‌درستی پر کنید. ممکن است شبیه چیزی شبیه به این باشد:

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

محفوظ‌ترین قید این است که اولین عضو ساختار my_backend_struct باید یک ساختار git_odb_backend باشد؛ این تضمین می‌کند که چیدمان حافظه همان چیزی است که کد Libgit2 انتظار دارد. باقی ساختار دلخواه است؛ این ساختار می‌تواند به هر اندازه‌ای که لازم دارید بزرگ یا کوچک باشد.

 تابع مقداردهی اولیه مقداری حافظه برای ساختار رزرو می‌کند، زمینهٔ سفارشی را راه‌اندازی می‌نماید و سپس اعضای ساختار `parent` که پشتیبانی می‌کند را پر می‌کند.
برای مجموعهٔ کامل امضاهای فراخوانی‌ها، فایل `include/git2/sys/odb_backend.h` در سورس Libgit2 را ببینید؛ مورد استفادهٔ خاص شما تعیین خواهد کرد کدام یک از این‌ها را می‌خواهید پشتیبانی کنید.

بایندینگ‌های دیگر (Other Bindings)

Libgit2 برای زبان‌های زیادی رابط ارائه کرده است. در اینجا یک مثال کوتاه نشان می‌دهیم که از چند مورد از بسته‌های رابط کامل‌تر در زمان نگارش استفاده می‌کند؛ کتابخانه‌هایی برای زبان‌های دیگری مانند C++، Go، Node.js، Erlang و JVM نیز وجود دارند که هر یک در مراحل مختلفی از تکامل قرار دارند. مجموعهٔ رسمی رابط‌ها را می‌توانید با مرور مخازن در https://github.com/libgit2 بیابید. کدی که خواهیم نوشت پیام کمیت (commit message) را از کمیتی باز می‌گرداند که در نهایت HEAD به آن اشاره می‌کند (تا حدی مشابه git log -1).

بایندینگ گیت برای .NET (LibGit2Sharp)

اگر در حال نوشتن یک برنامهٔ .NET یا Mono هستید، LibGit2Sharp (https://github.com/libgit2/libgit2sharp) همان چیزی است که به دنبالش هستید. این رابط‌ها به زبان C# نوشته شده‌اند و دقت زیادی در پوشش‌دهی فراخوانی‌های خام Libgit2 با APIهای بومی‌تر CLR به‌کار رفته است. برنامهٔ نمونهٔ ما به این شکل است:

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

برای برنامه‌های دسکتاپ ویندوز، حتی یک بستهٔ NuGet وجود دارد که به شما کمک می‌کند سریع شروع کنید.

بایندینگ گیت برای Objective-C (objective-git)

اگر برنامهٔ شما روی پلتفرم‌های اپل اجرا می‌شود، احتمالاً از Objective-C به‌عنوان زبان پیاده‌سازی استفاده می‌کنید. Objective-Git (https://github.com/libgit2/objective-git) نام رابط‌های Libgit2 برای آن محیط است. برنامهٔ نمونه به این شکل است:

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

Objective-git کاملاً با Swift قابل‌تعامل است، بنابراین اگر Objective-C را کنار گذاشته‌اید نگران نباشید.

بایندینگ گیت برای پایتون (pygit2)

رابط‌های Libgit2 برای پایتون با نام Pygit2 شناخته می‌شوند و در https://www.pygit2.org در دسترس‌اند. برنامهٔ نمونهٔ ما:

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

مطالعهٔ بیشتر (Further Reading)

البته، بررسی کامل قابلیت‌های Libgit2 خارج از دامنهٔ این کتاب است. اگر می‌خواهید اطلاعات بیشتری دربارهٔ خود Libgit2 به‌دست آورید، مستندات API آن در https://libgit2.github.com/libgit2 و مجموعه‌ای از راهنماها در https://libgit2.github.com/docs در دسترس هستند. برای دیگر بایندینگ‌ها، فایل README همراه و تست‌ها را بررسی کنید؛ معمولاً در آن‌ها آموزش‌های کوتاه و ارجاع‌هایی برای مطالعهٔ بیشتر پیدا می‌شود.

scroll-to-top