GIT-SUBTREE(1) Git Manual GIT-SUBTREE(1)

git-subtree - ادغام زیردرخت‌ها با یکدیگر و تقسیم مخزن به زیردرخت‌ها

git subtree [<options>] -P <prefix> [-S[<keyid>]] add <local-commit>
git subtree [<options>] -P <prefix> [-S[<keyid>]] add <repository> <remote-ref>
git subtree [<options>] -P <prefix> [-S[<keyid>]] merge <local-commit> [<repository>]
git subtree [<options>] -P <prefix> [-S[<keyid>]] split [<local-commit>]
git subtree [<options>] -P <prefix> [-S[<keyid>]] pull <repository> <remote-ref>
git subtree [<options>] -P <prefix> [-S[<keyid>]] push <repository> <refspec>

زیردرخت‌ها (subtrees) اجازه می‌دهند زیرپروژه‌ها در یک زیرپوشه از پروژه اصلی گنجانده شوند، و به صورت اختیاری تمام تاریخچه زیرپروژه را نیز در بر گیرند.

به عنوان مثال، می‌توانید کد منبع یک کتابخانه را به عنوان یک زیرپوشه از برنامه کاربردی خود اضافه کنید.

زیردرخت‌ها نباید با زیرماژول‌ها (submodules) که برای هدفی مشابه در نظر گرفته شده‌اند، اشتباه گرفته شوند. بر خلاف زیرماژول‌ها، زیردرخت‌ها به هیچ ساختار ویژه‌ای (مانند فایل‌های .gitmodules یا gitlinks) در مخزن شما نیاز ندارند و کاربران نهایی مخزن شما را وادار نمی‌کنند کار خاصی انجام دهند یا نحوه کار زیردرخت‌ها را یاد بگیرند. یک زیردرخت تنها یک زیرپوشه است که می‌توان مانند هر بخش دیگری از پروژه روی آن کامیت زد، شاخه ایجاد کرد و آن را ادغام نمود.

همچنین نباید آن‌ها را با استفاده از راهبرد ادغام زیردرخت (subtree merge strategy) اشتباه گرفت. تفاوت اصلی در این است که علاوه بر ادغام پروژه دیگر به عنوان یک زیرپوشه، می‌توانید کل تاریخچه یک زیرپوشه را از پروژه خود استخراج کرده و آن را به یک پروژه مستقل تبدیل کنید. بر خلاف راهبرد ادغام زیردرخت، می‌توانید به صورت رفت‌وبرگشتی بین این دو عملیات جابه‌جا شوید. اگر کتابخانه مستقل به‌روزرسانی شود، می‌توانید به صورت خودکار تغییرات را در پروژه خود ادغام کنید؛ اگر کتابخانه را درون پروژه خود به‌روزرسانی کنید، می‌توانید تغییرات را دوباره تفکیک (split) کرده و مجدداً در پروژه کتابخانه ادغام نمایید.

برای مثال، اگر کتابخانه‌ای که برای یک برنامه کاربردی ساخته‌اید در جای دیگری نیز مفید واقع شود، می‌توانید کل تاریخچه آن را استخراج کرده و به عنوان مخزن گیت مستقل منتشر کنید، بدون اینکه به اشتباه تاریخچه پروژه اصلی شما با آن آمیخته شود.


نکته

به منظور تمیز نگه داشتن پیام‌های کامیت، توصیه می‌کنیم تا حد امکان کامیت‌های خود را بین زیردرخت‌ها و پروژه اصلی تفکیک کنید. به عبارت دیگر، اگر تغییری ایجاد می‌کنید که هم روی کتابخانه و هم روی برنامه اصلی تأثیر می‌گذارد، آن را در دو کامیت جداگانه ثبت کنید. به این ترتیب، زمانی که بعداً کامیت‌های کتابخانه را تفکیک می‌کنید، توضیحات آن‌ها همچنان با مفهوم خواهند بود. اما اگر این موضوع برای شما اهمیتی ندارد، انجام آن ضروری نیست. دستور git subtree به سادگی بخش‌های غیرمرتبط با کتابخانه در کامیت را هنگام تفکیک آن به زیرپروژه نادیده می‌گیرد.

