اولین مخزنی که با Git ساختم، سه ماه بعد به یک فاجعه تبدیل شد: پنج برنچ بی‌نام، سه تا merge نیمه‌کاره و یک conflict که هرگز حل نشد. مسئله، پیچیدگی Git نبود؛ نبودِ یک مسیر یادگیری مرحله‌به‌مرحله بود. سال‌ها بعد، وقتی در پروژه‌های تیمی مختلف با Git کار کردم، فهمیدم هر کسی که Git را بلد است، آن را از روی تمرین یاد گرفته نه از روی مستندات رسمی. این مقاله همان مسیری است که اگر امروز بخواهم Git را به تازه‌کار یاد بدهم، گام‌به‌گام طی می‌کنم.

چرا Git بر تمام ابزارهای قبلی غالب شد؟

Git یک سیستم کنترل نسخه توزیع‌شده (Distributed Version Control System) است که در سال ۲۰۰۵ توسط لینوس توروالدز برای مدیریت هسته لینوکس ساخته شد. برای آشنایی با تاریخچه و جزئیات فنی آن، صفحه Git در ویکی‌پدیا منبع خوبی است. اما برای درک اینکه چرا Git بر رقبایی مثل SVN و CVS غالب شد، کافی است به سه ویژگی نگاه کنیم: سرعت، مدل شاخه‌بندی و کارکرد توزیع‌شده.

قبل از Git، ابزارهایی مثل SVN داشتیم که مخزن مرکزی‌شان حیاتی بود؛ اگر سرور مرکزی از دست می‌رفت، تاریخچه پروژه هم از دست می‌رفت. Git این مشکل را با مدل توزیع‌شده حل کرد: هر کلون از مخزن، یک نسخه کامل از تاریخچه را در خود دارد. این یعنی حتی اگر سرور اصلی هم از بین برود، هر توسعه‌دهنده یک بکاپ کامل دارد. اگر می‌خواهید تفاوت دقیق Git با SVN را ببینید، Git یا SVN: کدام بهتر است؟ را بررسی کنید.

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

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

Git یک ابزار نیست؛ یک مدل ذهنی است. تا وقتی فکر می‌کنید Git مجموعه‌ای از دستورات جادویی است، در هر مشکل جدید گیج می‌شوید. اما وقتی مدل ذهنی Git را بفهمید، دستورات را خودتان می‌سازید.

مدل ذهنی Git: قبل از هر دستوری، این را بفهمید

بیشتر تازه‌کارها Git را به‌عنوان یک مجموعه دستور می‌بینند و با حفظ کردن آن‌ها شروع می‌کنند. تجربه من این است که این رویکرد، در هفته اول کار می‌کند و در ماه سوم شکست می‌خورد. Git یک مدل ذهنی مشخص دارد که اگر آن را بفهمید، دستورات از دل آن بیرون می‌آیند.

مدل ذهنی Git سه جزء اصلی دارد: commit، برنچ و HEAD.

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

برنچ یک اشاره‌گر به یک commit است. وقتی می‌گوییم روی شاخه main هستیم، یعنی HEAD به commit آخر آن شاخه اشاره می‌کند. برنچ‌ها در Git موجودیت‌های مستقل نیستند؛ فقط ارجاع به یک نقطه در تاریخچه هستند.

HEAD یک اشاره‌گر به موقعیت فعلی شماست. HEAD می‌گوید همین حالا کدام commit را چک‌اوت کرده‌اید و روی کدام برنچ قرار دارید. وقتی git checkout main می‌زنید، HEAD به برنچ main منتقل می‌شود.

با درک این سه جزء، بقیه Git قابل پیش‌بینی می‌شود. مثلاً وقتی git commit می‌زنید، یک commit جدید ساخته می‌شود و برنچ فعلی به آن اشاره می‌کند. وقتی git branch new-feature می‌زنید، فقط یک اشاره‌گر جدید به همان commit ساخته می‌شود. وقتی git merge می‌کنید، دو شاخه در یک commit جدید به هم می‌رسند.

