پرش به محتویات

Version control system: Git

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

مزایا

  • تاریخچهٔ کامل تغییرات و امکان بازگردانی (undo).
  • همکاری هم‌زمان چند نفر روی یک پروژه و مدیریت ساده تداخل.
  • امکان بررسی اینکه چه کسی چه تغییری داده و چرا (log).
  • شاخه‌بندی و آزمایش ویژگی‌ها بدون تاثیر روی شاخهٔ اصلی (branching).

Git

مفاهیم کلیدی

Repository یا repo

  • تمام تاریخچه و شاخه و آنچه مربوط به یک پروژه است.

Working directory / Working tree

  • شامل فایل‌هایی که شما آن‌ها را ویرایش می‌کنید یا در حال ویرایش آن هستید.

Index / Staging area

  • جایی بین تغییر روی فایل و ایجاد یک commit ،شامل فایل‌هایی که برای commit بعدی انتخاب شده‌اند. یعنی فایل های که در محیط staging هستند در کامیت پیشرو که قرار است ایجاد کنید ثبت میشوند

Commit

  • یک "اسنپ شات" از تغییرات فایل ها که با یک hash id و نام نویسنده و تاریخ ثبت میشود. بعدا میتوان با ارجاع به id کامیت به تغییرات مربوط به آن بازگشت.

Branch

  • شاخه (Branch) در گیت مثل یک مسیر جداگانه برای توسعهٔ کد است. فرض کنید کد اصلی پروژه روی خطی به نام main قرار دارد؛ حالا اگر بخواهید یک قابلیت جدید یا تغییر بزرگ را امتحان کنید بدون اینکه به کد اصلی آسیب بزنید، یک شاخه جدید می‌سازید. در این شاخه می‌توانید آزادانه تغییر ایجاد کنید، commit بزنید و حتی خرابکاری کنید، چون همهٔ این تغییرات جدا از شاخهٔ اصلی نگه داشته می‌شوند. وقتی کارتان تمام شد و مطمئن شدید تغییرات درست کار می‌کنند، می‌توانید شاخه را دوباره به شاخهٔ اصلی (merge) وصل کنید. این کار کمک می‌کند همزمان چند نفر روی پروژه کار کنند بدون اینکه مزاحم کار یکدیگر شوند.
  • در واقع یک اشاره‌گر (Pointer) است که نشان می‌دهد شما همین الان روی کدام شاخه یا کدام commit قرار دارید. وقتی روی یک شاخه مثل main هستید، HEAD به آخرین commit آن شاخه اشاره می‌کند؛ یعنی هر تغییر و commit جدیدی که انجام دهید، از همان نقطه ادامه پیدا می‌کند. اگر HEAD را به یک commit قدیمی منتقل کنید، وارد حالتی به نام detached HEAD می‌شوید، یعنی دیگر روی شاخه خاصی نیستید و تغییرات شما به یک شاخه وصل نمی‌شوند مگر اینکه برای آن یک شاخه جدید بسازید. به زبان ساده: HEAD مثل یک نشانگر است که جای پای فعلی شما در تاریخچهٔ پروژه را مشخص می‌کند.

Tag

  • مثل یک نشانه ثابت روی یک commit است. وقتی بخواهید یک نقطه مهم از تاریخچهٔ پروژه را علامت‌گذاری کنید، مثلاً زمان انتشار نسخهٔ ۱.۰، روی آن commit یک tag می‌زنید. بر خلاف شاخه که حرکت می‌کند و با commit‌های جدید جلو می‌رود، tag همیشه به همان commit خاص متصل می‌ماند و تغییر نمی‌کند. این باعث می‌شود بتوانید به راحتی به آن نسخهٔ مشخص برگردید یا کدی را که در زمان انتشار وجود داشته دقیقاً دوباره به دست آورید. به همین دلیل tag‌ها معمولاً برای نسخه‌دهی (versioning) و انتشار (release) استفاده می‌شوند.

Remote

  • به یک نسخهٔ از repository گفته می‌شود که روی یک سرور یا مکان دیگری (مثلاً GitHub، GitLab یا یک سرور داخلی) قرار دارد و همهٔ اعضای تیم می‌توانند به آن دسترسی داشته باشند. وقتی شما یک پروژه را کلون می‌کنید، معمولاً یک remote پیش‌فرض به نام origin ساخته می‌شود که به همان مخزن اصلی روی سرور اشاره دارد. با استفاده از remote می‌توانید تغییرات خود را به سرور بفرستید (push) یا تغییرات دیگران را دریافت کنید (pull/fetch). به زبان ساده، remote پلیست میان مخزن local شما و نسخهٔ مرکزی پروژه که امکان همکاری تیمی را فراهم می‌کند.