add <local-commit>, add <repository> <remote-ref>

زیردرخت <prefix> را با وارد کردن محتویات آن از <local-commit> داده‌شده یا از <repository> و <remote-ref> مشخص‌شده ایجاد می‌کند. یک کامیت جدید به صورت خودکار ایجاد می‌شود که تاریخچه پروژه واردشده را با تاریخچه شما پیوند می‌دهد. با --squash, به جای کل تاریخچه، تنها یک کامیت منفرد از زیرپروژه وارد می‌شود.

merge <local-commit> [<repository>]

تغییرات اخیر تا <local-commit> را در زیردرخت <prefix> ادغام می‌کند. مانند git merge معمولی، این کار تغییرات محلی خودتان را حذف نمی‌کند؛ بلکه تنها آن تغییرات را با جدیدترین <local-commit> ادغام می‌نماید. با --squash, به جای ادغام کل تاریخچه، تنها یک کامیت حاوی تمام تغییرات ایجاد می‌شود.

اگر از --squash استفاده کنید، جهت ادغام همواره نیازی نیست رو به جلو باشد؛ برای مثال، می‌توانید از این دستور برای بازگشت در زمان از v2.5 به v2.4 استفاده کنید. اگر ادغام شما باعث ایجاد تداخل شود، می‌توانید آن را به روش‌های معمول برطرف کنید.

هنگام استفاده از --squash, اگر ادغام قبلی با --squash یک برچسب نشانه‌دار (annotated tag) از مخزن زیردرخت را ادغام کرده باشد، آن برچسب باید به صورت محلی در دسترس باشد. اگر <repository> داده شود، برچسب مفقود به صورت خودکار از آن مخزن واکشی خواهد شد.

split [<local-commit>] [<repository>]

یک تاریخچه جدید و مصنوعی از تاریخچه زیردرخت <prefix> در <local-commit> یا در صورت مشخص نشدن <local-commit> از HEAD استخراج می‌کند. تاریخچه جدید فقط شامل کامیت‌هایی (شامل ادغام‌ها) است که روی <prefix> تأثیر داشته‌اند، و هر یک از این کامیت‌ها اکنون محتویات <prefix> را به جای یک زیرپوشه، در ریشه پروژه قرار می‌دهند. بدین ترتیب، تاریخچه جدید ایجادشده برای صدور به عنوان یک مخزن گیت مستقل مناسب خواهد بود.

پس از تفکیک موفق، یک شناسه کامیت منفرد در خروجی استاندارد چاپ می‌شود. این شناسه متناظر با HEAD درخت ایجادشده جدید است که می‌توانید آن را به هر نحو دلخواه مدیریت کنید.

تفکیک‌های مکرر بر روی دقیقاً همان تاریخچه تضمین می‌شود که یکسان باشند (یعنی شناسه‌های کامیت یکسانی تولید کنند) تا زمانی که تنظیمات ارائه‌شده به split (مانند --annotate) یکسان باشند. به همین دلیل، اگر کامیت‌های جدیدی اضافه کنید و مجدداً تفکیک نمایید، کامیت‌های جدید به عنوان کامیت‌هایی روی تاریخچه تولیدشده قبلی پیوست می‌شوند، بنابراین git merge و دستورهای مرتبط همان‌طور که انتظار می‌رود کار خواهند کرد.

هنگامی که یک ادغام قبلی با --squash یک برچسب نشانه‌دار از مخزن زیردرخت را ادغام کرده باشد، آن برچسب باید به صورت محلی در دسترس باشد. اگر <repository> مشخص شده باشد، برچسب مفقود به صورت خودکار از آن مخزن دریافت خواهد شد.

