پست

دریافت ریپازیتوری‌های حجیم در گیت

دریافت ریپازیتوری‌های حجیم در گیت

چند وقت پیش، برای بررسی سورس‌کد پروژه Odoo نسخه ۱۹، تصمیم گرفتیم ریپازیتوری رسمی آن را کلون کنیم. اولین اجرای دستور معمولی git clone نشان داد که این پروژه چیزی نیست که با یک کلون ساده جلو برود. حجم تاریخچه کامیت‌ها، وجود ده‌ها برنچ فعال و سنگینی فایل‌های باینری باعث می‌شد فرآیند کلون هم زمان زیادی ببرد و هم فضای زیادی از دیسک اشغال کند. همین تجربه، دلیل نوشتن این مطلب شد.

چرا کلون معمولی برای ریپازیتوری‌های بزرگ مناسب نیست

وقتی دستور git clone بدون هیچ فلگ اضافه‌ای اجرا می‌شود، گیت به‌صورت پیش‌فرض کل تاریخچه تمام کامیت‌های ریپازیتوری و رفرنس تمام برنچ‌های ریموت را دریافت می‌کند. برای پروژه‌ای مثل Odoo که سال‌ها توسعه فعال داشته و دارای برنچ‌های متعدد برای هر نسخه است، این حجم داده می‌تواند به راحتی به چند گیگابایت برسد؛ در حالی که برای کار روزمره، معمولاً فقط به آخرین وضعیت یک برنچ مشخص نیاز است.

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

راهکار اول: کلون فقط یک برنچ

فلگ --single-branch به همراه --branch <name> باعث می‌شود گیت فقط تاریخچه همان برنچ مشخص‌شده را دنبال کند و اصلاً رفرنسی برای سایر برنچ‌های ریموت نسازد. این کار برای پروژه‌هایی که دارای برنچ‌های نگهداری متعدد برای نسخه‌های مختلف هستند (مثل 19.0، 18.0، 17.0 در Odoo) اهمیت زیادی دارد، زیرا از دانلود تاریخچه‌ای که اصلاً استفاده نمی‌شود جلوگیری می‌کند.

راهکار دوم: کلون کم‌عمق با محدودسازی تاریخچه

فلگ --depth 1 گیت را از دریافت کامیت‌های قدیمی معاف می‌کند و فقط آخرین کامیت برنچ انتخابی دانلود می‌شود. این روش با عنوان Shallow Clone شناخته می‌شود. برای کنترل دقیق‌تر تاریخچه، دو گزینه دیگر هم وجود دارد:

  • --shallow-since=<date> فقط کامیت‌های بعد از یک تاریخ مشخص را دریافت می‌کند؛ مناسب زمانی که فقط تغییرات چند ماه اخیر مورد نیاز است.
  • --shallow-exclude=<ref> تاریخچه را تا رسیدن به یک برنچ یا تگ مشخص محدود می‌کند؛ مثلاً دریافت تاریخچه فقط از یک تگ نسخه به بعد.

نکته‌ای که باید در نظر گرفت این است که با استفاده از این فلگ‌ها، دسترسی به تاریخچه کامل کامیت‌ها (مثلاً برای بررسی git log قدیمی یا git blame) از دست می‌رود، مگر آن‌که بعداً با git fetch --unshallow تاریخچه کامل دریافت شود.

راهکار سوم: کلون جزئی و دانلود فایل‌ها در لحظه نیاز

مهم‌ترین و پیشرفته‌ترین بخش این خانواده دستورات، فلگ --filter است که قابلیت Partial Clone را فعال می‌کند. این گزینه به سه شکل اصلی قابل استفاده است:

فیلترچه چیزی حذف می‌شودمناسب برای
--filter=blob:noneتمام محتوای فایل‌ها (Blob) تا زمان نیاز واقعیتوسعه مستمر روی پروژه؛ همان نوعی که برای Odoo استفاده شد
--filter=blob:limit=<size>فقط بلاب‌هایی بزرگ‌تر از حجم مشخص‌شده (مثلاً 1m)ریپازیتوری‌هایی با تعداد کم فایل باینری بزرگ، بدون از دست‌دادن فایل‌های کوچک متنی
--filter=tree:0ساختار درخت پوشه‌ها (Tree) به‌جز کامیت HEAD؛ تاریخچه کامیت‌ها کامل باقی می‌ماندزمانی که فقط یک بیلد یا چک‌اوت لازم است ولی تاریخچه کامل کامیت‌ها اهمیت دارد (Treeless Clone)

با استفاده از --filter=blob:none، تاریخچه کامیت‌ها و درخت فایل‌ها کامل باقی می‌ماند، اما محتوای فایل‌های قدیمی تنها زمانی از ریموت دریافت می‌شود که گیت واقعاً به آن‌ها نیاز پیدا کند. باید توجه داشت که کلون جزئی فقط زمانی کار می‌کند که سرور از قابلیت Promisor Remote پشتیبانی کند؛ GitHub، GitLab و Azure DevOps این ویژگی را دارند. همچنین هر بار که گیت به یک بلاب تاریخی نیاز پیدا کند، باید دوباره به سرور متصل شود، بنابراین دسترسی مستمر به ریموت اصلی ضروری است.

