Chapters ▾ 2nd Edition

7.11 ابزارهای گیت (Git Tools) - سابماژول ها (Submodules)

سابماژول ها (Submodules)

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

در اینجا یک مثال می‌آوریم. فرض کنید شما در حال توسعه یک وب‌سایت هستید و می‌خواهید فیدهای Atom بسازید. به جای نوشتن کد تولید Atom خودتان، تصمیم می‌گیرید از یک کتابخانه استفاده کنید. احتمالاً باید این کد را یا از یک کتابخانه مشترک مثل نصب CPAN یا Ruby gem وارد کنید، یا کد منبع آن را در درخت پروژه خود کپی کنید. مشکل وارد کردن کتابخانه این است که به سختی می‌توانید آن را شخصی‌سازی کنید و معمولاً پیاده‌سازی آن هم سخت‌تر است، چون باید مطمئن شوید همه کلاینت‌ها به آن کتابخانه دسترسی دارند. مشکل کپی کردن کد در پروژه خودتان این است که هر تغییر سفارشی که اعمال می‌کنید، هنگام به‌روزرسانی‌های نسخه اصلی ادغام دشواری دارد.

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

شروع با سابماژول ها (Starting with Submodules)

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

بیایید با اضافه کردن یک مخزن گیت موجود به عنوان زیرماژول در مخزنی که روی آن کار می‌کنیم شروع کنیم. برای اضافه کردن یک زیرماژول جدید، از دستور git submodule add به همراه URL مطلق یا نسبی پروژه‌ای که می‌خواهید دنبال کنید، استفاده می‌کنید. در این مثال، ما یک کتابخانه به نام “DbConnector” اضافه خواهیم کرد.

$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.

به طور پیش‌فرض، زیرماژول‌ها زیرپروژه را در دایرکتوری‌ای با نام همان مخزن، که در اینجا “DbConnector” است، قرار می‌دهند. اگر می‌خواهید در مکان دیگری قرار دهید، می‌توانید مسیر مورد نظر را در انتهای دستور اضافه کنید.

اگر در این مرحله دستور git status را اجرا کنید، چند نکته جلب توجه می‌کند.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .gitmodules
	new file:   DbConnector

اول، باید فایل جدید .gitmodules را ببینید. این یک فایل پیکربندی است که نگاشت بین URL پروژه و زیرشاخه محلی که آن را کشیده‌اید ذخیره می‌کند:

[submodule "DbConnector"]
	path = DbConnector
	url = https://github.com/chaconinc/DbConnector

اگر چند زیرماژول داشته باشید، چندین ورودی در این فایل خواهید دید. مهم است بدانید که این فایل مثل سایر فایل‌های شما (مثلاً .gitignore) تحت کنترل نسخه است. این فایل همراه با بقیه پروژه شما پوش و پول می‌شود. این روش به دیگران که پروژه را کلون می‌کنند، اطلاع می‌دهد که زیرماژول‌ها را از کجا دریافت کنند.

یادداشت
 از آنجا که URL موجود در فایل .gitmodules اولین آدرسی است که دیگران برای کلون یا دریافت پروژه تلاش می‌کنند، تا حد امکان از URL‌ای استفاده کنید که برای آن‌ها قابل دسترسی باشد.
برای مثال، اگر URL متفاوتی برای ارسال تغییرات (push) نسبت به URLی که دیگران برای دریافت (pull) استفاده می‌کنند دارید، از URLی استفاده کنید که دیگران به آن دسترسی دارند.
شما می‌توانید این مقدار را به صورت محلی با دستور `git config submodule.DbConnector.url PRIVATE_URL` برای استفاده شخصی خود بازنویسی کنید.
در صورت امکان، استفاده از URL نسبی می‌تواند مفید باشد.

مورد دیگر که در خروجی git status می‌بینید مربوط به پوشه پروژه است. اگر روی آن git diff اجرا کنید، نکته جالبی مشاهده خواهید کرد:

$ git diff --cached DbConnector
diff --git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
--- /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc

اگرچه DbConnector یک زیرپوشه در دایرکتوری کاری شما است، Git آن را به عنوان یک ساب‌ماژول می‌شناسد و زمانی که در آن دایرکتوری نیستید محتویات آن را دنبال نمی‌کند. در عوض، Git آن را به عنوان یک کامیت خاص از آن مخزن می‌بیند.

اگر می‌خواهید خروجی فرق بهتری داشته باشید، می‌توانید گزینه --submodule را به git diff اضافه کنید.

$ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DbConnector"]
+       path = DbConnector
+       url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000...c3f01dc (new submodule)