pull <repository> <remote-ref>

دقیقاً مشابه merge عمل می‌کند، اما مانند git pull ارجاع داده‌شده را از مخزن ریموت مشخص‌شده واکشی می‌نماید.

push <repository> [+][<local-commit>:]<remote-ref>

یک عملیات split را با استفاده از زیردرخت <prefix> در <local-commit> اجرا می‌کند و سپس با اجرای git push نتیجه را به <repository> و <remote-ref> ارسال می‌نماید. از این قابلیت می‌توان برای ارسال زیردرخت خود به شاخه‌های مختلف مخزن ریموت استفاده کرد. دقیقاً همانند split, اگر <local-commit> داده نشود، از HEAD استفاده می‌شود. علامت ابتدایی و اختیاری + نادیده گرفته می‌شود.

-q, --quiet

پیام‌های خروجی غیرضروری را در stderr سرکوب می‌کند.

-d, --debug

پیام‌های خروجی خطایابی بیشتری را در stderr تولید می‌کند.

-P <prefix>, --prefix=<prefix>

مسیر زیردرخت مورد نظر را در مخزن مشخص می‌کند. این گزینه برای تمامی دستورها الزامی است.

-S[<keyid>], --gpg-sign[=<keyid>], --no-gpg-sign

کامیت‌ها را با GPG امضا می‌کند. آرگومان keyid اختیاری است و به طور پیش‌فرض از هویت ثبت‌کننده کامیت استفاده می‌شود؛ --no-gpg-sign برای لغو گزینه --gpg-sign که قبلاً در خط فرمان داده شده، کاربرد دارد.

این گزینه‌ها برای add و merge همچنین می‌توانند به pull (که merge را پوشش می‌دهد)، split --rejoin (که بسته به مورد add یا merge را پوشش می‌دهد)، و push --rejoin (که split --rejoin را پوشش می‌دهد) داده شوند.

--squash

به جای ادغام کل تاریخچه از پروژه زیردرخت، تنها یک کامیت منفرد حاوی تمام تغییراتی که می‌خواهید ادغام شوند ایجاد می‌کند و سپس آن کامیت جدید را در پروژه شما ادغام می‌نماید.

استفاده از این گزینه به کاهش شلوغی گزارش تاریخچه (log) کمک می‌کند. کاربران به ندرت می‌خواهند همه تغییراتی را که بین نسخه v1.0 و v1.1 کتابخانه رخ داده است ببینند، زیرا هیچ‌یک از نسخه‌های میانی هرگز در برنامه آن‌ها گنجانده نشده است.

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

علاوه بر این، با --squash, می‌توانید بین نسخه‌های مختلف یک زیردرخت به جلو و عقب جابه‌جا شوید، نه صرفاً رو به جلو. دستور git subtree merge --squash همیشه زیردرخت را دقیقاً مطابق با کامیت مشخص‌شده تنظیم می‌کند، حتی اگر رسیدن به آن کامیت مستلزم بازگرداندن تغییراتی باشد که قبلاً اضافه شده بودند.

چه از --squash استفاده کنید یا خیر، تغییرات ایجادشده در مخزن محلی شما دست‌نخورده باقی می‌مانند و می‌توان بعداً آن‌ها را تفکیک کرده و به بالادست به زیرپروژه ارسال نمود.

-m <message>, --message=<message>

مقدار <message> را به عنوان پیام کامیت برای کامیت ادغام مشخص می‌کند.

این گزینه‌ها برای split همچنین می‌توانند به push (که split را پوشش می‌دهد) داده شوند.

--annotate=<annotation>

هنگام ایجاد تاریخچه ترکیبی، عبارت <annotation> را به عنوان پیشوند به هر پیام کامیت اضافه می‌کند. از آنجا که ما کامیت‌های جدیدی با همان پیام کامیت اما احتمالاً با محتوای متفاوت نسبت به کامیت‌های اصلی ایجاد می‌کنیم، این گزینه می‌تواند به تفکیک آن‌ها و جلوگیری از سردرگمی کمک کند.