ترکیب سه روش برای ریپازیتوری‌های سنگین

دستوری که برای دریافت سورس Odoo استفاده شد، دقیقاً ترکیبی از سه تکنیک بالا است:

1
git clone --branch 19.0 --single-branch --depth 1 --filter=blob:none https://github.com/MHKarami97/odoo.git odoo

این دستور تنها آخرین کامیت برنچ 19.0 را دریافت می‌کند، رفرنس سایر برنچ‌ها را نمی‌سازد و بلاب‌های غیرضروری را به‌صورت on-demand دریافت می‌کند. نتیجه، کاهش قابل‌توجه حجم دانلود و زمان کلون نسبت به یک کلون کامل و معمولی است.

لازم به ذکر است که مستندات رسمی Odoo برای نصب از طریق سورس، دستور پایه‌ای بدون --depth و --filter معرفی می‌کند، اما افزودن این دو فلگ برای کاربرانی که فقط قصد بررسی کد یا نصب سریع دارند، یک بهینه‌سازی رایج و مؤثر محسوب می‌شود.

Sparse Checkout؛ محدودسازی روی پوشه‌ها نه تاریخچه

روش‌های بالا حجم داده‌های شبکه و ذخیره‌شده در پوشه .git را کم می‌کنند، اما تعداد فایل‌های working directory را تغییر نمی‌دهند. برای ریپازیتوری‌های بزرگ که فقط بخشی از پوشه‌ها لازم است (مثل Odoo که ده‌ها ماژول دارد ولی فقط چند ماژول مورد استفاده قرار می‌گیرد)، دستور git sparse-checkout گزینه مناسب‌تری است:

1
2
3
git clone --filter=blob:none --sparse https://github.com/MHKarami97/odoo.git odoo
cd odoo
git sparse-checkout set addons/sale addons/purchase

این ترکیب باعث می‌شود گیت فقط تری و بلاب‌های همان پوشه‌های انتخاب‌شده را در working directory بیاورد، در حالی که ساختار کلی مخزن همچنان در دسترس باقی می‌ماند. حالت پیشنهادی گیت برای این کار «Cone Mode» است که با --cone فعال می‌شود و از نظر عملکرد بهینه‌تر از حالت الگوی آزاد قدیمی است.

Git LFS برای فایل‌های باینری بزرگ

اگر بخشی از حجم ریپازیتوری ناشی از فایل‌های باینری بزرگ (تصویر، ویدیو، دیتابیس نمونه) باشد، این فایل‌ها را می‌توان با Git LFS مدیریت کرد تا فقط اشاره‌گر آن‌ها در تاریخچه گیت ذخیره شود و محتوای واقعی جدا از مخزن اصلی نگهداری شود. ترکیب رایج برای مخازن بزرگ دارای LFS به این شکل است:

1
2
3
GIT_LFS_SKIP_SMUDGE=1 git clone --filter=blob:none --sparse <repo-url>
git sparse-checkout set <needed-dirs>
git lfs pull --include="<needed-dirs>/**"

پاک‌سازی تاریخچه برای کاهش حجم دائمی مخزن

اگر مسئله فقط سرعت کلون نیست و حجم واقعی مخزن روی سرور هم بیش‌ازحد است (مثلاً به دلیل فایل‌های حذف‌شده که هنوز در تاریخچه باقی‌اند)، راه‌حل متفاوت است. ابزار git filter-branch یا جایگزین سریع‌تر آن BFG Repo-Cleaner، برای بازنویسی تاریخچه و حذف کامل فایل‌های غیرضروری از تمام کامیت‌ها استفاده می‌شود. این روش برخلاف موارد قبلی، تاریخچه مخزن را برای همیشه تغییر می‌دهد و نیاز به هماهنگی با تمام همکاران پروژه دارد.

جمع‌بندی انتخاب روش مناسب

انتخاب نهایی به این سؤال بستگی دارد که مشکل اصلی کجاست:

  • اگر مشکل تعداد برنچ‌ها و کامیت‌های قدیمی است: --single-branch و --shallow-since یا --depth.
  • اگر مشکل حجم محتوای فایل‌هاست ولی تاریخچه کامیت‌ها لازم است: --filter=blob:none.
  • اگر حتی تاریخچه هم زیادی سنگین است و فقط یک بیلد لازم دارید: --filter=tree:0.
  • اگر مشکل تعداد پوشه‌ها و ماژول‌های غیرضروری در working directory است: git sparse-checkout با --cone.
  • اگر مشکل فایل‌های باینری بزرگ در طول تاریخچه است: Git LFS.
  • اگر مشکل حجم دائمی خود مخزن روی سرور است: بازنویسی تاریخچه با BFG یا filter-branch.