زمانی که commit انجام می‌دهید، چیزی شبیه به این خواهید دید:

$ git commit -am 'Add DbConnector module'
[master fb9093c] Add DbConnector module
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 DbConnector

به حالت 160000 در ورودی DbConnector توجه کنید. این حالت خاص در Git به این معنی است که شما یک کامیت را به عنوان ورودی دایرکتوری ثبت می‌کنید، نه به عنوان یک زیرپوشه یا فایل.

در نهایت، این تغییرات را push کنید:

$ git push origin master

کلون کردن یک پروژه با ساب‌ماژول‌ها (Cloning a Project with Submodules)

در اینجا پروژه‌ای با ساب‌ماژول درون آن را کلون می‌کنیم. وقتی چنین پروژه‌ای را کلون می‌کنید، به طور پیش‌فرض پوشه‌هایی که ساب‌ماژول دارند دریافت می‌شوند، اما هیچ فایلی در درون آن‌ها هنوز وجود ندارد:

$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject
$ ls -la
total 16
drwxr-xr-x   9 schacon  staff  306 Sep 17 15:21 .
drwxr-xr-x   7 schacon  staff  238 Sep 17 15:21 ..
drwxr-xr-x  13 schacon  staff  442 Sep 17 15:21 .git
-rw-r--r--   1 schacon  staff   92 Sep 17 15:21 .gitmodules
drwxr-xr-x   2 schacon  staff   68 Sep 17 15:21 DbConnector
-rw-r--r--   1 schacon  staff  756 Sep 17 15:21 Makefile
drwxr-xr-x   3 schacon  staff  102 Sep 17 15:21 includes
drwxr-xr-x   4 schacon  staff  136 Sep 17 15:21 scripts
drwxr-xr-x   4 schacon  staff  136 Sep 17 15:21 src
$ cd DbConnector/
$ ls
$

پوشه DbConnector وجود دارد، اما خالی است. شما باید دو دستور را از پوشه اصلی پروژه اجرا کنید: git submodule init برای مقداردهی اولیه فایل پیکربندی محلی شما، و git submodule update برای دریافت تمام داده‌های آن پروژه و چک‌اوت کامیت مناسب ذکر شده در پروژه اصلی:

$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'

اکنون زیرشاخه DbConnector دقیقاً در همان وضعیت است که در زمان کامیت قبلی بود.

اما راه ساده‌تری هم برای این کار وجود دارد. اگر هنگام اجرای دستور git clone گزینه --recurse-submodules را اضافه کنید، به‌طور خودکار هر زیرماژول موجود در مخزن را مقداردهی اولیه و به‌روزرسانی می‌کند، حتی اگر زیرماژول‌ها خودشان زیرماژول‌هایی داشته باشند.

$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'

اگر قبلاً پروژه را کلون کرده‌اید و فراموش کرده‌اید --recurse-submodules را استفاده کنید، می‌توانید مراحل git submodule init و git submodule update را با اجرای git submodule update --init ترکیب کنید. برای مقداردهی اولیه، دریافت و چک‌اوت هر زیرماژول تو در تو، می‌توانید از دستور مطمئن git submodule update --init --recursive استفاده کنید.

کار کردن روی پروژه‌ای با ساب‌ماژول‌ها (Working on a Project with Submodules)

اکنون یک نسخه از پروژه با ساب‌ماژول‌ها داریم و قصد داریم با هم‌تیمی‌هایمان روی هر دو پروژه اصلی و پروژه ساب‌ماژول همکاری کنیم.

دریافت تغییرات جدید از مخزن ساب‌ماژول (Pulling in Upstream Changes from the Submodule Remote)

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

اگر بخواهید تغییرات جدید را در یک ساب‌ماژول بررسی کنید، می‌توانید به پوشه آن بروید و دستورهای git fetch و سپس git merge روی شاخه بالادستی (upstream branch) اجرا کنید تا کد محلی به‌روزرسانی شود.

$ git fetch
From https://github.com/chaconinc/DbConnector
   c3f01dc..d0354fc  master     -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
 scripts/connect.sh | 1 +
 src/db.c           | 1 +
 2 files changed, 2 insertions(+)

حالا اگر به پروژه اصلی برگردید و دستور git diff --submodule را اجرا کنید، می‌بینید که زیرماژول به‌روزرسانی شده و فهرستی از کامیت‌های اضافه‌شده به آن را نمایش می‌دهد. اگر نمی‌خواهید هر بار برای اجرای git diff گزینه --submodule را تایپ کنید، می‌توانید آن را به عنوان فرمت پیش‌فرض با تنظیم مقدار پیکربندی diff.submodule روی “log” قرار دهید.