اگر می‌خواهید مسیر یادگیری Git را از صفر و ساختارمند شروع کنید، آموزش git از صفر نقطه شروع مناسبی است؛ اما برای اینکه از همان ابتدا با مدل ذهنی درست جلو بروید، همین مقاله را ادامه دهید.

اولین مخزن و اولین commit

برای شروع کار با Git، ابتدا باید آن را نصب کنید. در لینوکس و macOS، Git به‌طور پیش‌فرض نصب است یا به‌سادگی نصب می‌شود. در ویندوز از طریق نصب‌کننده رسمی Git for Windows یا با WSL. برای بررسی نصب بودن، در ترمینال دستور زیر را بزنید:

git --version

پس از اطمینان از نصب، اولین کار، تنظیم هویت شماست. Git از این اطلاعات برای ثبت نام نویسنده هر commit استفاده می‌کند:

git config --global user.name "نام شما"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main

حالا می‌توانید اولین مخزن خود را بسازید. در پوشه پروژه، دستور زیر را بزنید:

git init

این دستور یک پوشه مخفی به نام .git در مسیر شما می‌سازد که تمام تاریخچه و اطلاعات مخزن در آن نگه‌داری می‌شود. حالا فایلی بسازید، مثلاً README.md، و اولین commit خود را بزنید:

git add README.md
git commit -m "افزودن فایل README اولیه"

دستور git add فایل را به staging area اضافه می‌کند و دستور git commit آن را به‌عنوان یک عکس فوری ثبت می‌کند. حالا اگر دستور git log بزنید، اولین commit خود را می‌بینید.

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

سه حالت فایل در Git و نقش staging area

یکی از مفاهیمی که تازه‌کارها اغلب دیر یاد می‌گیرند، staging area یا همان index است. در Git، هر فایل می‌تواند در یکی از سه حالت باشد: Modified، Staged یا Committed. شناخت این سه حالت، پیش‌نیاز انجام کارهای پیشرفته‌تر است.

Modified: فایل تغییر کرده، اما این تغییرات در staging area ثبت نشده است. git status این فایل‌ها را در بخش «Changes not staged for commit» نشان می‌دهد.

Staged: فایل به staging area اضافه شده و آماده commit است. دستور git add file.php این کار را انجام می‌دهد. git status این‌ها را در بخش «Changes to be committed» نشان می‌دهد.

Committed: تغییرات در مخزن محلی Git ذخیره شده است. از این مرحله به بعد، فایل به‌عنوان بخشی از تاریخچه پروژه محسوب می‌شود.

چرا staging area مهم است؟ چون به شما اجازه می‌دهد تغییرات را به‌صورت انتخابی commit کنید. اگر در یک جلسه کاری پنج فایل را تغییر داده‌اید که دو تا از آن‌ها به یک فیچر مربوط است و سه‌تای دیگر به یک رفع باگ، می‌توانید اول فقط دو فایل را stage و commit کنید، سپس سه فایل دیگر را. این کار، تاریخچه را قابل فهم‌تر و ردیابی را راحت‌تر می‌کند.

دستور git diff تغییرات بین working directory و staging را نشان می‌دهد و git diff --staged تغییرات بین staging و آخرین commit. این دو دستور، ابزار روزمره شما در بازبینی هستند. اگر تازه با Git شروع کرده‌اید، عادت کنید قبل از هر commit یک بار git diff --staged بزنید تا مطمئن شوید همان چیزی را کامیت می‌کنید که می‌خواهید.

برنچ: قلب جریان کاری Git

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

git branch new-feature
git switch new-feature

# یا در یک دستور:
git switch -c new-feature

دستور git branch لیست برنچ‌های موجود را نشان می‌دهد و علامت ستاره کنار برنچ فعلی می‌گذارد. دستور git switch یا نسخه قدیمی‌تر git checkout شما را به برنچ موردنظر منتقل می‌کند. تفاوت جزئی بین این دو، در رفع خطاهای رایج git پوشش داده شده است.

