Chapters ▾ 2nd Edition

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

کتابخانه گیت برای زبان Java (JGit)

اگر می‌خواهید Git را در یک برنامه Java استفاده کنید، یک کتابخانه کامل به نام JGit وجود دارد. JGit یک پیاده‌سازی نسبتاً کامل Git است که به‌صورت native در Java نوشته شده و در جامعه Java به‌طور گسترده استفاده می‌شود. پروژه JGit تحت مجموعه Eclipse قرار دارد و صفحه اصلی آن در https://www.eclipse.org/jgit/ قابل دسترسی است.

راه‌اندازی (Getting Set Up)

راه‌های مختلفی برای اتصال پروژه شما به JGit و شروع برنامه‌نویسی وجود دارد. احتمالاً ساده‌ترین روش استفاده از Maven است – با اضافه کردن قطعه زیر به تگ <dependencies> در فایل pom.xml خود:

<dependency>
    <groupId>org.eclipse.jgit</groupId>
    <artifactId>org.eclipse.jgit</artifactId>
    <version>3.5.0.201409260305-r</version>
</dependency>

نسخه (version) احتمالاً تا زمان مطالعه شما به‌روز شده است؛ برای اطلاعات جدیدتر، https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit را بررسی کنید. پس از انجام این مرحله، Maven به‌طور خودکار کتابخانه‌های JGit مورد نیاز شما را دریافت و استفاده خواهد کرد.

اگر ترجیح می‌دهید وابستگی‌های باینری را خودتان مدیریت کنید، باینری‌های پیش‌ساخته JGit از https://www.eclipse.org/jgit/download در دسترس هستند. می‌توانید آن‌ها را با اجرای دستوری مشابه زیر در پروژه خود وارد کنید:

javac -cp .:org.eclipse.jgit-3.5.0.201409260305-r.jar App.java
java -cp .:org.eclipse.jgit-3.5.0.201409260305-r.jar App

عملیات سطح پایین (Plumbing)

JGit دو سطح پایه API دارد: plumbing و porcelain. این اصطلاحات از Git گرفته شده و JGit نیز تقریباً به همان بخش‌ها تقسیم می‌شود: - APIهای porcelain یک رابط کاربرپسند برای عملیات معمولی سطح کاربر هستند (همان کارهایی که کاربر معمولی با خط فرمان Git انجام می‌دهد). - APIهای plumbing برای تعامل مستقیم با اشیاء سطح پایین مخزن هستند.

نقطه شروع اکثر جلسات JGit، کلاس Repository است و اولین کاری که باید انجام دهید ایجاد یک نمونه از آن است. برای مخزنی مبتنی بر سیستم فایل (JGit اجازه استفاده از مدل‌های ذخیره‌سازی دیگر را نیز می‌دهد)، این کار با استفاده از FileRepositoryBuilder انجام می‌شود:

// Create a new repository
Repository newlyCreatedRepo = FileRepositoryBuilder.create(
    new File("/tmp/new_repo/.git"));
newlyCreatedRepo.create();

// Open an existing repository
Repository existingRepo = new FileRepositoryBuilder()
    .setGitDir(new File("my_repo/.git"))
    .build();

Builder یک API روان برای ارائه همه چیزهایی که برای پیدا کردن یک مخزن Git نیاز دارد، ارائه می‌دهد، چه برنامه شما مکان دقیق آن را بداند و چه ندانند. می‌تواند از متغیرهای محیطی استفاده کند (.readEnvironment())، از جایی در دایرکتوری کاری شروع کرده و جستجو کند (.setWorkTree(…).findGitDir())، یا به سادگی یک دایرکتوری .git شناخته‌شده را باز کند.

به محض اینکه یک نمونه Repository داشته باشید، می‌توانید کارهای متنوعی با آن انجام دهید. در ادامه چند نمونه سریع آورده شده است:

// Get a reference
Ref master = repo.getRef("master");

// Get the object the reference points to
ObjectId masterTip = master.getObjectId();

// Rev-parse
ObjectId obj = repo.resolve("HEAD^{tree}");

// Load raw object contents
ObjectLoader loader = repo.open(masterTip);
loader.copyTo(System.out);

// Create a branch
RefUpdate createBranch1 = repo.updateRef("refs/heads/branch1");
createBranch1.setNewObjectId(masterTip);
createBranch1.update();

// Delete a branch
RefUpdate deleteBranch1 = repo.updateRef("refs/heads/branch1");
deleteBranch1.setForceUpdate(true);
deleteBranch1.delete();

// Config
Config cfg = repo.getConfig();
String name = cfg.getString("user", null, "name");

در اینجا نکات مهمی وجود دارد:

خط اول یک اشاره‌گر به مرجع «master» می‌گیرد. JGit به‌طور خودکار مرجع واقعی master را که در refs/heads/master قرار دارد می‌گیرد و شیئی برمی‌گرداند که اجازه می‌دهد اطلاعاتی دربارهٔ آن مرجع بازیابی کنید. می‌توانید نام آن را (.getName()) دریافت کنید، و یا شیء هدف یک مرجع مستقیم (.getObjectId()) یا مرجعی که یک مرجع نمادین به آن اشاره می‌کند (.getTarget()) را بدست آورید. اشیای Ref همچنین برای نمایش ارجاعات برچسب‌ها و اشیا استفاده می‌شوند، بنابراین می‌توانید بپرسید که آیا برچسب «peeled» شده است، بدین معنی که به هدف نهایی یک زنجیره (احتمالاً طولانی) از اشیای برچسب اشاره می‌کند.