هر بار که عملیات تفکیک را انجام می‌دهید، باید از همان <annotation> یکسان استفاده کنید؛ در غیر این صورت تضمینی وجود ندارد که تاریخچه بازتولیدشده جدید با تاریخچه قبلی یکسان باشد. این موضوع مانع از کارکرد صحیح عملیات ادغام خواهد شد. ابزار git subtree تلاش می‌کند تا در هر صورت کار را پیش ببرد، به ویژه اگر از --rejoin استفاده کنید، اما ممکن است همیشه کارساز نباشد.

-b <branch>, --branch=<branch>

پس از ایجاد تاریخچه ترکیبی، شاخه جدیدی به نام <branch> حاوی تاریخچه جدید ایجاد می‌کند. این شاخه برای ارسال مستقیم و فوری به بالادست مناسب است. شاخه <branch> نباید از قبل وجود داشته باشد.

--ignore-joins

اگر از --rejoin استفاده کنید، git subtree تلاش می‌کند بازسازی تاریخچه خود را بهینه‌سازی کند تا فقط کامیت‌های جدید پس از آخرین --rejoin تولید شوند. --ignore-joins این رفتار را غیرفعال کرده و آن را مجبور می‌کند کل تاریخچه را دوباره تولید نماید. در یک پروژه بزرگ، این کار ممکن است زمان زیادی ببرد.

--onto=<onto>

اگر زیردرخت شما در ابتدا با روشی غیر از git subtree وارد شده باشد، ممکن است تاریخچه آن با آنچه git subtree انتظار دارد مطابقت نداشته باشد. در این حالت، می‌توانید شناسه کامیت <onto> را که متناظر با اولین بازبینی واردشده از تاریخچه زیرپروژه به پروژه شماست مشخص کنید، و git subtree تلاش خواهد کرد تاریخچه خود را از آن نقطه بسازد.

اگر از git subtree add استفاده کرده‌اید، هرگز به این گزینه نیاز نخواهید داشت.

--rejoin

پس از تفکیک، تاریخچه ترکیبی جدید ایجادشده را دوباره در پروژه اصلی شما ادغام می‌کند. به این ترتیب، تفکیک‌های بعدی می‌توانند فقط بخشی از تاریخچه را جستجو کنند که پس از جدیدترین --rejoin اضافه شده است.

اگر کامیت‌های تفکیک‌شده شما در نهایت در زیرپروژه بالادست ادغام شوند و سپس بخواهید آخرین نسخه بالادست را دریافت کنید، این امر به الگوریتم ادغام گیت اجازه می‌دهد هوشمندانه‌تر از تداخل‌ها جلوگیری کند (چرا که می‌داند این کامیت‌های ترکیبی از قبل بخشی از مخزن بالادست هستند).

متأسفانه، استفاده از این گزینه باعث می‌شود git log یک نسخه اضافی از هر کامیت جدید ایجادشده (نسخه اصلی و نسخه ترکیبی) را نمایش دهد.

اگر تمام ادغام‌های خود را با --squash انجام می‌دهید، مطمئن شوید که هنگام اجرای split --rejoin نیز از --squash استفاده می‌کنید.

فرض کنید یک مخزن محلی دارید و می‌خواهید یک کتابخانه خارجی را به آن اضافه کنید. در این مثال، مخزن git-subtree را به عنوان یک زیرپوشه از مخزن موجود git-extensions در مسیر ~/git-extensions/ اضافه می‌کنیم:

$ git subtree add --prefix=git-subtree --squash \
        git://github.com/apenwarr/git-subtree.git master

عبارت master باید یک ارجاع ریموت معتبر باشد و می‌تواند نام شاخه دیگری باشد.