$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
  > more efficient db routine
  > better connection routine

اگر در این مرحله کامیت کنید، زیرماژول را به گونه‌ای قفل می‌کنید که وقتی دیگران به‌روزرسانی می‌کنند، کد جدید در آن موجود باشد.

روش ساده‌تری هم برای این کار وجود دارد، اگر ترجیح می‌دهید به صورت دستی زیرشاخه را دریافت و ادغام نکنید. اگر دستور git submodule update --remote را اجرا کنید، گیت به زیرماژول‌های شما می‌رود و به صورت خودکار دریافت و به‌روزرسانی را انجام می‌دهد.

$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   3f19983..d0354fc  master     -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'

این دستور به طور پیش‌فرض فرض می‌کند که می‌خواهید چک‌اوت را به شاخه پیش‌فرض مخزن زیرماژول از راه دور (که توسط HEAD روی مخزن راه دور نشان داده می‌شود) به‌روزرسانی کنید. البته می‌توانید این را به چیز دیگری تغییر دهید. برای مثال، اگر می‌خواهید زیرماژول DbConnector شاخه “stable” آن مخزن را دنبال کند، می‌توانید این را یا در فایل .gitmodules (تا همه دیگران هم آن را دنبال کنند) یا فقط در فایل محلی .git/config تنظیم کنید. بیایید آن را در فایل .gitmodules تنظیم کنیم:

$ git config -f .gitmodules submodule.DbConnector.branch stable

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   27cf5d3..c87d55d  stable -> origin/stable
Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687'

اگر گزینه -f .gitmodules را حذف کنید، تغییر فقط برای شما اعمال می‌شود، اما منطقی‌تر است که این اطلاعات را همراه با مخزن ذخیره کنید تا همه دیگران هم آن را دنبال کنند.

وقتی در این مرحله دستور git status را اجرا کنیم، گیت نشان می‌دهد که ما روی زیرماژول “کامیت‌های جدید” داریم.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

  modified:   .gitmodules
  modified:   DbConnector (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

اگر تنظیم پیکربندی status.submodulesummary را فعال کنید، گیت همچنین خلاصه کوتاهی از تغییرات زیرماژول‌های شما را نمایش می‌دهد:

$ git config status.submodulesummary 1

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   .gitmodules
	modified:   DbConnector (new commits)

Submodules changed but not updated:

* DbConnector c3f01dc...c87d55d (4):
  > catch non-null terminated lines

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

$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "DbConnector"]
        path = DbConnector
        url = https://github.com/chaconinc/DbConnector
+       branch = stable
 Submodule DbConnector c3f01dc..c87d55d:
  > catch non-null terminated lines
  > more robust error handling
  > more efficient db routine
  > better connection routine

این بسیار جالب است چون ما می‌توانیم لاگ کامیت‌هایی که قرار است در زیرماژول خود کامیت کنیم را ببینیم. پس از کامیت، می‌توانید این اطلاعات را در آینده نیز با اجرای دستور git log -p مشاهده کنید.

$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: Scott Chacon <schacon@gmail.com>
Date:   Wed Sep 17 16:37:02 2014 +0200

    updating DbConnector for bug fixes

diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "DbConnector"]
        path = DbConnector
        url = https://github.com/chaconinc/DbConnector
+       branch = stable
Submodule DbConnector c3f01dc..c87d55d:
  > catch non-null terminated lines
  > more robust error handling
  > more efficient db routine
  > better connection routine

گیت به طور پیش‌فرض سعی می‌کند تمام زیرماژول‌های شما را هنگام اجرای git submodule update --remote به‌روزرسانی کند. اگر تعداد زیادی زیرماژول دارید، ممکن است بخواهید فقط نام زیرماژولی که می‌خواهید به‌روزرسانی کنید را وارد کنید.

دریافت تغییرات از مخزن پروژه اصلی (Pulling Upstream Changes from the Project Remote)

حالا بیایید خود را به جای همکار شما بگذاریم که کلون محلی خودش از مخزن MainProject را دارد. اجرای ساده git pull برای دریافت تغییرات تازه کامیت‌شده کافی نیست:

$ git pull
From https://github.com/chaconinc/MainProject
   fb9093c..0a24cfc  master     -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
   c3f01dc..c87d55d  stable     -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
 .gitmodules         | 2 +-
 DbConnector         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

$ git status
 On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   DbConnector (new commits)

Submodules changed but not updated:

* DbConnector c87d55d...c3f01dc (4):
  < catch non-null terminated lines
  < more robust error handling
  < more efficient db routine
  < better connection routine