یک قاعده عملی که در تمام پروژه‌هایم رعایت می‌کنم: هر برنچ باید یک هدف مشخص داشته باشد. اسم برنچ‌هایی مثل fix-login-bug، feature-user-profile یا refactor-payment خودشان توضیح می‌دهند چرا وجود دارند. از اسم‌های کلیشه‌ای مثل dev، temp یا test-branch پرهیز کنید؛ در سه ماه بعد، هیچ‌کس نمی‌داند این برنچ‌ها چه کاری انجام می‌دهند.

در جریان کاری تیمی، الگوی رایج این است که یک برنچ اصلی مثل main داشته باشید که همیشه پایدار است، و برای هر تغییر یک برنچ جداگانه از main بسازید. وقتی کار روی برنچ تمام شد، آن را با main ادغام می‌کنید. اگر می‌خواهید با راهبردهای مختلف برنچ‌بندی در تیم‌ها آشنا شوید، برنچ در git را ببینید که جریان‌های مختلف مثل Git Flow، GitHub Flow و Trunk-Based را کنار هم گذاشته‌ام.

Merge: ادغام امن و پیش‌بینی‌پذیر

Merge یعنی بردن تغییرات یک برنچ به برنچ دیگر. Git دو نوع اصلی merge دارد که تفاوتشان در جزئیات مهم است.

Fast-Forward Merge

اگر برنچ فعلی از آخرین commit برنچ هدف عقب نباشد و تاریخچه خطی باقی بماند، Git از Fast-Forward استفاده می‌کند؛ یعنی فقط اشاره‌گر برنچ مقصد را جابه‌جا می‌کند. نتیجه، تاریخچه خطی و ساده است.

git switch main
git merge new-feature

Three-Way Merge

اگر هر دو برنچ از نقطه‌ای مشترک جلو رفته باشند، Git یک commit جدید می‌سازد که دو تاریخچه را به هم متصل می‌کند. این commit، Merge Commit نام دارد و در تاریخچه با دو والد ظاهر می‌شود.

git switch main
git merge --no-ff new-feature

پرچم --no-ff باعث می‌شود حتی در حالتی که Fast-Forward ممکن است، Git یک Merge Commit بسازد. چرا این مفید است؟ چون در تاریخچه واضح است که یک قابلیت کامل از یک برنچ آمده. این مزیت در پروژه‌های بزرگ که چند توسعه‌دهنده هم‌زمان کار می‌کنند، بسیار مهم است. جزئیات بیشتر این الگو در مرج در git بررسی شده است.

در انتخاب بین این دو نوع merge، تجربه من این است: برای شاخه‌های کوتاه‌عمر (چند روزه)، Fast-Forward کافی است. برای شاخه‌های فیچر که چند هفته طول می‌کشند، Merge Commit شفافیت بیشتری می‌دهد. مهم‌تر از نوع merge، یکدستی تیم است؛ همه روی یک الگو توافق داشته باشند، بهتر از هر الگویی است که نیمه‌کاره پیاده شود.

تاریخچه Git شبیه دفتر خاطرات تیم است. اگر خودتان با خواندن آن گیج می‌شوید، پس بقیه هم گیج می‌شوند. Merge را طوری انجام دهید که شش ماه بعد هم بتوانید بفهمید چه اتفاقی افتاده است.

حل تعارض (Conflict) بدون اضطراب

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

وقتی تعارض رخ می‌دهد، Git در فایل موردنظر، نشانگرهای مشخصی می‌گذارد:

<<<<<<< HEAD
محتوای شما در برنچ فعلی
=======
محتوای طرف مقابل از برنچ دیگری
>>>>>>> new-feature

شما باید در فایل باز شده، بخش‌های اضافی را حذف کنید و نسخه درست را باقی بگذارید. سپس فایل را stage و commit کنید:

git add path/to/conflicted-file.php
git commit -m "حل تعارض در فایل پرداخت"

روش من در حل تعارض، سه مرحله‌ای است: اول، git status می‌زنم تا ببینم چند فایل درگیر هستند. دوم، هر فایل را با ویرایشگر باز می‌کنم و به‌جای تلاش برای انتخاب سریع یکی از دو طرف، ابتدا می‌فهمم چرا هر طرف این تغییر را داده. سوم، بعد از حل تعارض، یک تست کوچک روی پروژه اجرا می‌کنم تا مطمئن شوم راه‌حل من چیزی را نشکسته. اگر تعارض پیچیده است و وقت تصمیم ندارید، git merge --abort تمام تعارض را لغو می‌کند و شما را به حالت قبل برمی‌گرداند.

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

