اولین بار که پیام fatal: refusing to merge unrelated histories را دیدم، فکر کردم مخزنم خراب شده و باید از صفر بسازم. اگر آن روز کسی بود که بگوید این خطا یکی از ساده‌ترین خطاهای Git است، چند ساعت وقتم هدر نمی‌رفت. تجربه‌ام این است که در Git، هر خطا یک علت مشخص دارد و اکثر آن‌ها با یک دستور حل می‌شوند. اگر تازه با Git آشنا می‌شوید، آموزش Git از صفر نقطه شروع خوبی است و برای مرور پایه‌ها، Git در ویکی‌پدیا هم مفید است.

روش سیستماتیک عیب‌یابی خطاهای Git

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

دومین کار، بررسی وضعیت فعلی است. سه دستور که قبل از هر عملیاتی در خطا استفاده می‌کنم: git status که وضعیت شاخه و فایل‌ها را نشان می‌دهد، git log --oneline -10 که ده کامیت آخر را نشان می‌دهد و git reflog که تاریخچه کامل جابجایی‌های HEAD را نشان می‌دهد. این سه دستور، تصویر کاملی از وضعیت فعلی می‌دهند و جلوی تصمیم‌های اشتباه را می‌گیرند.

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

خطاهای push و pull

چند خطای رایج در push و pull و راه‌حل دقیق هرکدام:

  • rejected - non-fast-forward: یعنی مخزن راه دور کامیت‌هایی دارد که شما ندارید. راه‌حل: ابتدا git pull کنید، تعارضات را حل کنید و سپس push کنید. هرگز در این حالت از force push استفاده نکنید مگر اینکه شاخه فقط مال شما باشد.
  • refusing to merge unrelated histories: یعنی دو مخزن با تاریخچه مستقل دارید. راه‌حل: git pull origin main --allow-unrelated-histories. این اجازه می‌دهد دو تاریخچه ادغام شوند.
  • failed to push some refs: می‌تواند دلایل مختلفی داشته باشد. اگر مربوط به branch protection است، شاخه اصلی معمولاً نیاز به بازبینی دارد. اگر مربوط به تاریخچه است، همان non-fast-forward است.
  • Authentication failed: معمولاً رمز یا کلید SSH را درست تنظیم نکرده‌اید. برای HTTPS باید از Personal Access Token به جای رمز عبور استفاده کنید. برای SSH، کلید را در GitHub تنظیم کنید.
  • Could not resolve host: مشکل شبکه یا DNS. اتصال اینترنت را چک کنید و از تنظیمات پروکسی آگاه باشید.

یک نکته مهم در مورد git pull: به صورت پیش‌فرض، pull یک merge می‌سازد. اگر می‌خواهید تاریخچه خطی باشد، می‌توانید از git pull --rebase استفاده کنید. اما این دستور تاریخچه را بازنویسی می‌کند و نباید روی شاخه‌های عمومی استفاده شود. برای درک تفاوت merge و rebase، مرج در Git راهنمای کاملی دارد.

خطاهای merge و conflict

خطاهای merge بیشتر از جنس conflict هستند تا خطای سیستم. وقتی دو شاخه یک بخش از کد را متفاوت تغییر داده باشند، Git نمی‌تواند به صورت خودکار ادغام کند و از شما کمک می‌خواهد. راهنمای کامل در حل تعارض در Git آمده است. چند خطای رایج:

  • CONFLICT (content): تعارض در محتوای فایل. باید فایل را باز کنید و بخش‌های متعارض را با انتخاب نسخه درست حل کنید.
  • CONFLICT (modify/delete): یک طرف فایل را تغییر داده و طرف دیگر آن را حذف کرده. باید تصمیم بگیرید که فایل را نگه دارید یا حذف کنید.
  • CONFLICT (rename/rename): هر دو طرف فایل را تغییر نام داده‌اند. باید نام نهایی را انتخاب کنید.

روش حل تعارض که در پروژه‌ها به آن رسیدم: اول، با git status فهرست فایل‌های متعارض را ببینید. دوم، هر فایل را باز کنید و بخش‌های <<<<<<<، ======= و >>>>>>> را شناسایی کنید. سوم، تصمیم بگیرید کدام نسخه درست است و بقیه را حذف کنید. چهارم، git add بزنید و بعد git commit کنید تا merge کامل شود.

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

Detached HEAD یک وضعیت است، نه یک خطا. وقتی HEAD شما به یک کامیت خاص اشاره می‌کند نه به یک شاخه، در حالت detached هستید. این معمولاً بعد از git checkout <commit-hash> اتفاق می‌افتد. اگر در این حالت کامیت جدیدی بزنید، آن کامیت در هیچ شاخه‌ای نیست و ممکن است بعداً گم شود.

