برای پیدا کردن کمیته ای که باگ را وارد کرده، با آموزش Git Bisect کافی است یک commit خوب و یک commit بد مشخص کنید. Git با جستجوی دودویی بین این دو نقطه حرکت می کند و شما در هر مرحله فقط می گویید نتیجه خوب بود یا بد. در چند تکرار، اولین commit خراب معرفی می شود. اگر تکرار دستی زمانگیر است، با git bisect run آن را کاملا خودکار کنید.

چه زمانی به Git Bisect نیاز داریم؟

وقتی باگی اخیرا ظاهر شده و نمی دانید کدام تغییر آن را ایجاد کرده است. اگر فقط چند commit اخیر دارید، می توانید با بررسی دستی یا git log مشکل را پیدا کنید. اما وقتی صدها commit بین نسخه سالم و نسخه خراب دارید، bisect سریع ترین و مطمئن ترین راه است. برای خطاهای زمان اجرا، افت کارایی، تغییر رفتار API یا تست هایی که تازه قرمز شده اند، عالی عمل می کند.

پیش نیازها و آماده سازی

  • مخزن Git باید تاریخچه ای حداقل شامل یک نقطه سالم و یک نقطه خراب داشته باشد.
  • Working tree باید تمیز باشد. اگر تغییر محلی دارید:
    git stash -u
  • معیار قطعی برای تشخیص «خوب/بد» داشته باشید؛ مثلا یک تست واحد که در حالت سالم سبز و در حالت خراب قرمز شود، یا یک دستور بازتولید کننده خطا.
  • یک commit یا tag سالم که مطمئنید باگ در آن وجود نداشته را انتخاب کنید؛ هرقدر قدیمی تر و مطمئن تر، بهتر.

مسیر سریع (در 60 ثانیه)

  1. شروع bisect و تعیین مرزها:
    git bisect start
    git bisect bad HEAD
    git bisect good <hash-or-tag-of-a-good-commit>
  2. Git شما را به یک commit می برد. برنامه یا تست را اجرا کنید. اگر مشکل وجود دارد:
    git bisect bad

    اگر مشکل وجود ندارد:

    git bisect good
  3. مرحله قبل را تکرار کنید تا پیام “first bad commit is …” را ببینید.
  4. پس از اتمام:
    git bisect reset

مثال عملی با تست خودکار

هدف: هر commit را بسازیم و تستی را اجرا کنیم که در صورت وجود باگ، با کد غیر صفر خارج شود. با git bisect run این ارزیابی برای هر commit به صورت خودکار انجام می شود.

اسکریپت تست نمونه

این اسکریپت برای پروژه های Node.js فقط یک نمونه است. برای هر پشته فناوری، دستورات ساخت و تست را مطابق پروژه خود جایگزین کنید.

#!/usr/bin/env bash
# فایل: bisect-test.sh
# خروجی 0 = خوب، خروجی غیر صفر (به جز 125) = بد، خروجی 125 = پرش (skip)

# اگر وابستگی ها نصب نشدند یا محیط آماده نیست، این commit را رد کن
npm ci --prefer-offline --no-audit || exit 125

# اگر ساخت لازم است، اینجا اضافه کنید (در صورت شکست، exit غیر صفر یعنی بد)
# npm run build || exit 1

# اجرای تست ها: شکست = بد (کد غیر صفر)، موفقیت = خوب (کد 0)
npm test

دسترسی اجرایی بدهید:

chmod +x bisect-test.sh

اجرای خودکار bisect

git bisect start
git bisect bad HEAD
git bisect good <known-good-hash-or-tag>
git bisect run ./bisect-test.sh

Git هر commit میانی را checkout می کند، اسکریپت را اجرا می کند و بر اساس کد خروجی، آن commit را good یا bad علامت می زند. وقتی کار تمام شد، commit مقصر را نشان می دهد. سپس با reset به حالت عادی برگردید:

git bisect reset

نتیجه را چگونه راستی آزمایی کنیم؟

برای اطمینان از صحت خروجی، این چک کوتاه را انجام دهید:

  1. جزئیات commit اعلام شده را ببینید:
    git show <bad-commit>
  2. تست را روی commit بد اجرا کنید تا شکست را ببینید:
    git checkout <bad-commit>
    # اجرای دستور بازتولید یا اسکریپت تست
    ./bisect-test.sh || echo "Fail as expected"
  3. Commit قبلی را امتحان کنید تا سالم باشد:
    git checkout <bad-commit>^
    ./bisect-test.sh && echo "Pass as expected"
  4. پس از بررسی:
    git checkout -