Rebase: چه زمانی و چه زمانی نه

Rebase یک ابزار قدرتمند است که گاهی در دستان نادرست، به یک فاجعه تبدیل می‌شود. ایده اصلی rebase این است که commit‌های یک برنچ را بردارد و آن‌ها را روی یک پایه جدید دوباره اعمال کند. نتیجه، تاریخچه خطی و تمیز است، بدون commit‌های merge اضافه.

git switch new-feature
git rebase main

این دستور، commit‌های جدید در برنچ new-feature را برمی‌دارد و آن‌ها را روی آخرین commit برنچ main اعمال می‌کند. تفاوت اصلی با merge این است که rebase، تاریخچه را بازنویسی می‌کند. commit‌ها شناسه جدیدی می‌گیرند و تاریخچه دیگر شبیه قبل نیست.

قاعده طلایی که در هر پروژه‌ای تکرار می‌کنم: برنچ‌هایی که با دیگران به اشتراک گذاشته‌اید را rebase نکنید. اگر برنچ شما روی مخزن راه دور push شده و کسی دیگر هم رویش کار می‌کند، rebase باعث می‌شود آن‌ها نتوانند به‌درستی pull کنند و مجبور شوند تاریخچه را دستی تعمیر کنند.

کاربرد اصلی rebase در جریان کاری من: قبل از اینکه یک برنچ فیچر را برای بازبینی بفرستم، rebase می‌زنم تا commit‌ها روی آخرین main اعمال شوند. این کار، بررسی Pull Request را ساده‌تر می‌کند. همچنین از rebase برای squash کردن چند commit کوچک به یک commit معنادار استفاده می‌کنم، معمولاً با git rebase -i HEAD~3.

ابزار git rebase -i یک ویرایشگر تعاملی باز می‌کند که در آن می‌توانید به هر commit بگویید pick، squash، edit یا drop شود. این قابلیت، ابزاری قدرتمند برای تمیز کردن تاریخچه قبل از ادغام است. اگر می‌خواهید با کاربردهای تیمی این ابزار آشنا شوید، رفع خطاهای رایج git چند سناریوی واقعی از rebase نادرست را بررسی می‌کند.

مخزن راه دور، remote و push/pull

تا اینجا همه چیز محلی بود. اما Git قدرت واقعی‌اش را در همکاری نشان می‌دهد. مخزن راه دور (Remote Repository) نسخه‌ای از پروژه است که روی سرور میزبانی می‌شود — مثل GitHub، GitLab یا Bitbucket. برای اتصال مخزن محلی به یک remote:

git remote add origin git@github.com:username/repo.git
git push -u origin main

دستور اول یک remote به نام origin اضافه می‌کند و دستور دوم شاخه main را برای بار اول push می‌کند. از این به بعد، برای ارسال تغییرات، فقط git push کافی است و برای دریافت تغییرات دیگران، git pull.

سه دستور مهم در کار با remote: git fetch، git pull و git push. تفاوت fetch و pull در این است که fetch تغییرات را از سرور می‌گیرد اما روی برنچ محلی اعمال نمی‌کند، در حالی که pull این دو کار را همزمان انجام می‌دهد. تجربه من این است که در پروژه‌های تیمی، عادت به fetch سپس merge آگاهانه، از pull کورکورانه امن‌تر است.

در سال‌های اخیر، احراز هویت در GitHub و GitLab از طریق SSH یا توکن‌های شخصی انجام می‌شود. اگر با تنظیم SSH آشنایی ندارید، در مقاله‌های مربوط به GitHub راهنمای گام‌به‌گام وجود دارد؛ همچنین آموزش github مسیر اتصال مخزن محلی به GitHub را توضیح می‌دهد.

Pull Request: از پیشنهاد تا ادغام

