سامانه کنترل نسخه (Version Control System) یا به اختصار VCS، یکی از مهمترین سیستمهای مورد استفاده در دنیای برنامهنویسان امروز است. با مثالی ساده، یکی از ویژگی های مهم این سیستم را بررسی میکنیم؛ فرض کنید یک فایل متنی را ایجاد و چندین بار آن را ویرایش کردهاید. در ویرایش پنجم به این نتیجه میرسید که ویرایش اول شما بهتر بوده و قصد بازگشت به نسخهای را دارید که در ویرایش اول ایجاد شده است. VCS این امکان را به شما میدهد که بینهایت بار اشتباه کنید و بتوانید اشتباهات خود را جبران کنید. به وسیلهٔ VCS، میتوانید نسخههای متفاوتی از فایلهای پروژه را نگهداری کرد و در صورت وقوع اشتباه، آن را تصحیح کرد. البته این یکی از چندین کاربرد VCS است.
VCS تغییرات و ویرایشهای ما را ذخیرهسازی کرده و به هنگام خطا و در صورت نیاز میتوانند نسخه های قبلی را بازیابی کنند. همچنین VCS با فراهم کردن امکانات مهمی همچون قابلیت پیگیری تغییرات اعمالی و جلوگیری از تداخل آنها با یکدیگر و ساخت شاخههای مختلف از پروژه، فرآیند توسعه پروژهها به صورت گروهی را سادهتر میسازند. اساس کارکرد VCS، بر روی ثبت و ذخیرهسازی تاریخچهٔ تغییرات فایلها است.
برخی از ویژگیهای مهم این سیستمها عبارتند از:
VCS ها به سه دستهٔ محلی، متمرکز و توزیعشده تقسیم میشوند. در ادامه هر کدام از دستهها بررسی خواهدشد.
<figure>
<caption>انواع سامانههای کنترل نسخه</caption>
</figure>
سادهترین کاری که یک کاربر میتواند برای کنترل نسخ پروژه خود انجام دهد، کپی کردن فایلها داخل یک پوشه جدا (و اگر هوشمندانه باشد، با ثبت ساعت و تاریخ آن لحظه به عنوان نام آن پوشه) بعد از هر تغییر مهم است. علیرغم راحتی و سادگی، اینکار شدیدا مستعد بوجود آوردن خطا است؛ زیرا کاربر به سادگی میتواند مبدا و مقصد عملیات کپی را اشتباه کند و همه چیز خراب شود! یا حتی تغییرات به اشتباه در فایلهای قدیمیتری انجام اعمال شود و باعث حذف ناخواسته یک سری تغییرات دیگر شود!
برای حل چالش کنترل نسخه، برنامهنویسان از مدتها پیش سیستمهای مدیریت نسخهای را توسعه دادند که از یک پایگاه داده ساده برای ثبت تغییرات در فایلها بهره میبردند.
<figure>
<caption>شمای سامانه کنترل نسخه محلی</caption>
</figure>
یکی از معروفترین ابزارهای VCS به نام RCS (Revision Control System) است که همچنان بر روی بسیاری از کامپیوترها (عمدتا لینوکسی) یافت میشود. RCS با نگهداری مجموعهای از وصلهها (Patch؛ تغییرات بین دو نسخه متوالی از فایل) در قالب یک فرمت مشخص بر روی دیسک کار میکند. پایگاه داده RCS تنها در اختیار کاربر محلی کامپیوتر قرار دارد؛ بنابراین افراد بیرون از ناحیهٔ محلی، توانایی تعامل با پایگاه داده مربوطه را ندارند.
از نقاط ضعف این مدل از VCS ها میتوان به عدم امکان مدیریت پروژههای گروهی و همچنین نقطه تکی شکست اشاره کرد.
نقطه تکی شکست (Single point of failure) زمانی مطرح میشود که منابع مورد استفاده در یک نقطه فیزیکی (مثلا یک سرور) تجمیع شده و در صورتی بوجود آمدن خرابی در آن نقطه، منابع با خطر خرابی یا از بین رفتن مواجه شوند.
برای رفع نیاز همکاری با سایر اعضاء پروژه، VCS های متمرکز ارائه شدند. این سامانهها از یک سرور که حاوی تمام نسخههای فایلها هستند، استفاده میکند. هر یک از کاربران که بر روی یک پروژهٔ مشترک فعالیت میکنند، میتوانند مستقلا با سرور در ارتباط باشند (به صورت ارتباط Client/Server)، تغییرات جدید را دریافت و یا تغییرات خودشان را در آن ثبت کنند. برای مثال، در تصویر زیر میبینیم که کاربر A و کاربر B به صورت مستقل به سرور VCS مرکزی متصل هستند و عملیات مورد نیاز خود را انجام میدهند. برای سالهای متمادی، نوع متمرکز به عنوان نوع استاندارد VCSها شناخته میشد.
<figure>
<caption>شمای سامانه کنترل نسخه متمرکز</caption>
</figure>
با این حال، VCSهای متمرکز نیز از ایراد مبرا نیستند. واضحترین عیب آنها، بوجود آمدن یک نقطه شکست به واسطه متمرکز بودن معماری آنها است؛ یعنی اگر سرور برای چند ساعت در دسترس نباشد (مثلا با هدف تعمیرات، بروزرسانی یا حتی به دلیل خرابی سرور) هیچ کاربری نمیتواند نسخه جدید از فایلهایش را ذخیره کند. یا حتی بدتر از آن، اگر دیسک سخت سرور خراب شود، تقریبا همه چیز از بین میرود!
در این نوع VCSها، تمام دادهها بر روی تنها یک سرور مرکزی ذخیره میشوند. یعنی اگر اعضاء بخواهند تغییراتی در پروژه ایجاد کنند،برای ثبت تغییرات حتما باید به سرور مرکزی متصل باشند.
به طور خلاصه، ویژگیهای VCS متمرکز عبارتند از:
همچنین، برخی از نمونههای VCS متمرکز نیز عبارتند از:
در یک VCS توزیعشده، کاربران نه تنها آخرین تغییرات اعمالشده بر فایلها را از سرور دریافت میکنند، بلکه یک نسخه کامل از مخزن (=محلی که مجموعه فایلهای حاوی نسخههای مختلف از یک پروژه در آن قرار داد) به همراه سوابق تغییرات پروژه را نیز در سمت خود میسازند. بنابراین، در صورت وقوع خرابی در سرور، هر کدام از مخازن ساخته شده سمت کاربران میتواند به عنوان پشتیبان در نظر گرفته شده و بازیابی اطلاعات در سرور از طریق آن انجام شود (حتی هر کاربر خودش میتواند در نقش سرور برای باقی کاربران عمل کند). به عبارت دیگر، هر کاربری که از VCS توزیعشده استفاده میکند، یک نسخه پشتیبان کامل از تمام دادهها را در اختیار دارد. همچنین در صورتی که سرور اصلی در دسترس نباشد، اعضای گروه میتوانند تغییرات را در سیستم خودشان اعمال کرده و در زمانی که ارتباط دوباره برقرار شد آن را با سرور اصلی هنگامسازی کنند. در تصویر زیر مشاهده میشود که کاربران A و B هر کدام یک نسخه کامل از سرور را در سمت خودشان دارند و در صورت از دست رفتن ارتباط با سرور، میتوانند با یکدیگر نیز ارتباط برقرار کنند.
<figure>
<caption> شمای سامانه کنترل نسخه توزیعشده</caption>
</figure>
به علاوه، بسیاری از VCS های توزیعشده، قابلیت پشتیبانی از چندین مخزن از راه دور را دارند. به این صورت که کاربر میتواند بر روی توسعه یک پروژهٔ واحد با تیمهای مختلفی که آدرس مخزن سرورشان با یکدیگر متفاوت است، همکاری کند.
نمونههای VCS توزیعشده عبارتند از:
در تصویر زیر میتوان تاریخچهٔ ظهور VCS های توزیعشده را مشاهده نمود.
<figure>
<caption>خط زمانی ظهور VCSهای توزیعشده</caption>
</figure>
همانطور که گفته شد، گیت یک سیستم کنترل نسخه است که به صورت توزیعشده سازماندهی میشود. گیت تنها سیستم کنترل نسخه موجود نیست، اما معروفترین آنهاست.
<figure>
<caption>درختوارهٔ تاریخچه توسعه لینوکس</caption>
</figure>
توسعهٔ گیت از زمانی آغاز شد که جامعه توسعهدهندگان لینوکس، استفاده از بیت کیپر (یک سامانه مدیریت کد که قبلاً برای توسعه هستهٔ لینوکس استفاده میشد) را به دلایل مسائل حقوقی کنار گذاشتند. لذا، تصمیم بر آن شد که سامانه مدیریت نسخهای توسعه داده شود که هم جایگزین بیتکیپر شود، هم مشکلات موجود در آن و نیازمندیهای جدید را رفع نماید. در نهایت، اولین نسخه از گیت در دسامبر ۲۰۰۵ به صورت رسمی عرضه شد.
برای مطالعه بیشتر درباره رابطه با ارتباط لینوکس و بیتکیپر میتوانید از اینجا اطلاعات بیشتری کسب کنید.
اهداف مهمی که در توسعه گیت در نظر گرفته شدند عبارتند از:
گیت برای هماهنگ کردن وظایف میان اشخاص مختلفی که روی یک پروژه کار میکنند، بسیار مفید است. میتوانید از گیت برای نوشتن یک مقاله استفاده کنید، یا وقتی که میخواهید تغییراتِ اعمال شده را، در یک فایل هنری یا طراحی، پیگیری کنید. بسیاری از برنامهنویسان به صورت روزانه از گیت استفاده میکنند و تسلط روی آن میتواند تأثیر خوبی روی رزومهی کاریتان داشته باشد.
مهمترین تفاوت بین GIT و سایر VCSها (مانند SVN)، دیدگاه آن نسبت به ذخیرهسازی دادهها است شده است. در اکثر VCSها، اطلاعات به شکل لیستی از تغییرات فایل-محور ذخیره میشود. در واقع، این ابزارها به اطلاعات به چشم مجموعهای از فایلها و تغییراتی نگاه میکنند که در گذر زمان در فایلهای اولیه اتفاق میافتند. به این روش کنترل نسخه اصطلاحا تغییر-محور گفته میشود.
تصویر زیر یک سناریوی ساده از مدل ذخیرهسازی تغییر-محور را نشان میدهد؛ در نسخهٔ شماره ۱، سه فایل A، B و C وجود در پروژه وجود دارند. بعد از مدتی، A و C دچار تغییر شده و و این تغییرات در نسخهٔ شماره ۲ ذخیره میشوند. از آنجایی که B از نسخه قبل تا نسخه جدید تغییری نداشته است، اطلاعات جدیدی هم از آن در نسخه ۲ اضافه نمیشود. به همین منوال، عملیات ثبت نسخ جدید در آینده نیز ادامه پیدا میکند.
<figure>
<caption>ذخیرهسازی اطلاعات به شکل تغییرات نسبت به نسخه پایه هر فایل</caption>
</figure>
در گیت، به اطلاعات به چشم جریانی از لحظهنگارهای (Snapshot) یک فایلسیستم مینیاتوری نگاه میشود. هر زمان که آماده ایجاد نسخه جدید میشوید و حالت فعلی پروژه خود را ذخیره میکنید، GIT در آن لحظه یک تصویر (=لحظهنگار) از تمام فایلهای شما ثبت کرده و اشارهگری به آن را ذخیره میکند. برای کارایی بیشتر، فایلهایی که تغییری نداشتهاند، در لحظهنگار جدید ثبت نمیشوند و تنها ارجاعاتی به آنها (در آخرین نسخه تغییر یافته) نگهداری میشود.
در تصویر زیر، میتوانیم سناریوی مبتنی بر مدل ذخیرهسازی اطلاعات در گیت را مشاهده کنیم؛ در نسخه ۱، تمام فایلهای پروژه ثبت میشوند. در نسخه ۲، تنها فایلهای A و C تغییر یافته است؛ لذا در لحظهنگار مربوط به نسخه ۲، کل فایلهای A و C و لینک به فایل B در نسخه ۱، در نظر گرفته خواهند شد. در نسخه ۳، تنها فایل C تغییر یافته است؛ لذا کل فایل C به همراه لینکهای فایل B در نسخه ۱ و فایل B در نسخه ۲ در لحظهنگار مربوطه ذخیره خواهند شد.
<figure>
<caption>ذخیرهسازی اطلاعات به شکل جریانی از لحظهنگارها در طول انجام پروژه</caption>
</figure>
ابزار گیت (تقریبا) در هر سیستم عاملی قابل نصب است. در ادامه، راهنمای نصب مربوط به دو سیستم عامل ویندوز و توزیع لینوکسی Ubuntu آورده شده است.
برای نصب گیت در سیستم عامل ویندوز، از طریق سایت رسمی [گیت](https://git-scm.com/download/win) نسخه مربوط به سیستم عامل خود را دانلود و به شکل زیر آن را نصب کنید.
<figure>
<caption>مراحل نصب گیت در سیستم عامل ویندوز</caption>
</figure>
پس از اتمام مراحل نصب، Git Bash نیز به صورت خودکار نصب میشود. با استفاده از Git Bash میتوان دستورات مهم لینوکسی را در محیط سیستم عامل ویندوز اجرا کرد (تصویر زیر):
<figure>
<caption> بررسی نسخه گیت نصبشده</caption>
</figure>
در سیستم عامل لینوکس و در نسخه ی Ubuntu با استفاده از دستور زیر میتوان گیت را نصب کرد :
sudo apt-get update sudo apt-get install git
در سایر توزیعهای لینوکسی نیز میتوان اقدام به نصب گیت کرد. برای مثال، در نسخههای redhat میتوان با اجرای دستور زیر، گیت را نصب کرد:
sudo yum install git
قبل از آغاز کار با گیت و بررسی امکانات آن به صورت عملی، میبایست با اصطلاحات حیاتی و مهم آن آشنایی پیدا کنیم. تعدادی از مهمترین این اصطلاحات در ادامه آورده شده است.
مخزن در بردارندهٔ سابقهٔ کلی پروژهٔ شماست. تمام تغییرات شما بر روی فایلهای پروژه، در مخزن قابل مشاهده است.
<figure>
<caption> مخزن</caption>
</figure>
دو نوع مخزن داریم:
در گیت تغییرات نسبت به نسخههای قبلی ذخیره میشود و هر تغییر تحت عنوان یک اصلاح یا Revision ذخیره میشود. Revision ها یک نویسندهٔ مشخص دارند و علاوه بر این حاوی پیامی است که توضیحات مربوط به تغییرات این نسخه است که توسط نویسنده بیان شده است.
پس از چندین اصلاح ممکن است به یک اصلاح خاص برسیم (مثلا یک نسخه از پروژه تولید شود که بخواهیم آن را به شکلی متمایز کنیم) که میتوان آن را به وسیله یک برچسب علامتگذاری کرد.
برای فهم بهتر مفهوم شاخه، سناریوی زیر را در نظر بگیرید؛ فرض کنید شما و دوست برنامهنویستان قصد دارید روی یک پروژه کار کنید و قسمتهای مختلف را بین خود تقسیم میکنید. برای این که شما دو نفر به صورت موازی بتوانید وظایف مشخص خود را پیش ببرید هر کدام یک شاخه ایجاد کرده و تغییرات را روی شاخهٔ مربوط به خودتان انجام میدهید. استفاده از شاخه در گیت خصوصا در پروژههایی که به صورت گروهی انجام میگیرد، بسیار ضروریست.
برای مثال در تصویر پایین، دو شاخه وجود دارد؛ یکی شاخهٔ اصلی و دیگری شاخهای که از اصلاح v1.0
از شاخهٔ اصلی انشعاب گرفته شده است.
فرض کنید در یک پروژه (که تحت گیت مدیریت میشود) وظایفی به شما در مورد توسعه پروژه محول شده است. ابتدا باید یک کپی از آخرین نسخهٔ پروژه را از سرور دریافت کنید (در این سناریو فرض کردهایم که آخرین نسخه از شاخهٔ master
دریافت شده است).
<figure>
<caption>شمای شاخه اصلی</caption>
</figure>
به عمل دریافت آخرین نسخه پروژه از سرور، Checkout گفته میشود که در نتیجه آن، شما یک نسخه (Working Copy) از پروژه را در پوشهٔ مد نظر خود (Working tree یا Working Directory) ایجاد میکنید:
<figure>
<caption>دریافت آخرین نسخه پروژه</caption>
</figure>
بعد از دریافت پروژه، میتوانیم شروع به توسعه و اعمال تغییرات در پروژه کنیم:
<figure>
<caption>اعمال تغییرات بر روی فایلهای پروژه</caption>
</figure>
پس از اعمال تغییرات، میبایست آنها را به شکل یک اصلاح جدید در مخزن ذخیره کنیم. به این کار ثبت یا اصطلاحا کامیت (Commit) گفته میشود:
<figure>
<caption>اعمال تغییرات روی مخزن و ثبت آنها</caption>
</figure>
از دیدگاه گیت، فایلهای پروژه (موجود در Working Directory) میتوانند یکی از ۴ حالات زیر را داشته باشند:
- Untracked: داخل Working Directory هستند ولی تغییراتشان توسط گیت رصد نمیشوند. - Unmodified: توسط گیت رصد میشوند و از کامیت قبلی تاکنون تغییری نداشتهاند. - Modified: توسط گیت رصد میشوند، ولی برای ثبت در کامیت بعدی انتخاب نشدهاند. - Staged: آمادهٔ ثبت در کامیت بعدی هستند.
<figure>
<caption>حالات مربوط به فایلهای پروژه</caption>
</figure>
به ناحیهای که فایلهای دارای حالت Staged در آن قرار دارند، اصطلاحا Staging Area یا Index Area نیز گفته میشود.
با استفاده از دستور زیر میتوان یک مخزن جدید ساخت:
git init [repo-name]
اگر با ترمینال (یا command line ویندوز) کار میکنید، ابتدا باید اطمینان حاصل کنید که در مسیر درستی برای ساخت مخزن قرار دارید. برای اینکار از دستور pwd
(در ترمینال لینوکس) یا دستور dir
در command line ویندوز استفاده میکنیم:
cw@testing:~$ pwd /home/cw/
دستور pwd
(که یک دستور لینوکسی است) به ما اعلام میکند که مسیر فعلی چیست (با این دستور در مبحث لینوکس بیشتر آشنا میشویم). خروجی دستور بالا در یک ماشین لینوکسی معنا پیدا میکند. در یک ماشین ویندوزی (و با استفاده از Git Bash)، خروجی به این شکل خواهد بود:
cw@testing: MINGW64 ~ $ pwd /c/Users/cw
در ادامه مثال، یک مخزن گیت درون پوشهای با نام project
میسازیم:
cw$testing:~$ git init project Initialized empty Git repository in /home/cw/project/.git/
سپس بررسی میکنیم که در پوشه project
که ساخته شده چه چیزهایی وجود دارد:
cw@testing:~$ ls -a project . .. .git
سه پوشه در پوشه project
وجود دارد؛ .
، ..
و .git
. با دو پوشهٔ اول در مبحث لینوکس آشنا خواهید شد. همینقدر بدانید که پوشهٔ .git
محلی است که مخزن پروژه project
در آن قرار دارد.
میتوانید محتویات پوشهٔ مخزن را نیز ببینید (در این درس از بررسی آنها چشمپوشی میکنیم):
cw@testing:~$ ls -a project/.git branches config description HEAD hooks info objects refs
در صورتی که ورودی دستور git init
خالی باشد، مخزن در پوشه ی فعلی (که در آن قرار داریم) ذخیره میشود. به عبارتی، در مثال قبل میتوانستیم ابتدا پوشه project
را ایجاد کنیم و سپس یک مخزن درون آن ایجاد کنیم.
پوشهٔ .git
حاوی کلیهٔ اطلاعات مهم پروژه توسط گیت است و نباید هیچ وقت آن را حذف کنید . در صورت حذف آن، کلیهٔ سوابق پروژه و تغییرات آن از بین میرود.
پس از نصب گیت و قبل از هر کاری، میبایست اطلاعات خود را که شامل یک ایمیل و یک نام کاربری است در تنظیمات مخزن ثبت کنید:
git config --global user.email "your-email" git config --global user.name "your-username"
با اینکار، شما به صورت سراسری ایمیل و نام کاربریتان را برای هر پروژهای که تحت گیت باشد، تنظیم میکنید. اگر مایل به تنظیم سراسری این موارد نیستید یا میخواهید این تنظیمات را برای هر پروژه به صورت مجزا انتخاب کنید، دستور بالا را بدون سوییچ –global
و در پوشهٔ پروژه مورد نظر، اجرا کنید. برای مثال، دستورات بالا را به این شکل وارد میکنیم:
cw@testing:~/project$ git config user.email "testing@cw.com" cw@testing:~/project$ git config user.name "tester"
ایمیل و نام کاربریای که در تنظیمات گیت وارد میکنید، میتواند هر چیزی باشد (حتی اطلاعات جعلی). با این حال، با توجه به اینکه این اطلاعات توسط سایر اعضاء پروژه قابل مشاهده است، توصیه میشود انتخابتان به گونهای باشد که برای سایر کاربران پروژه قابل شناسایی باشید.
پس از مقداردهی اولیهٔ مخزن، در پوشهٔ پروژه تغییرات خود را اعمال کنید. برای افزودن فایل(ها) به Staging Area از دستور زیر استفاده میکنیم:
git add file [file1] [file2] ...
برای مثال، فایل hello
را میسازیم و به پروژه اضافه میکنیم:
cw@testing:~/project$ echo 'Hello World!' > hello cw@testing:~/project$ git add hello
در خط اول، دستور پایین فایل hello
شامل محتوای متنی Hello World
ایجاد شده است (با دستور echo در مبحث آموزش لینوکس بیشتر آشنا میشوید). برای ثبت حالت فعلی پروژه (ایجاد لحظهنگار جدید)، از دستور زیر استفاده میکنیم:
git commit -m [message]
در ادامه مثال قبلی، ثبت کامیت را به صورت زیر انجام میدهیم:
cw@testing:~/project$ git commit -m "added file 'hello'" [master (root-commit) 28de2e5] added file 'hello' 1 file changed, 1 insertion(+) create mode 100644 hello
میتوان به جای اضافه کردن تک به تک فایلها، از کاراکتر نقطه .
استفاده کنیم. با اینکار، تمامی فایلهایی که یا در مسیر فعلی قرار دارند، یا محل قرارگیری آنها جزو زیرمجموعههای مسیر فعلی است، به Staging Area اضافه میشوند:
git add .
برای هر کامیت باید یک پیام وارد شود. این پیام شامل یک عبارت توضیحی در مورد تغییراتی است که در فایل ها ایجاد شده است.
همواره سعی کنید تا پیام کامیت را به گونهای انتخاب کنید که ضمن کوتاه و مختصر بودن، معرف تغییراتی باشد که این پیام را روی آن قرار می دهید.
با استفاده از دستور زیر میتوان اطلاعات کلی کامیتهایی که اخیرا در پروژه اعمال شده است را مشاهده کنیم:
git log
اجرای این دستور منجر به این خروجی خواهد شد:
cw@testing:~/project$ git log commit 28de2e5cf7fc12a499484165b71fbbe8940799bc (HEAD -> master) Author: tester <tester@cw.com> Date: Mon Sep 27 18:44:01 2021 +0330 added file ''hello''
هر کامیت، مقدار منحصر به فردی دارد که با عنوان
Commit Hash
یا هشِ کامیت شناخته میشود. در خروجی دستور
git log
مقدار نوشته شده روبروی عبارت
commit
همان هش کامیت است. هر زمان که استفاده از هش کامیت نیاز شد، میتوانید تنها ۷ کاراکتر اول آن را بنویسید.
با استفاده از دستور زیر میتوان اطلاعات زیادی را در مورد مخزن دریافت کرد.
git status
اطلاعات قابل نمایش توسط این دستور عبارتند از:
- شاخه کنونی - وضعیت تازگی اطلاعات نسبت به سرور - فایلهای تغییر یافته (Modified) ولی نشان نشده (Unstaged) - فایلهای تغییر یافته و آمادهٔ ثبت (Staged) - فایلهای جدیدا اضافه شده و آماده ثبت (Staged) - فایلهای موجود ولی خارج از رصد (Untracked)
پس از اجرای دستور بالا خروجی زیر ظاهر میشود:
cw@testing:~/project$ git status On branch master nothing to commit, working tree clean
طبیعی است که چیزی برای کامیت شدن وجود نداشته باشد (چون تغییر جدیدی رخ نداده است). حال یک فایل جدید ایجاد میکنیم تا وضعیت را مجددا بسنجیم:
cw@testing:~/project$ echo "Hello Universe!" > universe cw@testing:~/proejct$ git status On branch master Untracked files: (use "git add <file>..." to include in what will be commited) universe nothing added to commit but untracked files present (use "git add" to track)
با دستورات بالا، فایل universe
را ایجاد و دستور git status
را مجددا اجرا کردیم. همانطور که مشاهده میشود، فایل تازه ایجاد شدهٔ universe
توسط گیت شناسایی شده است، ولی هنوز تغییرات آن رصد قابل رصد نیست. برای بردن فایل به حالت Staged از دستور git add
استفاده کرده و سپس وضعیت را دوباره میسنجیم:
cw@testing:~/project$ git add universe cw@testing:~/project$ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: universe
مشاهده میشود که پس از اضافه شدن، وضعیت فایل universe
تغییر کرد و به حالت Staged تغییر یافت. حال محتویات فایل hello
(که قبلا ساخته شده است) را تغییر میدهیم و وضعیت را دوباره بررسی میکنیم:
cw@testing:~/project$ echo "Hello again" >> hello cw@testing:~/project$ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: universe Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: hello
همانطور که مشاهده میشود فایل hello
آماده ی کامیت شدن نیست (در حالت Modified قرار دارد). برای تغییر حالت فایلهای Modified به Staged، میبایست از سوییچ -a
در دستور کامیت استفاده کرد:
cw@testing:~/project$ git commit -am "added file 'universe'" [master 524757a] added file 'universe' 2 files changed, 2 insertions(+) create mode 100644 universe cw@testing:~/project$ git status On branch master nothing to commit, working tree clean cw@testing:~/project$ git log commit 523757a046e5f44aeed6131e516ffb37e694dcfa Author: tester <tester@cw.com> Date: Mon Sep 27 20:03:41 2021 +0330 added file 'universe' commit 28de2e5cf7fc12a499484165b71fbbe8940799bc Author: tester <tester@cw.com> Date: Mon Sep 27 18:44:01 2021 +0330 added file 'hello'
وقتی از دو سوییچ به صورت همزمان در دستور استفاده میکنیم (اینجا یعنی سوییچهای
-a
و
-m
)
میتوان آنها را چسبیده بهم نوشت. در این صورت، تنها یک کاراکتر
-
نوشته میشود.
در صورتی که از سوییچ -a
استفاده نکنیم، فایل hello
همچنان در حالت Modified خواهد ماند:
cw@testing:~/project$ git commit -m "added file 'universe'" [master 771d323] added file 'universe' 1 file changed, 1 inserstion(+) create mode 100644 universe cw@testing:~/project$ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: hello no changes added to commit (use "git add" and/or "git commit -a")
یک راه دیگر آن است که پس از تغییر فایل hello
مجددا آن را با دستور git add
به حالت Staged ببریم:
git add hello
هنگامی که به تازگی یک مخزن ایجاد میکنیم، به صورت پیشفرض یک شاخهٔ master
ساخته میشود که نقش شاخه اصلی مخزن مذکور را بازی میکند و هر کامیتی که انجام دهیم روی این شاخه انجام میشود. با استفاده از این مفهوم، میتوان به تعداد توسعهدهندگانی که در تیم حضور دارند شاخههای اختصاصی ساخته و در آن واحد تمامی اعضای تیم بتوانند اقدام به پیشبرد پروژه کنند و هر زمان که لازم شد، شاخهها را با یکدیگر ادغام کنند.
در توسعه ی پروژه، فقط باید کدهایی روی شاخهٔ اصلی قرار گیرد که کاملا پایدار و بدون مشکل هستند. با این تفاسیر منطقی به نظر میرسد که شاخهٔ master
به عنوان شاخهٔ آزمون و خطا استفاده نشود و هنگامی که یکی از اعضای تیم قصد توسعهٔ نرم افزار را داشت شاخهٔ جدایی را ایجاد کرده و روی آن ادامه دهد تا هنگامی که به پایداری اشاره شده برسد.
<figure>
<caption>شاخه اصلی پروژه به همراه دو شاخه فرعی</caption>
</figure>
همانطور که در تصویر فوق ملاحظه میشود، خط افقی که در سراسر نمودار مشاهده میشود به عنوان شاخه اصلی یا master
است. فرض میکنیم توسط توسعهدهندهٔ اصلی پروژه، کامیتهایی تحت عناوین C1
و C2
انجام شده است. سپس یک شاخه از شاخهٔ اصلی جدا شده و نامی دلخواه همچون Branch1
برای آن در نظر گرفتهایم (که فرضاً توسط یکی از اعضای تیم توسعه داده میشود) به طوری که وی سه کامیت داخل این شاخه انجام داده است. به صورت موازی، برای یکی دیگر از اعضای تیم شاخهای به نام Branch2
ساخته شده و او نیز یک کامیت در این شاخه انجام داده است. در نهایت، دولوپر اصلی این پروژه کلیهٔ تغییرات اِعمال شده در دو شاخهٔ فوق را با شاخهٔ اصلی ادغام کرده و مجموع این تغییرات را تحت عنوان C7
در شاخه اصلی کامیت کرده است.
در مثال خودمان، برای آنکه لیستی از شاخههای پروژه را مشاهده کنیم دستور زیر را اجرا میکنیم:
cw@testing:~/project$ git branch * master
این دستور لیستی از کلیه ی شاخهها را نمایش میدهد و همانطور که گفته شد، شاخهٔ master
به صورت شاخهٔ انتخابی پیشفرض نمایش داده شده است. علامت *
تایید میکند که در حال حاضر، روی این شاخه هستیم.
فرض کنید که میخواهیم شاخهٔ دیگری از شاخهٔ اصلی انشعاب بگیریم تا وظایف خود را روی آن شاخه ادامه دهیم. به شکل زیر عمل کرده و شاخه ی Branch1
را ایجاد میکنیم:
cw@testing:~/project$ git branch Branch1
پس از ایجاد شاخهٔ مورد نظر، یک بار دیگر لیست شاخهها را نمایش میدهیم:
cw@testing:~/project$ git branch * master Branch1
برای رفتن به شاخهای غیر از شاخه فعلی از دستور زیر استفاده میکنیم:
git checkout <branch-name>
در مثال قبل، برای رفتن به شاخه Branch1
به این صورت عمل میکنیم:
cw@testing:~/project$ git checkout Branch1 Switched to branch 'Branch1'
با بررسی مجدد لیست شاخهها و دیدن علامت *
روبروی نام Branch1
، متوجه میشویم که جابجایی تایید شده است:
cw@testing:~/project$ git branch master * Branch1
از دستور git checkout
همچنین برای ایجاد یک شاخه و پریدن روی آن در قالب یک دستور واحد نیز استفاده میشود:
git checkout -b <branch-name>
در واقع دستور بالا، خلاصه دو دستور پایین است:
git branch <branch-name> git checkout <branch-name>
برای آنکه در عمل هم متوجه این موضوع شویم، در ادامه مثال قبل یک فایل متنی ایجاد میکنیم؛ سپس آن را کامیت میکنیم تا ببینیم تغییرات در کدام شاخه اعمال میشود:
cw@testing:~/project$ git add new.txt cw@testing:~/project$ git commit -m "new file added" [branch1 b276cac] new file added 1 file changed, 3 insertions(+) create mode 100644 new.txt
سپس گزارش تغییرات شاخه را با دستور git log
چک میکنیم:
cw@testing:~/project$ git log --oneline b276cac (HEAD -> Branch1) new file added ...
همانطور که در پیامی که برای کامیت آمده است میبینیم، تغییرات روی شاخه Branch1
اعمال شده است (و نه شاخهٔ master
). سوییچ –oneline
برای این استفاده شده است که تنها موارد کلی مثل پیام کامیت و ۷ کاراکتر اول هش کامیت نمایش داده شود و از نمایش جزئیات غیر ضروری دیگر خودداری شود.
با جابجایی میان شاخهها، به صورت پیشفرض به آخرین کامیت شاخه مقصد میرویم. تصویر زیر بیانگر این موضوع است:
از آنجایی که جابجایی بین کامیتهای دو شاخه انجام میشود، بر اساس اختلافات میان این دو کامیت، ممکن است پس از اجرای دستور فایلها و محتوای آنها تغییراتی داشته باشد. با برگشت به شاخه (و کامیت) قبلی، فایلها و محتوایشان به حالت سابق باز خواهند گشت. برای فهم بهتر این موضوع، به شاخهٔ master
رجوع کنید تا وضعیت فایل new.txt
در این شاخه را بررسی کنید:
cw@testing:~/project$ git checkout master Switched to branch 'master'
اکنون اگر به محتویات داخل پوشه نگاهی بیندازید، خواهید دید که فایل new.txt
حذف شده است.
همانطور که بالاتر گفته شد، با جابجایی میان شاخهها، به صورت پیشفرض به آخرین کامیت شاخهٔ مقصد میرویم. میتوان در هنگام پرش به شاخه مقصد، با استفاده هش کامیت، مقصد پرش را دقیقتر مشخص کرد:
git checkout [branch-name] [commit-hash]
اگر هدف جابجایی میان کامیتهای شاخهٔ فعلی باشد، میتوان از نوشتن نام شاخه صرف نظر کرد و دستور را به این صورت نوشت:
git checkout [commit-hash]
گاهی اوقات لازم است تا به چند کامیت عقبتر بازگردیم. در این مواقع، لازم نیست هش کامیت را بدانیم؛ برای رفتن به n کامیت عقبتر از کامیت نهایی (که n یک عدد صحیح بزرگتر از صفر است) میتوانیم بدین شکل از دستور استفاده کنیم (اگر شاخه فعلی مد نظر است، نام شاخه را خالی میگذاریم):
git checkout [branch-name] HEAD[~n]
اگر در شاخه فعلی تغییراتی انجام داده باشید، تا زمانی که تغییرات را کامیت یا حذف نکردهاید، اجازه پرش به شاخههای دیگر به شما داده نخواهد شد. با این حال، ممکن است شما مایل به انجام هیچکدام باشید. یک راه ساده برای حل این موضوع، ذخیرهسازی موقت تغییرات در حافظهای به نام Stash است. برای اینکار از دستور زیر استفاده میکنیم:
git stash
با استفاده از این دستور، تغییرات کامیت نشده روی فایلها تحت رصد گیت در محلی ذخیره شده و از فایلها حذف میشوند تا حالت فایلها به آخرین وضعیت کامیت شده تغییر یابد. سپس، به راحتی میتوانید به شاخههای دیگر پرش کنید. برای بازگرداندن تغییراتی که ذخیره شده بودند، از دستور زیر استفاده میشود:
git stash pop
اشاره به git stash
در اینجا صرفا به این خاطر بود که محدودیتهای احتمالی از جمله پرش به شاخهٔ دیگر با وجود تغییرات کامیت نشده را به شکلی ساده برطرف نماییم.
در عمل، این دستور ریزهکاریهای بسیاری دارد که از ذکر آنها در این آموزش صرف نظر میکنیم و مخاطبان علاقهمند را برای کسب اطلاعات بیشتر درباره آن به منابع دیگر (مانند
اینجا)
ارجاع میدهیم.
با استفاده از دستور git merge
میتوان دو شاخه ی موجود را با هم ادغام (Merge) کرد. برای مثال فرض کنید که حسین ابتدا یک شاخه برای خود از شاخهٔ اصلی ایجاد کرده و آن را توسعه داده است و حالا قصد دارد تا تمامی تغییراتی که روی شاخهٔ خود داده است را روی شاخهٔ اصلی نیز اعمال کند.
در تصویر بالا، فرض کنید شاخهٔ زرد توسط حسین از شاخهٔ اصلی جدا شده است و توسعه داده شده و حال قصد دارد این دو شاخه را با هم ادغام کند تا به شکل سمت راست تبدیل شود. برای این کار ابتدا روی شاخهٔ master
میرویم:
git checkout master
سپس با استفاده از دستور زیر شاخهٔ feature
را با شاخهای که در آن قرار داریم (یعنی master
) ادغام میکنیم:
git merge feature
همچنین میتوان چند شاخه را همزمان در شاخه فعلی ادغام کرد. برای مثال، با استفاده از دستور زیر، شاخههای feature-1
و feature-2
در شاخه فعلی ادغام میشوند:
git merge feature-1 feature-2
مبحث ادغام شاخهها یکی از مباحث نسبتا پیچیده در گیت محسوب میشود. در این آموزش سعی شد تا صرفا به معرفی این موضوع پرداخته شود؛ با این حال، اگر علاقهمند به مطالعه بیشتر در این مورد بودید، میتوانید اینجا را مطالعه کنید.
با استفاده از سوییچ -d
میتوان یک شاخه را حذف کرد. در ادامه مثال قبل، سعی میکنیم تا شاخهای که ایجاد کردهایم، حذف کنیم:
cw@testing:~/project$ git branch -d Branch1 error: The branch 'Branch1' is not fully merged. If you are sure you want to delete it, run 'git branch -D Branch1'.
همانطور که مشاهده میکنید شاخهٔ Branch1
حذف نشد و گیت هشدار میدهد که تغییراتی که در آن شاخه اعمال شده است هنوز با شاخه ی master
ادغام نشده است.
اگر از آپشن
-D
(به صورت حروف بزرگ) در دستور استفاده کنیم، بدون توجه به ادغام این دو شاخه آن را حذف میکند.
بعد از رفع موضوع (و حذف شاخه Branch1
)، در لیست شاخههای موجود، اثری از این شاخه نخواهد بود:
cw@testing:~/project$ git branch * master
پیش از این اشاره کردیم که دو نوع مخزن داریم؛ محلی و از راه دور (ریموت). [گیتهاب](https://github.com/) (Github) یک مخزن تحت وب است که به شما اجازه میدهد تا پروژههای خود را به صورت ریموت در اختیار دیگران قرار دهید. در ادامهٔ این موضوع، فرض کنید که دو نفر بر روی یک پروژهٔ مشترک کار میکنند که یکی در یک شهر و دیگری در شهری دیگر زندگی میکند. گیتهاب این امکان را فراهم میکند که این دو نفر، مخازن خود را با یکدیگر اشتراک بگذارند.
برای این که یک مخزن ریموت در گیتهاب درست کنید باید ابتدا در این وبسایت ثبت نام کنید و سپس با استفاده دکمهٔ سمت راست و کنار تصویر پروفایل new repository
را کلیک کنید.
<figure>
<caption> منوی اعمال مرتبط با مخزن در گیتهاب</caption>
</figure>
پس از کلیک بر روی این دکمه، صفحهٔ زیر باز میشود که اطلاعات مربوط به مخزن یعنی نام پروژه و دیگر ویژگیها از جمله عمومی و یا خصوصی بودن آن را باید مشخص کنید و سپس مخزن را ایجاد کنید.
<figure>
<caption>ساخت مخزن جدید در گیتهاب</caption>
</figure>
پس از ایجاد مخزن برای این که فایل های خود را روی مخزن قرار دهیم تا دیگران بتوانند به آن دسترسی داشته باشند، باید ابتدا آدرس مخزن ریموت خود را به مخزن محلی خود اضافه کنیم تا مخزن محلی به مخزن ریموت متصل شود. برای این کار از دستور زیر استفاده میکنیم:
git remote add origin git@github.com:ROF13ThFloor/TestRepo.git
پس از اضافه شدن مخزن ریموت گیتهاب، باید کدهای کامیت شدهٔ خود را به گیتهاب اضافه کنیم. برای این کار از دستور زیر استفاده میکنیم:
git push -u origin main
همانطور که گفته شد Staging Area محدودهای است که فایلهای موجود در این ناحیه تحت نظاره گیت قرار گرفتهاند و گیت تغییرات آنها را بررسی میکند. همچنین گفته شد که با استفاده از دستور git add
میتوان یک فایل را به Staging Area اضافه کرد. پس از آن و با استفاده از دستور git commit
و تعیین پیام آن، تغییرات در محیطی تحت عنوان Local Git Repository یا «مخزن محلی گیت» ذخیره میشود. در این قسمت تمامی تغییرات بر روی نسخه ی محلی (=ماشین شما) ذخیره شده است و نسخهی ریموت تغییری نکردهاست. همچنین در قسمت قبل گفتیم که میتوانیم با استفاده از دستور push تغییرات نسخهی محلی را روی نسخهی ریموت هم اعمال کنیم.
برای درک بهتر روند اضافهشدن به Staging Area تا اضافه شدن به مخزن ریموت، تصویر زیر را مشاهده کنید.
<figure>
<caption>مراحل ثبت تغییرات و ارسال آن به مخزن از راه دور</caption>
</figure>
فرض کنید علی و محمد برنامهنویس و همکار هستند و دو نسخهٔ یکسان از یک پروژه را در کامپیوتر خود در اختیار دارند. علی مدتی روی قسمتی از پروژه کار میکند و به محمد میگوید که به زودی تغییراتش را در گیتهاب قرار میدهد تا محمد بتواند با دریافت آن، ادامه توسعه را انجام دهد. پس از اعمال تغییرات، علی مخزن پروژه روی گیتهاب را با دستور push
بروزرسانی کرده و محمد را باخبر میکند. محمد برای دریافت تغییرات از دستور زیر استفاده میکند:
git fetch
پس از اجرای دستور بالا در کامپیوتر محمد، میتوان تغییراتی که علی داده است را مشاهده کرد، لازم به ذکر است که با اجرای دستور بالا کدی که روی کامپیوتر محمد است تغییری نمیکند و ما فقط میتوانیم تغییراتی که علی داده است را مشاهده کنیم (تغییراتی که علی داده است را دریافت میکند ولی آن را در Working tree اعمال نمیکند). برای آنکه این تغییرات را روی Working tree محمد هم داشته باشیم (کد محمد دچار تغییر شود) از دستور زیر استفاده میکنیم:
git pull origin master
در این دستور گفته میشود که تمامی تغییراتی که روی شاخهٔ master
آدرس ریموت یعنی origin
(که در قسمت قبل آن را اضافه کردیم) داشتیم را روی شاخهٔ master
مخزن محلی نیز اعمال شود.
در واقع دستور pull
علاوه بر این که تغییرات را دریافت میکند (fetch
)، آنها را در Working tree اعمال نیز میکند. اگر بخواهیم باقی شاخههای موجود نیز به روزرسانی شوند از دستور زیر استفاده میکنیم:
git pull
بدین ترتیب محمد تمام تغییراتی که علی داده بود را دریافت میکند و میتواند روی آن ادامه دهد. این تغییرات شامل اضافهشدن شاخهها نیز میباشد.
فرض کنید پروژهای را در گیتهاب مشاهده کردهاید که قصد دریافت آن را دارید (یعنی این پروژه اکنون روی کامپیوتر شما نیست). برای دریافت پروژه، به صفحهٔ مرتبط با آن در گیتهاب مراجعه کنید و آدرس مخزن را (همانند تصویر زیر) دریافت کنید.
همانطور که در تصویر میبینید روی دکمهٔ سبز رنگ Code
کلیک کنید و آدرس موجود در بخش Https
را کپی کنید. سپس در ترمینال با استفاده از دستور زیر پروژه را دریافت کنید (لینک دریافت مخزن برای هر پروژه، منحصر به فرد است).
git clone <repo-link>
پس از اجرای دستور بالا، گیت از شما نام کاربری و رمز عبور گیتهاب را درخواست میکند که پس از وارد کردن آنها پروژهٔ مورد نظر در پوشهٔ فعلی بارگیری میشود.
همانطور که در مثال موجود در تصویر بالا مشاهده میشود پروژهٔ CreateRaid05Script
در پوشهٔ فعلی دریافت شد.
این دستور، یک دستور چندمنظوره برای لغو تمامی تغییراتی است که اعمال کردهایم. فرض کنید که در بازهای شما تغییراتی را در برنامهٔ خود اعمال کردهاید که اشتباه بوده است و الان متوجه اشتباه بودن آن شدهاید. اگر قصد دارید تغییراتی که اعمال کردهاید را پاک کنید و به آخرین کامیتی که که انجام دادهاید بازگردید، از دستور زیر استفاده کنید.
git reset [commit-hash]
دستور git reset
میتواند سه نوع سوییچ به عنوان ورودی دریافت کند:
این سوییچ باعث میشود که تغییراتی که Staged هستند و یا هنوز استیجنشده باقیماندند لغو میشوند.
git reset --hard HEAD
به جای HEAD میتوان از هر هش کامیت معتبری استفاده کرد. برای مثال، با اجرای دستور زیر تمامی کامیتهای بعد از هش کامیت وارد شده از دست خواهند رفت:
git reset --hard [commit-hash]
علاوه بر این تمامی فایلهایی که Staged هستند و یا هنوز Staged نشدهاند نیز حذف میشوند و تاریخچهٔ کامیتها به کامیت مورد نظر تغییر میکند.
این سوییچ تمام فایلها را در حالت فعلی نگهداری میکند ولی آنها را از حالت Staged خارج میکند.
git reset --mixed [commit-hash]
برای درک بهتر این قسمت فرض کنید شاخهٔ master
شامل سه کامیت A,B,C باشد که آخرین کامیت C میباشد. در صورتی که از دستور بالا استفاده کنیم تمامی تغییراتی که دادهایم و آنها را پیش از این Stage کردهایم از حالت Staged خارج میشوند. در این حالت همچنان تغییرات قابل مشاهده است اما فایلهای آنها در حالت Staged نیستند.
این سوییچ همچنان تغییرات را نگهداری میکند و حتی فایلهایی که Stage شدهاند را هم در حالت Stage نگهداری میکند. برای درک بهتر این سوییچ فرض کنید سه کامیت A-B-C وجود دارد و ما از دستور زیر استفاده میکنیم .
git reset --soft b
پس از اجرای دستور بالا HEAD
(اشارهگر به آخرین کامیت) به کامیت b
اشاره میکند و علاوه بر این تمامی تغییراتی که در کامیت C
بودهاست به صورت Stage
شده باقی خواهدماند. (در حالت mixed
این اتفاق رخ نمیداد و کلیهی فایلهایی که در کامیت C
هستند از حالت Staged خارج میشوند)
قالب کلی دستور reset
با سوییچ –soft
به شکل زیر است:
git reset --soft [commit-hash]
تصویر زیر میتواند به شما برای درک بهتر تفاوت این سه سوییچ کمک کند:
برای حذف یک فایل از پوشهٔ پروژه از دستور زیر استفاده میکنیم:
git rm [file-name]
دستور بالا مانند این است که شما یک فایل را از Working tree خود پاک کردهاید. گاهی اوقات ما نیاز داریم تا فایلها را صرفا از حالت Staged به حالت Modified تغییر دهیم (یعنی برعکس کاری که دستور git add
انجام میدهد)؛ برای مثال، مانند شرایطی که فایلی حاوی اطلاعات خصوصیتان را به اشتباه آماده کامیت کردهاید. برای انجام این عمل از سوییچ –cached
استفاده میکنیم:
git rm [file-name] --cached
در این حالت، فایل از Working tree شما پاک نمیشود و صرفا از حالت Staged خارج میشود (با بررسی خروجی دستور git status
میتوانید صحت این موضوع را بسنجید).
فرض کنید در پوشهی کاری خود قصد دارید نام یک فایل را تغییر دهید با استفاده از دستور زیر میتوان نام فایل مورد نظر را تغییر داد:
git mv [old-name] [new-name]