می‌توانید از پرچم --squash صرف‌نظر کنید، اما انجام این کار تعداد کامیت‌های موجود در مخزن محلی شما را افزایش می‌دهد.

اکنون در مخزن git-extensions خود، یک پوشه ~/git-extensions/git-subtree داریم که حاوی کدهای شاخه master از git://github.com/apenwarr/git-subtree.git است.

بیایید از مخزن کد منبع git به عنوان مثال استفاده کنیم. ابتدا، نسخه خود را از مخزن git.git دریافت کنید:

$ git clone git://git.kernel.org/pub/scm/git/git.git test-git
$ cd test-git

ابزار gitweb (کامیت 1130ef3) از زمان کامیت 0a8f4f0 در git ادغام شد و پس از آن دیگر به طور مستقل نگهداری نشد. اما فرض کنید که به طور مستقل نگهداری می‌شد و ما می‌خواستیم تغییرات گیت در gitweb را از آن زمان به بعد استخراج کنیم تا با پروژه بالادست به اشتراک بگذاریم. می‌توانید این کار را انجام دهید:

$ git subtree split --prefix=gitweb --annotate='(split) ' \
        0a8f4f0^.. --onto=1130ef3 --rejoin \
        --branch gitweb-latest
$ gitk gitweb-latest
$ git push git@github.com:whatever/gitweb.git gitweb-latest:master

(ما از 0a8f4f0^.. استفاده می‌کنیم زیرا به معنای «تمام تغییرات از 0a8f4f0 تا نسخه فعلی، شامل خود 0a8f4f0» است.)

اگر gitweb در ابتدا با استفاده از git subtree add ادغام شده بود (یا یک تفکیک قبلی با مشخص کردن --rejoin انجام شده بود)، آن‌گاه می‌توانستید تمام تفکیک‌های خود را بدون نیاز به به خاطر سپردن شناسه‌های نامتعارف کامیت انجام دهید:

$ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \
        --branch gitweb-latest2

و می‌توانید تغییرات را به همان سادگی از پروژه بالادستی ادغام کنید:

$ git subtree pull --prefix=gitweb \
        git@github.com:whatever/gitweb.git master

یا با استفاده از --squash، می‌توانید در واقع به نسخه قبلی gitweb برگردید:

$ git subtree merge --prefix=gitweb --squash gitweb-latest~10

سپس تغییراتی اعمال کنید:

$ date >gitweb/myfile
$ git add gitweb/myfile
$ git commit -m 'created myfile'

و دوباره به جلو حرکت سریع (fast-forward) انجام دهید:

$ git subtree merge --prefix=gitweb --squash gitweb-latest

و مشاهده کنید که تغییرات شما همچنان دست‌نخورده باقی مانده است:

$ ls -l gitweb/myfile

و می‌توانید آن را تفکیک کرده و تغییرات خود را نسبت به gitweb استاندارد مشاهده کنید:

git log gitweb-latest..$(git subtree split --prefix=gitweb)

فرض کنید یک دایرکتوری سورس با فایل‌ها و زیرپوشه‌های فراوان دارید و می‌خواهید دایرکتوری lib را به پروژه گیت مستقل خود تبدیل کنید. روش سریعی برای انجام این کار در ادامه آورده شده است:

ابتدا، مخزن جدید را در هر مکانی که می‌خواهید ایجاد کنید:

$ <go to the new location>
$ git init --bare

بازگشت به دایرکتوری اصلی شما:

$ git subtree split --prefix=lib --annotate="(split)" -b split

سپس شاخه جدید را به مخزن خالی جدید ارسال کنید:

$ git push <new-repo> split:master

نوشته‌شده توسط Avery Pennarun <apenwarr@gmail.com[1]>

بخشی از مجموعه git(1)

1.
apenwarr@gmail.com
mailto:apenwarr@gmail.com
2026-06-29 Git 2.55.0