no changes added to commit (use "git add" and/or "git commit -a")

به طور پیش‌فرض، دستور git pull به صورت بازگشتی تغییرات زیرماژول‌ها را دریافت می‌کند، همان‌طور که در خروجی اولین دستور بالا مشاهده می‌کنیم. با این حال، این دستور زیرماژول‌ها را به‌روزرسانی نمی‌کند. این موضوع در خروجی دستور git status نشان داده می‌شود که وضعیت زیرماژول را “modified” و دارای “new commits” نمایش می‌دهد. علاوه بر این، کروشه‌هایی که کامیت‌های جدید را نمایش می‌دهند به سمت چپ (<) اشاره دارند، که نشان می‌دهد این کامیت‌ها در پروژه اصلی (MainProject) ثبت شده‌اند اما در نسخه‌ی محلی زیرماژول DbConnector وجود ندارند. برای نهایی کردن به‌روزرسانی، باید دستور git submodule update را اجرا کنید:

$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'

$ git status
 On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

توجه داشته باشید که برای اطمینان، باید دستور git submodule update را با گزینه --init اجرا کنید، در صورتی که کامیت‌های پروژه اصلی که تازه دریافت کردید، زیرماژول‌های جدیدی اضافه کرده باشند، و همچنین با گزینه --recursive اگر زیرماژول‌ها خودشان زیرماژول‌های تو در تو داشته باشند.

اگر می‌خواهید این فرایند به صورت خودکار انجام شود، می‌توانید گزینه --recurse-submodules را به دستور git pull اضافه کنید (از نسخه Git 2.14 به بعد). این کار باعث می‌شود Git بلافاصله پس از pull، دستور git submodule update را اجرا کند و زیرماژول‌ها را در وضعیت صحیح قرار دهد. علاوه بر این، اگر می‌خواهید Git همیشه pull را با گزینه --recurse-submodules انجام دهد، می‌توانید گزینه پیکربندی submodule.recurse را روی true تنظیم کنید (این ویژگی از نسخه Git 2.15 برای دستور git pull فعال است). این گزینه باعث می‌شود Git گزینه --recurse-submodules را برای تمام دستورات پشتیبانی‌شده (به جز clone) به‌کار ببرد.

یک وضعیت خاص هنگام دریافت به‌روزرسانی‌های پروژه اصلی ممکن است رخ دهد: ممکن است مخزن بالادستی (upstream) آدرس URL زیرماژول را در فایل .gitmodules در یکی از کامیت‌هایی که دریافت می‌کنید تغییر داده باشد. این موضوع ممکن است مثلاً زمانی اتفاق بیفتد که پروژه زیرماژول پلتفرم میزبانی خود را تغییر دهد. در این صورت، ممکن است دستور git pull --recurse-submodules یا git submodule update با خطا مواجه شود، اگر پروژه اصلی به کامیتی از زیرماژول اشاره کند که در مخزن راه دور زیرماژول که در مخزن شما به صورت محلی تنظیم شده، پیدا نشود. برای رفع این مشکل، باید دستور git submodule sync را اجرا کنید:

# copy the new URL to your local config
$ git submodule sync --recursive
# update the submodule from the new URL
$ git submodule update --init --recursive

کار کردن روی یک زیرماژول (Working on a Submodule)

احتمالاً اگر از زیرماژول‌ها استفاده می‌کنید، دلیلش این است که واقعاً می‌خواهید همزمان روی کد زیرماژول و کد پروژه اصلی (یا چند زیرماژول مختلف) کار کنید. در غیر این صورت، احتمالاً از یک سیستم مدیریت وابستگی ساده‌تر مانند Maven یا Rubygems استفاده می‌کردید.

پس حالا بیایید یک مثال از ایجاد تغییرات در زیرماژول به موازات کار روی پروژه اصلی را بررسی کنیم و ببینیم چگونه این تغییرات را همزمان کامیت و منتشر کنیم.

تاکنون، وقتی دستور git submodule update را برای دریافت تغییرات از مخازن زیرماژول اجرا می‌کردیم، Git تغییرات را دریافت کرده و فایل‌ها را در زیرشاخه به‌روزرسانی می‌کرد، اما زیرمخزن را در وضعیتی به نام “detached HEAD” قرار می‌داد. این یعنی هیچ شاخه کاری محلی (مثلاً master) وجود ندارد که تغییرات را دنبال کند. عدم وجود شاخه کاری که تغییرات را دنبال کند به این معنی است که حتی اگر در زیرماژول تغییراتی را کامیت کنید، این تغییرات ممکن است در دفعه بعدی اجرای git submodule update از بین بروند. اگر می‌خواهید تغییرات در زیرماژول ردیابی شوند، باید مراحل اضافی‌تری انجام دهید.

 برای آسان‌تر کردن کار با زیرماژول و ویرایش آن، باید دو کار انجام دهید.