Pull Request (PR) یکی از قوی‌ترین اجزای جریان کاری مبتنی بر Git است. ایده اصلی این است که وقتی روی یک برنچ فیچر کارتان تمام شد، آن را به‌عنوان یک پیشنهاد برای ادغام به برنچ main ارائه می‌دهید. سپس همکاران شما کد را مرور می‌کنند، کامنت می‌گذارند و در نهایت PR را تأیید یا رد می‌کنند.

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

در طول مرور کد، ممکن است کامنت‌هایی دریافت کنید که نیاز به تغییر داشته باشند. روند معمول این است که تغییرات جدید را روی همان برنچ کامیت و push کنید؛ PR به‌طور خودکار به‌روز می‌شود. در پایان، هر کسی که مسئول بازبینی است، PR را ادغام می‌کند. برای جزئیات بیشتر درباره این فرآیند، pull request در github گام‌به‌گام نوشته شده است.

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

چهار جریان کاری رایج در تیم‌ها

جریان کاری Git یا Git Workflow یعنی مجموعه قواعدی که مشخص می‌کند تیم چطور از برنچ‌ها استفاده می‌کند. چهار جریان کاری رایج وجود دارد که هر کدام برای شرایط خاصی مناسب هستند.

۱. Git Flow

قدیمی‌ترین و ساختارمندترین جریان. با برنچ‌های main، develop، feature، release و hotfix کار می‌کند. برای پروژه‌های نرم‌افزاری که نسخه‌بندی مشخص دارند مناسب است، اما برای اپلیکیشن‌های وب که روزانه منتشر می‌شوند، سربار زیادی دارد.

۲. GitHub Flow

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

۳. Trunk-Based Development

در این جریان، همه توسعه‌دهنده‌ها تقریباً همیشه روی یک برنچ مشترک کار می‌کنند. برنچ‌های فیچر کوتاه‌عمر هستند و پس از چند ساعت یا چند روز ادغام می‌شوند. برای تیم‌هایی که فرآیند CI/CD قوی دارند، این جریان بیشترین سرعت را می‌دهد.

۴. Forking Workflow

در این جریان، هر توسعه‌دهنده یک fork از مخزن اصلی می‌سازد و روی آن کار می‌کند. برای پروژه‌های متن‌باز که مشارکت‌کنندگان خارجی دارند، این جریان رایج‌ترین است. اگر روی پروژه‌های GitHub با مشارکت خارجی کار می‌کنید، آموزش github این جریان را با جزئیات توضیح داده است.

در انتخاب بین این چهار جریان، اندازه تیم و سرعت انتشار تعیین‌کننده است. تیم‌های زیر پنج نفر با انتشار روزانه، احتمالاً GitHub Flow یا Trunk-Based انتخاب درستی دارند. تیم‌های بزرگ‌تر با چرخه‌های انتشار مشخص، Git Flow را ترجیح می‌دهند. اگر روی پروژه‌های وردپرسی کار می‌کنید، جریان کاری که با آن کار می‌کنید باید با ساختار پلاگین یا قالب شما هماهنگ باشد؛ گیت در وردپرس به این نکته پرداخته است.

نجات از فاجعه: undo، reset و reflog

در سال‌های اول کار با Git، بزرگ‌ترین ترسم این بود که اشتباهی commit یا merge کنم و نتوانم برگردانم. با تجربه، فهمیدم Git تقریباً هر چیزی را می‌تواند برگرداند، اگر بدانید کدام دستور کجاست.

undo کردن تغییرات

اگر تغییرات فایل هنوز commit نشده، git restore file.php تغییرات فایل را برمی‌گرداند. اگر فایل stage شده باشد، git restore --staged file.php آن را از staging خارج می‌کند.

برگرداندن یک commit

اگر می‌خواهید یک commit را برگردانید اما تاریخچه را حفظ کنید، git revert commit-hash یک commit جدید می‌سازد که تغییرات قبلی را معکوس می‌کند. این دستور امن است چون تاریخچه را بازنویسی نمی‌کند.

reset کردن به یک commit قبلی