راه‌حل: اگر می‌خواهید این کامیت‌ها را نگه دارید، یک شاخه جدید بسازید: git checkout -b <new-branch>. اگر می‌خواهید به شاخه قبلی برگردید و این تغییرات را رها کنید: git checkout main یا git switch main. اگر کامیت زده‌اید و می‌خواهید آن را به شاخه فعلی بیاورید، با git reflog هش کامیت را پیدا کنید و با git cherry-pick به شاخه اعمال کنید.

برای جلوگیری از این وضعیت، از git switch به جای git checkout <commit-hash> استفاده کنید چون واضح‌تر است. اگر واقعاً می‌خواهید یک کامیت قدیمی را ببینید، بهتر است روی یک شاخه موقت بروید: git switch -c temp-view <commit>. برای یادگیری دستورات Git، دستورات ضروری Git و برنچ در Git راهنمای کاربردی دارند.

خطاهای reset و بازیابی کامیت‌های از دست رفته

ترسناک‌ترین خطاهای Git، از جنس از دست دادن کار هستند. اما خبر خوب این است که Git تقریباً هر چیزی را می‌تواند بازیابی کند. رایج‌ترین سناریو: کسی git reset --hard می‌زند و کامیت‌های خود را از دست می‌دهد. راه‌حل:

  1. با git reflog تاریخچه جابجایی‌های HEAD را ببینید.
  2. هش کامیتی که می‌خواهید بازیابی کنید را پیدا کنید.
  3. با git reset --hard <commit-hash> به آن کامیت برگردید یا git cherry-pick <commit-hash> آن را روی شاخه فعلی اعمال کنید.

حتی اگر شاخه‌ای را با git branch -D حذف کرده باشید، تا زمانی که git reflog اطلاعات دارد، می‌توانید آن را بازیابی کنید. برای این کار، هش کامیت آخر شاخه را در reflog پیدا کنید و با git branch <name> <commit-hash> آن را بازسازی کنید. تنها در صورتی که Git garbage collector اجرا شده باشد و reflog پاک شده باشد، بازیابی ممکن نیست که این حالت به صورت پیش‌فرض تا ۳۰ روز نمی‌افتد.

یک نکته طلایی: قبل از هر git reset --hard یا git clean -fd، یک بار دیگر به git status نگاه کنید. اگر تغییرات مهمی دارید که کامیت نشده‌اند، اول با git stash ذخیره کنید یا با یک کامیت موقت نگه دارید. این یک عادت کوچک است که در پروژه‌ها چند بار نجاتم داده. برای درک reset و revert، دستورات ضروری Git را ببینید.

خطاهای بازنویسی تاریخچه و force push

وقتی تاریخچه شاخه‌ای را بازنویسی می‌کنید، مثلاً با git rebase یا git commit --amend، هش کامیت‌ها تغییر می‌کند و مخزن راه دور در حالت عقب‌تر می‌ماند. در این حالت، push معمولی رد می‌شود و باید از force push استفاده کنید. اما force push یک ابزار خطرناک است که در پروژه‌های تیمی می‌تواند فاجعه بسازد.

دو نوع force push وجود دارد. git push --force که بی‌رحم است و هر تغییری را در سرور جایگزین می‌کند. git push --force-with-lease که محتاط‌تر است: فقط اگر مخزن راه دور از زمانی که شما fetch کرده‌اید تغییری نکرده باشد، push را انجام می‌دهد. در پروژه‌های تیمی، همیشه از force-with-lease استفاده کنید، نه force.

قاعده‌ای که در تیم‌ها به آن رسیدم: force push فقط روی شاخه‌های شخصی و موقت. هرگز روی main، develop یا هر شاخه‌ای که دیگران روی آن کار می‌کنند. اگر ناچار به force push روی شاخه مشترک هستید، حتماً با تیم هماهنگ کنید و به همه اطلاع بدهید تا از دست رفتن کار جلوگیری شود. برای درک بهتر، Pull Request در GitHub و برنچ در Git راهنمای کاملی دارند.

force push روی شاخه‌های عمومی مثل تیراندازی به سمت تاریکی است؛ ممکن است به کسی آسیب بزند که هرگز نمی‌بینی.

خطاهای مرتبط با فایل‌ها و لاک

چند خطای دیگر که در پروژه‌ها زیاد دیده‌ام:

  • index.lock: یعنی فرآیند Git قبلی به درستی بسته نشده. راه‌حل: مطمئن شوید هیچ فرآیند Git دیگری در حال اجرا نیست و سپس فایل .git/index.lock را حذف کنید. روی ویندوز ممکن است نیاز به بستن ادیتور یا IDE داشته باشید.
  • Permission denied (publickey): کلید SSH شما به درستی تنظیم نشده. کلید عمومی را در GitHub یا GitLab اضافه کنید و ssh-agent را بررسی کنید.
  • File too large: فایلی از حد مجاز Git عبور کرده. راه‌حل: فایل را از کامیت حذف کنید و در gitignore قرار دهید. برای حذف از تاریخچه، از ابزارهایی مثل git-filter-repo استفاده کنید.
  • Filename too long: معمولاً روی ویندوز اتفاق می‌افتد. راه‌حل: git config --system core.longpaths true.
  • LF will be replaced by CRLF: هشدار است نه خطا، اما می‌تواند مشکل‌ساز شود. با فایل .gitattributes می‌توانید line endings را یکنواخت کنید.