ابتدا باید به هر زیرماژول بروید و یک شاخه برای کار کردن انتخاب کنید.
سپس باید به گیت بگویید در صورت ایجاد تغییرات و سپس اجرای دستور `git submodule update --remote` که تغییرات جدیدی از مخزن بالادستی می‌آورد، چه رفتاری داشته باشد.
گزینه‌ها این است که می‌توانید تغییرات را با کار محلی خود ادغام کنید، یا سعی کنید کار محلی خود را روی تغییرات جدید پایه‌گذاری مجدد (rebase) کنید.

اول از همه، به دایرکتوری زیرماژول خود برویم و یک شاخه چک‌اوت کنیم.

$ cd DbConnector/
$ git checkout stable
Switched to branch 'stable'
 بیایید تلاش کنیم زیرماژول خود را با گزینه "`merge`" به‌روزرسانی کنیم.
برای مشخص کردن دستی، کافی است گزینه `--merge` را به فرمان `update` خود اضافه کنیم.
در اینجا می‌بینیم که تغییری در سرور برای این زیرماژول ایجاد شده و این تغییر با موفقیت ادغام می‌شود.
$ cd ..
$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   c87d55d..92c7337  stable     -> origin/stable
Updating c87d55d..92c7337
Fast-forward
 src/main.c | 1 +
 1 file changed, 1 insertion(+)
Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea'

اگر وارد پوشه DbConnector شویم، تغییرات جدید قبلاً در شاخه محلی stable ما ادغام شده‌اند. حالا ببینیم چه اتفاقی می‌افتد وقتی خودمان تغییر محلی در کتابخانه ایجاد کنیم و هم‌زمان شخص دیگری تغییر دیگری را در شاخه اصلی (upstream) ارسال کند.

$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'Unicode support'
[stable f906e16] Unicode support
 1 file changed, 1 insertion(+)

اگر حالا زیرماژول خود را به‌روزرسانی کنیم، می‌توانیم ببینیم وقتی تغییر محلی داریم و در upstream هم تغییری هست که باید وارد کنیم، چه اتفاقی می‌افتد.

$ cd ..
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: Unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'

اگر گزینه‌های --rebase یا --merge را فراموش کنید، گیت فقط زیرماژول را به آخرین نسخه روی سرور به‌روزرسانی می‌کند و پروژه شما را در وضعیت detached HEAD قرار می‌دهد.

$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'

اگر این اتفاق افتاد، نگران نباشید، می‌توانید به سادگی به پوشه زیرماژول برگردید و شاخه خود را دوباره checkout کنید (که هنوز تغییرات شما را دارد) و سپس به‌صورت دستی شاخه origin/stable (یا هر شاخه ریموتی که می‌خواهید) را merge یا rebase کنید.

اگر تغییرات خود را در زیرماژول commit نکرده باشید و فرمان submodule update را اجرا کنید که باعث بروز مشکل شود، گیت تغییرات را دریافت می‌کند اما کاری نمی‌کند که کار ذخیره‌نشده شما در زیرماژول بازنویسی شود.

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   5d60ef9..c75e92a  stable     -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
	scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'

اگر تغییراتی ایجاد کرده باشید که با تغییرات upstream تداخل داشته باشند، گیت هنگام اجرای به‌روزرسانی به شما اطلاع می‌دهد.

$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'

می‌توانید به پوشه زیرماژول بروید و تداخل را همان‌طور که معمولاً انجام می‌دهید، رفع کنید.

انتشار تغییرات زیرماژول (Publishing Submodule Changes)

حالا ما تغییراتی در پوشه زیرماژول داریم. بعضی از این تغییرات از طریق به‌روزرسانی‌های upstream دریافت شده‌اند و بعضی دیگر به‌صورت محلی ایجاد شده‌اند و هنوز برای هیچکس دیگری در دسترس نیستند چون آن‌ها را هنوز ارسال (push) نکرده‌ایم.

$ git diff
Submodule DbConnector c87d55d..82d2ad3:
  > Merge from origin/stable
  > Update setup script
  > Unicode support
  > Remove unnecessary method
  > Add new option for conn pooling

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