اگر می‌خواهید به یک commit قدیمی برگردید، git reset سه حالت دارد: --soft که تغییرات را نگه می‌دارد در staging، --mixed که تغییرات را نگه می‌دارد در working directory، و --hard که همه تغییرات را پاک می‌کند. حالت --hard خطرناک است؛ قبل از استفاده از آن، حتماً بکاپ بگیرید یا اطمینان داشته باشید که تغییرات لازم را از دست می‌دهید.

reflog: بیمه‌نامه Git

اگر اشتباهی کردید که ظاهراً غیرقابل بازگشت بود، git reflog نجات‌دهنده شماست. این دستور، تاریخچه همه تغییرات HEAD را نشان می‌دهد، حتی تغییراتی که از تاریخچه رسمی حذف شده‌اند. می‌توانید با git reset --hard HEAD@{2} به یکی از حالت‌های قبلی برگردید.

یک تجربه واقعی که ارزش تعریف دارد: در یکی از پروژه‌ها، یک توسعه‌دهنده به‌اشتباه یک برنچ مهم را با git branch -D حذف کرد. به‌نظر می‌رسید کار از دست رفته است. اما با git reflog، commit آخر آن برنچ را پیدا کردیم و با یک دستور ساده برنچ را بازیابی کردیم. از آن روز به بعد، reflog برای من یکی از مهم‌ترین دستورات Git است.

اگر با خطاهای رایج Git روبرو شده‌اید، رفع خطاهای رایج git مجموعه‌ای از پیام‌های خطا و راه‌حل‌شان را کنار هم گذاشته است. اما برای درک عمیق‌تر، فهم reflog و reset پایه‌ای است که تمام راه‌حل‌ها را قابل پیش‌بینی می‌کند.

اشتباهات رایجی که تازه‌کارها مرتکب می‌شوند

  • کامیت کردن همه چیز در یک commit: یک commit باید یک تغییر منطقی را نشان دهد، نه پنج تغییر بی‌ربط. کامیت‌های بزرگ، بازبینی و بازگشت را سخت می‌کنند.
  • پیام commit بی‌معنا: پیام‌هایی مثل «fix» یا «update» هیچ اطلاعاتی به خواننده نمی‌دهند. به‌جای آن، بنویسید «رفع خطای اعتبارسنجی در فرم ثبت‌نام».
  • کار مستقیم روی main: در پروژه‌های تیمی، کار کردن مستقیم روی main یک اشتباه رایج است. همیشه یک برنچ از main بسازید و پس از اتمام کار، ادغام کنید.
  • commit کردن فایل‌های حساس: فایل‌های .env، کلیدهای SSH و رمزها هرگز نباید commit شوند. قبل از کامیت، git diff --staged بزنید.
  • rebasing روی برنچ اشتراکی: قاعده طلایی: برنچ مشترک را rebase نکنید. اگر این کار را بکنید، همکارانتان مجبور به تعمیر دستی تاریخچه می‌شوند.
  • force push بی‌احتیاط: git push --force می‌تواند تاریخچه مخزن راه دور را بازنویسی کند و کار دیگران را از بین ببرد. اگر نیاز به force push دارید، از --force-with-lease استفاده کنید که امن‌تر است.
  • نگه‌داشتن برنچ‌های قدیمی: برنچ‌هایی که چند ماه از ادغام‌شان گذشته، فقط تاریخچه را شلوغ می‌کنند. بعد از ادغام هر برنچ، آن را حذف کنید.
  • نداشتن فایل gitignore: بدون .gitignore، فایل‌های موقت، لاگ‌ها و پوشه‌های وابستگی هم به مخزن اضافه می‌شوند. اولین کاری که در هر پروژه جدید باید بکنید، ساخت این فایل است.
  • چند commit با پیام یکسان: اگر ده commit با پیام «wip» دارید، زمان تمیز کردن تاریخچه است. از git rebase -i استفاده کنید و commit‌ها را به شکل معنادار squash کنید.
  • نادیده گرفتن مرور کد در PR: یک PR که بدون بازبینی ادغام می‌شود، همان لحظه یک بدهی فنی تازه می‌سازد. مرور کد را جدی بگیرید، حتی در تیم‌های کوچک.