خط دوم هدف مرجع master را می‌گیرد که به‌صورت یک نمونهٔ ObjectId برگردانده می‌شود. ObjectId نمایانگر هش SHA-1 یک شیء است که ممکن است در پایگاه‌داده اشیأ گیت وجود داشته باشد یا نه. خط سوم مشابه است، اما نشان می‌دهد JGit چگونه نحو rev-parse را مدیریت می‌کند (برای اطلاعات بیشتر رجوع کنید به ارجاعات شاخه‌ها (Branch References)); می‌توانید هر مشخص‌کنندهٔ شیئی را که گیت می‌فهمد ارسال کنید، و JGit یا یک ObjectId معتبر برای آن شیء برمی‌گرداند، یا null.

دو خط بعدی نشان می‌دهند چگونه محتوای خام یک شیء را بارگذاری کنید. در این مثال، ما از ObjectLoader.copyTo() برای پخش محتویات شیء مستقیماً به خروجی استاندارد استفاده می‌کنیم، اما ObjectLoader همچنین متدهایی برای خواندن نوع و اندازهٔ یک شیء و همچنین بازگرداندنش به‌صورت یک آرایه بایت دارد. برای اشیای بزرگ‌تر (جایی که .isLarge() مقدار true برمی‌گرداند)، می‌توانید از .openStream() برای گرفتن یک شیء شبیه InputStream استفاده کنید که قادر است دادهٔ خام شیء را بدون بارگذاری کامل آن در حافظه بخواند.

چند خط بعدی نشان می‌دهد چه کارهایی لازم است تا یک شاخهٔ جدید ایجاد شود. ما یک نمونهٔ RefUpdate می‌سازیم، چند پارامتر را پیکربندی می‌کنیم و با فراخوانی .update() تغییر را اجرا می‌کنیم. بلافاصله پس از آن کدی هست برای حذف همان شاخه. توجه داشته باشید که .setForceUpdate(true) برای این کار لازم است؛ در غیر این صورت فراخوانی .delete() مقدار REJECTED را بازمی‌گرداند و هیچ کاری انجام نخواهد شد.

 این تنها نمونهٔ کوچکی از کل APIِ سطح پایین (plumbing) است؛ روش‌ها و کلاس‌های بسیار بیشتری موجودند.
همچنین در اینجا نحوهٔ رسیدگی JGit به خطاها نشان داده نشده است که از طریق استفاده از استثناها (exceptions) انجام می‌شود.
API‌های JGit گاهی استثناهای استاندارد جاوا مانند IOException را پرتاب می‌کنند، ولی مجموعه‌ای از انواع استثناهای خاص JGit نیز وجود دارد (مثل NoRemoteRepositoryException، CorruptObjectException و NoMergeBaseException).

رابط سطح بالا (Porcelain)

APIهای سطح پایین نسبتاً کامل هستند، اما کنار هم قرار دادن آن‌ها برای رسیدن به اهداف متداول—مثل افزودن یک فایل به ایندکس یا ساختن یک commit جدید—می‌تواند دست‌وپاگیر باشد. JGit مجموعه‌ای از APIهای سطح بالاتر ارائه می‌دهد تا در این مسیر کمک کند و نقطهٔ ورود به این APIها کلاس Git است:

Repository repo;
// construct repo...
Git git = new Git(repo);

کلاس Git دارای مجموعه‌ای مناسب از متدهای سطح بالا به سبک «سازنده» (builder) است که می‌توان از آن‌ها برای ساخت رفتارهای نسبتاً پیچیده استفاده کرد. بیایید به یک مثال نگاه کنیم — انجام عملی شبیه git ls-remote:

CredentialsProvider cp = new UsernamePasswordCredentialsProvider("username", "p4ssw0rd");
Collection<Ref> remoteRefs = git.lsRemote()
    .setCredentialsProvider(cp)
    .setRemote("origin")
    .setTags(true)
    .setHeads(false)
    .call();
for (Ref ref : remoteRefs) {
    System.out.println(ref.getName() + " -> " + ref.getObjectId().name());
}

این یک الگوی رایج در کلاس Git است؛ متدها یک شیء فرمان (command object) برمی‌گردانند که به شما امکان زنجیره‌ای کردن فراخوانی متدها برای تنظیم پارامترها را می‌دهد و وقتی که متد .call() را فراخوانی می‌کنید اجرا می‌شوند. در این مثال، ما از remote به نام origin درخواست تگ‌ها را کرده‌ایم، اما نه heads. همچنین به استفاده از شیء CredentialsProvider برای احراز هویت توجه کنید.

بسیاری از فرمان‌های دیگر نیز از طریق کلاس Git در دسترس‌اند، از جمله اما نه محدود به add، blame، commit، clean، push، rebase، revert و reset.

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

این تنها نمونهٔ کوچکی از توانمندی‌های کامل JGit است. اگر علاقه‌مندید و می‌خواهید بیشتر بیاموزید، منابع و راهنمایی‌های بعدی را ببینید:

  • مستندات رسمی APIِ JGit را می‌توانید در https://www.eclipse.org/jgit/documentation بیابید. این‌ها Javadoc استاندارد هستند، بنابراین IDE محبوب شما برای JVM هم می‌تواند آن‌ها را به‌صورت محلی نصب کند.

  • کتاب آشپزخانهٔ JGit (JGit Cookbook) در https://github.com/centic9/jgit-cookbook نمونه‌های متعددی از انجام کارهای مشخص با JGit را شامل می‌شود.

scroll-to-top