برای اطمینان از اینکه این مشکل پیش نیاید، می‌توانید از گیت بخواهید قبل از push کردن پروژه اصلی، بررسی کند که همه زیرماژول‌های شما به درستی push شده‌اند. فرمان git push آرگومان --recurse-submodules را می‌پذیرد که می‌تواند روی “check” یا “on-demand” تنظیم شود. گزینه “check” باعث می‌شود اگر هر کدام از تغییرات commit شده زیرماژول‌ها push نشده باشند، فرمان push به‌طور کامل متوقف شود.

$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
  DbConnector

Please try

	git push --recurse-submodules=on-demand

or cd to the path and use

	git push

to push them to a remote.

همان‌طور که می‌بینید، همچنین نکات مفیدی درباره کارهایی که ممکن است بخواهید انجام دهید به شما می‌دهد. گزینه ساده این است که به هر زیرماژول بروید و به‌صورت دستی به ریموت‌ها push کنید تا مطمئن شوید در دسترس دیگران قرار دارد و سپس دوباره این push را امتحان کنید. اگر می‌خواهید رفتار “check” برای همه push‌ها به‌صورت پیش‌فرض اجرا شود، می‌توانید این تنظیم را با دستور git config push.recurseSubmodules check انجام دهید.

گزینه دیگر استفاده از مقدار “on-demand” است که این کار را به صورت خودکار برای شما انجام می‌دهد.

$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
   c75e92a..82d2ad3  stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
   3d6d338..9a377d1  master -> master

همان‌طور که می‌بینید، گیت ابتدا وارد ماژول DbConnector شده و آن را push کرده، سپس پروژه اصلی را push کرده است. اگر push زیرماژول به هر دلیلی شکست بخورد، push پروژه اصلی نیز شکست خواهد خورد. می‌توانید این رفتار را با دستور git config push.recurseSubmodules on-demand به‌صورت پیش‌فرض تنظیم کنید.

ادغام تغییرات زیرماژول (Merging Submodule Changes)

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

اگر یکی از کامیت‌ها پیش‌نیاز مستقیم کامیت دیگر باشد (ادغام بدون درگیری یا fast-forward)، گیت به سادگی کامیت جدیدتر را برای ادغام انتخاب می‌کند و همه چیز خوب پیش می‌رود.

اما گیت حتی برای یک ادغام ساده نیز به طور خودکار اقدام نمی‌کند. اگر کامیت‌های زیرماژول از هم جدا شده و نیاز به ادغام داشته باشند، چیزی شبیه به این را خواهید دید:

$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/chaconinc/MainProject
   9a377d1..eb974f8  master     -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.

در واقع، گیت متوجه شده که دو شاخه نقاطی در تاریخچه زیرماژول دارند که از هم جدا شده‌اند و نیازمند ادغام هستند. این موضوع را به صورت “merge following commits not found” توضیح می‌دهد، که کمی گیج‌کننده است، اما بعداً دلیل آن را شرح خواهیم داد.

برای حل مشکل، باید بفهمید زیرماژول باید در چه وضعیتی باشد. به طرز عجیبی، گیت اطلاعات زیادی به شما نمی‌دهد، حتی شناسه‌های SHA-1 کامیت‌های هر دو شاخه را هم نشان نمی‌دهد. خوشبختانه فهمیدن این موضوع ساده است. اگر دستور git diff را اجرا کنید، می‌توانید SHA-1 کامیت‌های ثبت شده در هر دو شاخه‌ای که می‌خواستید ادغام کنید را به دست آورید.

$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector

در این مورد، eb41d76 کامیتی است که ما در زیرماژول داشتیم و c771610 کامیتی است که بالادستی داشت. اگر به دایرکتوری زیرماژول برویم، باید قبلاً روی eb41d76 باشد چون ادغام آن را تغییر نداده است. اگر به هر دلیلی چنین نبود، می‌توانید به سادگی یک شاخه بسازید و روی آن چک‌اوت کنید.

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

پس، به دایرکتوری زیرماژول می‌رویم، شاخه‌ای به نام “try-merge” بر اساس آن SHA-1 دوم از git diff ایجاد می‌کنیم و به صورت دستی ادغام را انجام می‌دهیم.

$ cd DbConnector

$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135

$ git branch try-merge c771610

$ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.

در اینجا یک تعارض واقعی در ادغام داشتیم، پس اگر آن را حل کنیم و کامیت کنیم، می‌توانیم به سادگی پروژه اصلی را با نتیجه به‌روزرسانی کنیم.

$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes

$ cd .. (2)
$ git diff (3)
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
 -Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector (4)