در مورد مدیریت فایل‌های بزرگ، راه‌حل بلندمدت استفاده از Git LFS (Large File Storage) است. اگر پروژه شما فایل‌های باینری بزرگ مثل تصاویر محصول یا ویدئو دارد، Git LFS ابزار ضروری است. برای پروژه‌های وردپرسی که فایل‌های رسانه‌ای زیاد دارند، این ابزار می‌تواند جلوی بسیاری از مشکلات را بگیرد. برای مدیریت رسانه‌ها در وردپرس، بهترین افزونه‌های بهینه‌سازی تصویر و فشرده‌سازی تصاویر سایت راهنمای کاربردی دارند.

پیشگیری از خطاهای رایج Git

پیشگیری همیشه بهتر از درمان است. چند عادت که در پروژه‌ها به آن‌ها رسیدم و خطاها را کم می‌کنند:

  1. هر روز صبح، git pull از main یا develop. این عادت جلوی تعارض‌های بزرگ را می‌گیرد.
  2. قبل از هر reset --hard یا clean -fd، از تغییرات فعلی یک بکاپ بگیرید یا stash کنید.
  3. شاخه‌ها را کوتاه نگه دارید. شاخه‌ای که بیش از چند روز عمر دارد، احتمال تعارضش بالا می‌رود.
  4. کامیت‌های کوچک و متمرکز بزنید. کامیت بزرگ، خطای بزرگ به همراه می‌آورد.
  5. از ابزار گرافیکی مثل GitKraken یا VS Code برای دیدن وضعیت مخزن استفاده کنید. تصویر واضح‌تر از متن، جلوی بسیاری از اشتباه‌ها را می‌گیرد.
  6. پیام کامیت واضح و معنادار بنویسید. کامیت‌های با پیام خوب، در زمان عیب‌یابی نجات‌دهنده هستند.

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

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

آیا کامیت‌های پاک‌شده قابل بازیابی هستند؟ معمولاً بله. تا زمانی که git reflog اطلاعات دارد که به صورت پیش‌فرض ۳۰ روز است، می‌توانید با git reflog کامیت‌های از دست رفته را پیدا و بازیابی کنید.

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

چگونه یک فایل اضافه‌شده به Git را حذف کنم؟ از staging با git rm --cached <file> و از کامیت با git rm <file>. اگر می‌خواهید از تاریخچه هم حذف شود، به ابزارهایی مثل git-filter-repo نیاز دارید.

چرا Git به من اجازه push نمی‌دهد؟ دلایل مختلفی دارد: عدم دسترسی، branch protection، non-fast-forward، یا احراز هویت نامعتبر. پیام خطا را با دقت بخوانید تا علت مشخص شود.

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

برای یادگیری Git به صورت کامل، آموزش Git از صفر، دستورات ضروری Git و برنچ در Git را ببینید. برای کار تیمی با Git، Pull Request در GitHub و مدیریت پروژه با GitHub Projects راهنمای کاربردی دارند. برای ادغام با CI/CD، GitHub Actions و CI/CD برای پروژه‌های وردپرسی را ببینید. برای پروژه‌های وردپرسی، گیت در توسعه وردپرس نکات ارزشمندی دارد. برای کار با GitLab، GitLab برای تیم‌های DevOps و مهاجرت از GitHub به GitLab را مطالعه کنید.

آنچه از پروژه‌های واقعی یاد گرفتم

سه چیز بعد از سال‌ها کار با Git در ذهنم جا افتاده. اول، هر خطای Git یک علت مشخص دارد و اکثر آن‌ها با یک دستور حل می‌شوند. دوم، Git تقریباً هیچ‌وقت چیزها را به‌طور دائمی پاک نمی‌کند؛ با reflog می‌توان بازیابی کرد. سوم، پیشگیری از خطا با عادت‌های ساده، بسیار موثرتر از رفع خطا پس از وقوع است. برای اصول کار با مخزن، ساختاربندی پروژه توسعه وردپرس و اصول کدنویسی تمیز راهنمای کاملی دارند. برای مدیریت ابزارها، Visual Studio Code، Jira و تجربه استفاده از GitHub در پروژه‌های تیمی را ببینید. برای مدیریت پروژه‌های بزرگ، غلبه بر چالش‌های پروژه‌های وردپرسی نکات ارزشمندی دارد.

اگر خطای Gitی داشتید که مدت‌ها طول کشید تا حل شود یا راه‌حل جالبی برای آن پیدا کردید، در دیدگاه بنویسید. برای من جالب است بدانم کدام خطا بیشترین دردسر را در پروژه‌های شما ایجاد کرده است. 🔧