Merge

  • یک روش برای ترکیب تغییرات یک شاخه با شاخهٔ دیگر. وقتی شما روی یک شاخهٔ اصلی (مثلاً main) کار می‌کنید و همزمان یک شاخهٔ جدید (مثلاً feature) ساخته‌اید، در نهایت باید تغییرات آن‌ها را با هم ادغام کنید. دستور git merge این کار را انجام می‌دهد: تغییرات شاخهٔ دیگر را وارد شاخهٔ فعلی می‌کند. اگر تغییرات با هم تداخلی نداشته باشند، این کار به‌صورت خودکار انجام می‌شود؛ اما اگر در فایل‌های مشابه تغییرات ناسازگار باشد، به اصطلاح conflict رخ می‌دهد و شما باید آن را به‌صورت دستی حل کنید. merge معمولاً یک commit جدید به نام merge commit ایجاد می‌کند که نشان می‌دهد دو شاخه در این نقطه به هم پیوسته‌اند.

rebase

  • روشی دیگر برای ترکیب شاخه‌هاست که تاریخچهٔ گیت را خطی‌تر و مرتب‌تر نشان می‌دهد. در rebase، commit‌های شاخهٔ شما یکی‌یکی روی شاخهٔ هدف (مثلاً main) "بازنویسی" و دوباره اعمال می‌شوند، انگار که از ابتدا آن تغییرات روی همان شاخه انجام شده‌اند. نتیجه این است که تاریخچهٔ پروژه ساده‌تر و یک‌دست‌تر می‌شود. با این حال، چون rebase تاریخچه را تغییر می‌دهد، نباید روی شاخه‌هایی که دیگران همزمان روی آن‌ها کار می‌کنند استفاده شود، چون باعث تداخل و مشکلات هماهنگی می‌شود.

Conflict

  • در گیت، Conflict زمانی رخ می‌دهد که دو تغییر ناسازگار به‌طور همزمان روی یک بخش از فایل ایجاد شوند و گیت نتواند به‌صورت خودکار تصمیم بگیرد کدام تغییر درست است. برای مثال، اگر شما در شاخهٔ feature خطی از یک فایل را تغییر دهید و همزمان در شاخهٔ main همان خط تغییر دیگری داشته باشد، هنگام merge یا rebase گیت متوقف می‌شود و آن قسمت را به‌عنوان conflict علامت می‌زند. در این حالت شما باید فایل را باز کنید، بخش‌های علامت‌گذاری‌شده را بررسی کنید و به‌صورت دستی تصمیم بگیرید کدام نسخه (یا ترکیبی از هر دو) درست است.

دستورات گیت

init

  • این دستور یک مخزن گیت جدید در پوشهٔ فعلی ایجاد می‌کند؛ یعنی پوشه‌ای به نام .git می‌سازد که همهٔ تاریخچه، پیکربندی داخلی و متادیتای گیت را نگه می‌دارد. معمولاً وقتی می‌خواهی یک پروژهٔ جدید را تحت کنترل git قرار دهید این را اجرا می‌کنیم.
git init

clone

  • مخزن remote (مثلاً GitHub/GitLab) را کپی می‌کند و یک پوشهٔ محلی کامل به همراه تاریخچه آن می‌سازد. آدرس می‌تواند SSH یا HTTPS باشد.
git clone <repository-url>

status

  • وضعیت فعلی مخزن محلی را نشان می‌دهد: چه فایل‌هایی unstaged هستند، چه فایل‌هایی staged شده‌اند، کدام فایل‌ها untracked هستند، و روی چه شاخه‌ای هستید. اولین دستوری که قبل از commit یا push باید اجرا کرد تا مشخص شود چه چیزی تغییر کرده است.
git status

add

  • فایل‌ها یا تغییرات را به staging area منتقل می‌کند تا در commit بعدی ثبت شوند.
git add <path>
or
git add .
  • به‌صورت تعاملی بخش‌هایی از تغییرات یک یا چند فایل را نشان می‌دهد و اجازه می‌دهد تنها بخش‌های مشخصی را stage کنی. وقتی می‌خواهی commit‌های منطقی و کوچک بسازی، بسیار مفید است.
git add -p

commit

  • تغییرات موجود در staging area را به‌عنوان یک commit جدید ثبت می‌کند. پیام کوتاه باید خلاصه و امری باشد. برای توصیف بلندتر می‌توان بدون -m اجرا کرد تا یک ادیتور باز شود.