$ git commit -m "Merge Tom's Changes" (5)
[master 10d2c60] Merge Tom's Changes
  1. ابتدا تعارض را حل می‌کنیم.

  2. سپس به دایرکتوری پروژه اصلی بازمی‌گردیم.

  3. دوباره می‌توانیم SHA-1 ها را بررسی کنیم.

  4. ورودی زیرماژول که دچار تعارض شده را حل کنیم.

  5. ادغام را کامیت می‌کنیم.

ممکن است کمی گیج‌کننده باشد، اما واقعاً کار سختی نیست.

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

به همین دلیل پیام خطای قبلی “merge following commits not found” بود، چون گیت نمی‌توانست این کار را انجام دهد. این پیام گیج‌کننده است چون چه کسی انتظار دارد گیت سعی کند این کار را انجام دهد؟

اگر یک کامیت ادغام قابل قبول پیدا کند، چیزی شبیه به این خواهید دید:

$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:

  git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"

which will accept this suggestion.
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.

دستوری که گیت پیشنهاد می‌دهد، ایندکس را به گونه‌ای به‌روزرسانی می‌کند که گویی شما git add را اجرا کرده‌اید (که تعارض را برطرف می‌کند)، سپس کامیت می‌کند. اما احتمالاً نباید این کار را انجام دهید. می‌توانید به راحتی وارد دایرکتوری ساب‌ماژول شوید، تفاوت‌ها را ببینید، به این کامیت به‌روزرسانی سریع انجام دهید، به‌درستی تست کنید و سپس کامیت کنید.

$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward

$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forward to a common submodule child'

این کار همان نتیجه را می‌دهد، اما حداقل این‌طوری می‌توانید مطمئن شوید که همه چیز درست است و کد در دایرکتوری ساب‌ماژول شما هست وقتی کارتان تمام شد.

نکاتی درباره ساب‌ماژول‌ها (Submodule Tips)

چند کار وجود دارد که می‌توانید برای آسان‌تر کردن کار با ساب‌ماژول‌ها انجام دهید.

حلقه foreach روی زیرماژول‌ها (Submodule Foreach)

دستوری به نام foreach برای ساب‌ماژول‌ها وجود دارد که به شما اجازه می‌دهد یک دستور دلخواه را در هر ساب‌ماژول اجرا کنید. این خیلی مفید است اگر چندین ساب‌ماژول در یک پروژه داشته باشید.

مثلاً فرض کنید می‌خواهید یک ویژگی جدید شروع کنید یا یک باگ را رفع کنید و روی چند ساب‌ماژول کار می‌کنید. می‌توانید به راحتی تمام تغییرات در همه ساب‌ماژول‌ها را stash کنید.

$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable

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

$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'

ایده کلی همین است. یکی از کارهای واقعاً مفید این است که یک diff یکپارچه و زیبا از تغییرات در پروژه اصلی و تمام زیرپروژه‌ها ایجاد کنید.

$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)

      commit_pager_choice();

+     url = url_decode(url_orig);
+
      /* build alias_argv */
      alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
      alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
        return url_decode_internal(&url, len, NULL, &out, 0);
 }