این ده اشتباه، از دل پروژه‌های واقعی بیرون آمده‌اند. اگر می‌خواهید ببینید Git چطور در تیم‌های حرفه‌ای استفاده می‌شود، نقد و بررسی Git: چرا برای توسعه‌دهندگان ضروری است؟ و مقایسه GitHub و GitLab دیدگاه گسترده‌تری ارائه می‌دهند.

پرسش‌های پرتکرار درباره یادگیری Git

چقدر طول می‌کشد تا Git را یاد بگیرم؟

برای کارهای پایه مثل commit، برنچ و merge، یک هفته تمرین کافی است. برای تسلط روی rebase، حل تعارض‌های پیچیده و reflog، حداقل چند ماه کار روی پروژه واقعی لازم است. تجربه من این است که کسانی که روزانه با Git کار می‌کنند، در سه ماه به سطح قابل قبولی می‌رسند.

آیا باید Git را قبل از GitHub یاد بگیرم؟

بله. GitHub یک سرویس میزبانی مخزن بر پایه Git است. اگر Git را بلد نباشید، GitHub فقط مجموعه‌ای از دکمه‌ها بدون معنا می‌شود. پیشنهاد می‌کنم اول Git را روی مخزن محلی تمرین کنید، بعد به GitHub بروید. برای اتصال این دو، آموزش github مسیر اتصال را توضیح می‌دهد.

تفاوت merge و rebase چیست؟

Merge تاریخچه را حفظ می‌کند و یک commit جدید برای ادغام می‌سازد. Rebase تاریخچه را بازنویسی می‌کند و commit‌ها را روی پایه جدید اعمال می‌کند. Merge برای برنچ‌های مشترک و بازبینی امن‌تر است، Rebase برای تمیز کردن تاریخچه برنچ محلی مناسب‌تر است.

چطور از شر یک commit اشتباه خلاص شوم؟

اگر commit هنوز push نشده، git reset --soft HEAD~1 تغییرات را در staging نگه می‌دارد و شما می‌توانید ویرایش کنید. اگر push شده، git revert commit-hash یک commit جدید معکوس می‌سازد که تغییرات را از بین می‌برد، بدون اینکه تاریخچه را دست بزند.

Git برای پروژه‌های وردپرسی مناسب است؟

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

چند برنچ می‌توانم داشته باشم؟

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

آیا باید از ابزارهای بصری Git استفاده کنم؟

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

اگر رمز SSH را گم کردم چه کار کنم؟

اگر کلید خصوصی SSH را گم کردید، فقط باید یک کلید جدید بسازید و کلید عمومی را در GitHub یا GitLab جایگزین کنید. اگر کلید شما توسط کسی دیگر بدست آمده، بلافاصله کلید قدیمی را از حساب خود حذف کنید. این رویکرد و رویکردهای امنیتی مشابه در سرور را در راه‌اندازی VPS امن برای میزبانی وردپرس بررسی کرده‌ام.

آیا برای پروژه‌های تک‌نفره Git ارزش دارد؟

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

چطور از force push بی‌خطر استفاده کنم؟

به‌جای --force، از --force-with-lease استفاده کنید. این گزینه قبل از اعمال تغییر، بررسی می‌کند که آیا کسی دیگری به برنچ push کرده یا نه. اگر بله، عملیات متوقف می‌شود و شما می‌توانید اول تغییرات را fetch کنید. این عادت ساده، جلوی خیلی از فاجعه‌های تیمی را گرفته است.

یک توصیه برای شروع

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

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

اگر در طول مسیر یادگیری Git به موقعیتی روبرو شده‌اید که مدتی وقت‌تان را گرفته — مثلاً یک conflict سرسخت یا یک rebase پیچیده — خوشحال می‌شوم آن را در دیدگاه‌ها بخوانم. بخصوص اگر راه‌حل خلاقانه‌ای برای بازیابی یک اشتباه پیدا کرده‌اید؛ این تجربه‌ها برای خواننده بعدی که در حال یادگیری Git است، از هر مرجع رسمی ارزشمندتر است. 🌱