git commit -m "commit message"

branch

  • این دستور فهرستی از شاخه‌های محلی (local branches) در مخزن را نشان می‌دهد و شاخه‌ای را که هم‌اکنون روی آن هستید مشخص می‌کند. این دستور اطلاعاتی دربارهٔ وضعیت شاخه‌ها (مثل آخرین commit) نمایش نمی‌دهد.
git branch

گزینهٔ -a مشخص میکند git branch همهٔ شاخه‌ها را نشان دهد — هم شاخه‌های محلی و هم شاخه‌های remote-tracking (آن‌هایی که از remote مثل origin ردیابی می‌شوند).

git branch -a

switch

  • برای تغییر شاخه (switch) یا ایجاد و رفتن به شاخهٔ جدید استفاده می‌شود. switch خواناتر است ولی checkout هم هنوز رایج است. وقتی شاخه‌ای را می‌سازید با -c یا -b هم‌زمان وارد آن می‌شوید.
git switch <branch>       
git switch -c <new-branch>          // create and switch
git checkout -b <new-branch>

merge

  • تغییرات شاخهٔ مشخص را با شاخهٔ جاری ادغام می‌کند. اگر اختلافی نباشد ممکن است fast-forward انجام شود؛ در غیر این صورت یک merge commit ساخته می‌شود. در صورت وجود conflict، باید آن‌ها را دستی حل کرد.
git merge <branch>

rebase

  • کامیت‌های شاخهٔ فعلی را بر پایهٔ شاخهٔ مشخص «بازپخش» می‌کند تا تاریخچه خطی شود. این کار تاریخچه را بازنویسی می‌کند؛ پس توصیه می‌شود تنها روی شاخه‌های محلی یا شاخه‌هایی که دیگران به آن وابسته نیستند rebase انجام دهید.
git rebase <base-branch>

fetch

این دستور فقط اطلاعات و تغییرات جدید را از remote دانلود می‌کند بدون ادغام با شاخهٔ محلی. این دستور امن است چون تغییری روی working tree ایجاد نمی‌کند.

git fetch <remote>

pull

  • git pull --rebase مرسوم تر است چون تاریخچهٔ محلی را خطی نگه می‌دارد؛ ولی اگر نمی‌دانی ولی اگر از کاری که انجام میدهی اطمینان نداری ، بهتر است ابتدا fetch و سپس دستی merge یا rebase انجام دهی. ا* ین دستور معادل git fetch به‌علاوهٔ ادغام خودکار (merge)است.
git pull
git pull --rebase

push

  • تغییرات شاخهٔ محلی را به remote می‌فرستد.
git push <remote> <branch>
  • این دستور برای حذف شاخه‌ای از remote استفاده می‌شود
git push <remote> --delete <branch>

remote

  • لیست remotes و آدرس‌های fetch/push آن‌ها را نشان می‌دهد (مثلاً origin). مفید برای دیدن اینکه remote به کجا متصل است.
git remote -v
  • یک remote جدید تعریف می‌کند (مثلاً origin) که می‌توان از آن fetch/push کرد.
git remote add <name> <url>

log

  • تاریخچهٔ commits را نمایش می‌دهد.
git log

dif

  • این دستور تفاوت‌ها را نمایش می‌دهد: git diff تفاوت working tree با index را نشان می‌دهد (unstaged changes)، و git diff --staged تفاوت index با آخرین commit را (یعنی آنچه در commit بعدی خواهد رفت).
git diff
git diff --staged

show

  • جزئیات یک commit (اطلاعاتی مانند diff, message, metadata) را نمایش می‌دهد.
git show <commit-ish>

blame

  • برای هر خط فایل نشان می‌دهد که آخرین بار چه کسی و در کدام commit آن خط را تغییر داده است.
git blame <file>

revert

  • یک commit جدید ایجاد می‌کند که اثر commit مشخص‌شده را خنثی می‌کند. این روش امن برای undo در تاریخچهٔ عمومی است، چون تاریخچه را بازنویسی نمی‌کند.
git revert <commit>

reset

  • این دستور HEAD را به commit مشخص منتقل می‌کند:
    • soft: فقط HEAD را جابجا می‌کند (staged حفظ می‌شود).
    • mixed (پیش‌فرض): HEAD را جابجا و index را ریست می‌کند، اما فایل‌های کاری را نگه می‌دارد.
    • hard: همه همگام می‌شوند و تغییرات محلی حذف می‌شوند — این خطرناک است، قبل از اجرا حتماً مطمئن شو.
git reset --soft <commit>

git reset --mixed <commit>  

git reset --hard <commit>