+char *url_decode(const char *url)
+{
+       return url_decode_mem(url, strlen(url));
+}
+
 char *url_decode_parameter_name(const char **query)
 {
        struct strbuf out = STRBUF_INIT;

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

نام‌های مستعار مفید (Useful Aliases)

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

$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'

به این ترتیب می‌توانید به سادگی git supdate را برای به‌روزرسانی ساب‌ماژول‌ها اجرا کنید یا git spush را برای پوش با بررسی وابستگی‌های ساب‌ماژول.

Issues with Submodules (مشکلات ساب‌ماژول‌ها)

استفاده از ساب‌ماژول‌ها بدون مشکل نیست.

Switching branches (تغییر شاخه‌ها)

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

$ git --version
git version 2.12.2

$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'

$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...

$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
 2 files changed, 4 insertions(+)
 create mode 160000 CryptoLibrary

$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	CryptoLibrary/

nothing added to commit but untracked files present (use "git add" to track)

حذف این دایرکتوری سخت نیست، اما ممکن است کمی گیج‌کننده باشد که آنجا باشد. اگر آن را حذف کنید و سپس به شاخه‌ای که آن ساب‌ماژول را دارد برگردید، باید دستور submodule update --init را اجرا کنید تا دوباره آن را بازسازی کند.

$ git clean -ffdx
Removing CryptoLibrary/

$ git checkout add-crypto
Switched to branch 'add-crypto'

$ ls CryptoLibrary/

$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'

$ ls CryptoLibrary/
Makefile	includes	scripts		src

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

نسخه‌های جدیدتر گیت (گیت 2.13 به بالا) این موضوع را ساده کرده‌اند با افزودن گزینه --recurse-submodules به دستور git checkout که وضعیت ساب‌ماژول‌ها را برای شاخه‌ای که به آن سوئیچ می‌کنیم، به درستی تنظیم می‌کند.

$ git --version
git version 2.13.3

$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'

$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...

$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
 2 files changed, 4 insertions(+)
 create mode 160000 CryptoLibrary

$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

استفاده از گزینه‌ی --recurse-submodules در دستور git checkout وقتی که روی چند شاخه‌ی مختلف در پروژه‌ی اصلی کار می‌کنید و هر کدام از این شاخه‌ها ساب‌ماژول شما را روی کامیت‌های مختلفی نشان می‌دهند، بسیار مفید است. در واقع، اگر بین شاخه‌هایی که ساب‌ماژول را روی کامیت‌های متفاوت ثبت کرده‌اند جابه‌جا شوید، پس از اجرای git status، ساب‌ماژول به‌عنوان «تغییر یافته» نمایش داده شده و «کامیت‌های جدید» نشان داده می‌شود. دلیل این موضوع این است که وضعیت ساب‌ماژول به‌طور پیش‌فرض هنگام تغییر شاخه حفظ نمی‌شود.

این موضوع می‌تواند گیج‌کننده باشد، بنابراین همیشه ایده‌ی خوبی است که هنگام کار با پروژه‌ای که ساب‌ماژول دارد، از git checkout --recurse-submodules استفاده کنید. برای نسخه‌های قدیمی‌تر گیت که گزینه‌ی --recurse-submodules را ندارند، پس از چک‌اوت می‌توانید با دستور git submodule update --init --recursive ساب‌ماژول‌ها را به وضعیت درست برگردانید.

خوشبختانه می‌توانید به گیت (نسخه ۲.۱۴ و بالاتر) بگویید که همیشه گزینه‌ی --recurse-submodules را به‌کار ببرد، با تنظیم گزینه‌ی پیکربندی submodule.recurse به این صورت: git config submodule.recurse true. همان‌طور که پیش‌تر گفته شد، این کار باعث می‌شود گیت در تمام دستورات دارای گزینه‌ی --recurse-submodules (به جز git clone) به صورت بازگشتی وارد ساب‌ماژول‌ها شود.

Switching from subdirectories to submodules (جابه‌جایی از زیرشاخه‌ها به ساب‌ماژول‌ها)

مشکل اصلی دیگر که بسیاری با آن مواجه می‌شوند، جابه‌جایی از زیرشاخه‌ها به ساب‌ماژول‌ها است. اگر در پروژه‌تان فایل‌هایی را دنبال می‌کنید و قصد دارید آن‌ها را به یک ساب‌ماژول منتقل کنید، باید مراقب باشید وگرنه گیت به شما خطا می‌دهد. فرض کنید فایل‌هایی در یک زیرشاخه از پروژه دارید و می‌خواهید آن را به ساب‌ماژول تبدیل کنید. اگر زیرشاخه را حذف کرده و سپس دستور submodule add را اجرا کنید، گیت به شما هشدار می‌دهد:

$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index

شما باید دایرکتوری CryptoLibrary را آن استیج کنید سپس میتوانید ساب ماژول اضافه کنید:

$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.

حال فرض کنید این کار را در یک شاخه انجام داده‌اید. اگر بخواهید به شاخه‌ای برگردید که آن فایل‌ها هنوز در درخت اصلی هستند و به صورت ساب‌ماژول نیستند، این خطا را دریافت می‌کنید:

$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
  CryptoLibrary/Makefile
  CryptoLibrary/includes/crypto.h
  ...
Please move or remove them before you can switch branches.
Aborting

می‌توانید با دستور checkout -f به زور تغییر شاخه دهید، اما مراقب باشید که تغییرات ذخیره‌نشده‌ای نداشته باشید چون ممکن است با این دستور بازنویسی شوند.

$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'

بعد از آن، هنگام بازگشت، ممکن است به دلایلی دایرکتوری CryptoLibrary خالی باشد و اجرای git submodule update هم مشکل را حل نکند. ممکن است لازم باشد وارد دایرکتوری ساب‌ماژول شوید و دستور git checkout . را اجرا کنید تا همه فایل‌هایتان بازگردند. این دستور را می‌توانید با استفاده از اسکریپت submodule foreach برای چندین ساب‌ماژول اجرا کنید.

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

با این ابزارها، ساب‌ماژول‌ها می‌توانند روشی نسبتاً ساده و مؤثر برای توسعه همزمان چند پروژه‌ی مرتبط اما مستقل باشند.

scroll-to-top