اگر commit قبلی هم بد بود، یعنی مرز «commit سالم» اولیه را اشتباه انتخاب کرده اید. مرز سالم را عقب تر ببرید و bisect را مجدد اجرا کنید.

اشتباهات رایج و چطور از آنها اجتناب کنیم

  • انتخاب commit سالم نادرست: اگر «good» واقعا آلوده باشد، تمام نتیجه گیری اشتباه می شود. ترجیحا از یک نسخه منتشر شده یا tag معتبر استفاده کنید.
  • تستی که باگ را آشکار نمی کند: معیار تشخیص باید قطعی باشد. یک تست شکست خور ثابت یا فرمان بازتولید شفاف آماده کنید.
  • آلودگی وضعیت کاری: فایل های ساخته شده یا وابستگی های قدیمی ممکن است نتیجه را کج کنند. قبل از bisect روی پاکیزگی حساب باز کنید؛ در صورت نیاز از این فرمان استفاده کنید:
    git clean -xfd
  • نادیده گرفتن commit های ناسازگار: اگر commit میانی به هر دلیل قابل ساخت نیست، از skip استفاده کنید:
    git bisect skip

    در حالت خودکار، با exit 125 این کار را انجام دهید.

  • فراموش کردن reset: همیشه پس از اتمام:
    git bisect reset

ترفندها و نکات کاربردی

  • ثبت مسیر تصمیم گیری:
    git bisect log      # تاریخچه good/bad/skip
    git bisect replay <file>  # تکرار یک bisect از روی لاگ
  • حداقل سازی تست: تستی بسازید که فقط سناریوی مشکوک را اجرا کند تا هر چرخه سریع تر شود. زمان اجرای کوتاه، تعداد تکرار را به شکل محسوسی کم می کند.
  • مدیریت تست های ناپایدار: اگر تست گاهی می شکند، آن را پایدار کنید یا با چند بار تکرار نتیجه اکثریت بگیرید. نمونه سرراست:
    #!/usr/bin/env bash
    tries=3
    pass=0
    for i in $(seq 1 $tries); do
      npm test && pass=$((pass+1))
    done
    if [ "$pass" -ge 2 ]; then exit 0; else exit 1; fi
  • محیط سازگار: اگر ساخت پروژه سنگین است، از کش وابستگی یا build incremental بهره ببرید، اما مراقب نتایج کاذب باشید. در صورت تردید، قبل از هر چرخه build تمیز انجام دهید.

کار با سناریوهای خاص

Commit های ادغامی و تاریخچه پیچیده

Bisect روی DAG تاریخچه کار می کند، نه فقط روی خط مستقیم. اگر commit بد داخل یک merge باشد، Git آن را پیدا می کند. گاهی لازم است هر دو والد merge را بررسی کنید. اگر نتیجه مبهم شد، محدوده good/bad را دقیق تر کنید.

وقتی «خوب» قطعی ندارید

اگر نقطه سالم دقیقی نمی شناسید، قدیمی ترین commit های پروژه را امتحان کنید. اگر پروژه در ابتدا باگ را نداشت، همان نقطه good است. نبود نقطه good باعث می شود bisect نتواند جهت را تشخیص دهد.

پرش خودکار وقتی ساخت ممکن نیست

در اسکریپت، هر زمان پیش نیازها فراهم نشد یا build شکست بیرونی داشت که به باگ شما ربطی ندارد، با کد 125 از آن commit عبور کنید:

make deps || exit 125
make test

مقایسه کوتاه: چه زمانی گزینه دیگری بهتر است؟

  • git blame: وقتی تغییر غلط در چند فایل محدود و نزدیک به هم است و می خواهید آخرین ویرایشگر خطوط را ببینید. برای باگ های سیستمی یا اثرات غیرمستقیم کافی نیست.
  • git log -S / -G: جستجو بر اساس اضافه یا حذف یک نشانه (string یا regex). اگر به یک کلیدواژه خاص مشکوکید سریع جواب می دهد، اما برای باگ های رفتاری بدون نشانه متنی مناسب نیست.
  • Revert آزمایشی: وقتی می دانید چند commit اخیر مقصرند، برگرداندن موقت آنها ممکن است سریع تر باشد؛ ولی اگر دامنه گسترده است، bisect امن تر و نظام مندتر است.

گام بعدی چیست؟

حالا که commit خراب را پیدا کردید، یک تست بازگشتی بنویسید تا مانع تکرار شود، سپس با یک commit مجزا مشکل را رفع کنید یا در صورت نیاز همان commit را بازگردانید (revert). لاگ bisect و اسکریپت تست را در مخزن نگه دارید تا برای خطاهای بعدی هم بتوانید جستجوی خودکار را سریع اجرا کنید.