Chapters ▾ 2nd Edition

7.10 ابزارهای گیت (Git Tools) - اشکال‌زدایی با گیت (Debugging with Git)

اشکال‌زدایی با گیت (Debugging with Git)

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

حاشیه‌نویسی فایل (File Annotation)

اگر باگی در کدتان پیدا کردید و می‌خواهید بدانید کی و چرا به وجود آمده، حاشیه‌نویسی فایل معمولاً بهترین ابزار شماست. این ابزار نشان می‌دهد که آخرین کمیت (commit) که هر خط از هر فایلی را تغییر داده، کدام است. پس اگر متدی در کد شما مشکل دارد، می‌توانید با دستور git blame فایل را حاشیه‌نویسی کنید تا مشخص شود کدام کمیت مسئول ایجاد آن خط بوده است.

مثال زیر از git blame برای تعیین کمیت و کمیت‌کننده مسئول خطوطی در فایل Makefile هسته لینوکس در سطح بالایی استفاده می‌کند و همچنین با گزینه -L خروجی حاشیه‌نویسی را به خطوط ۶۹ تا ۸۲ آن فایل محدود می‌کند:

$ git blame -L 69,82 Makefile
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 69) ifeq ("$(origin V)", "command line")
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 70)   KBUILD_VERBOSE = $(V)
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 71) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 72) ifndef KBUILD_VERBOSE
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 73)   KBUILD_VERBOSE = 0
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 74) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 75)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 76) ifeq ($(KBUILD_VERBOSE),1)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 77)   quiet =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 78)   Q =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 79) else
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 80)   quiet=quiet_
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 81)   Q = @
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 82) endif

توجه کنید که فیلد اول، شناسه SHA-1 جزئی کمیتی است که آخرین تغییر را روی آن خط اعمال کرده. دو فیلد بعدی اطلاعاتی از آن کمیت هستند — نام نویسنده و تاریخ نوشتن کمیت — تا به راحتی بفهمید چه کسی و چه زمانی آن خط را تغییر داده است. بعد از آن شماره خط و محتوای فایل نمایش داده می‌شود. همچنین به خطوط کمیت‌هایی که با ^1da177e4c3f4 شروع می‌شوند توجه کنید؛ پیشوند ^ نشان می‌دهد این خطوط از اولین کمیت مخزن آمده‌اند و از آن زمان تاکنون تغییر نکرده‌اند. این کمی گیج‌کننده است، چون تاکنون حداقل سه کاربرد مختلف ^ را در گیت دیده‌اید، ولی این معنی آن در اینجا است.

یکی دیگر از نکات جالب گیت این است که تغییر نام فایل‌ها را به طور صریح دنبال نمی‌کند. گیت تنها اسنپ‌شات‌ها را ثبت می‌کند و بعداً به طور ضمنی تلاش می‌کند بفهمد چه چیزی تغییر نام داده است. یکی از ویژگی‌های جالب این موضوع این است که می‌توانید از گیت بخواهید انواع حرکات کد را هم تشخیص دهد. اگر به git blame گزینه -C را بدهید، گیت فایل حاشیه‌نویسی شده را تحلیل می‌کند و سعی می‌کند بفهمد بخش‌هایی از کد که در آن وجود دارد در اصل از کجا کپی شده‌اند. مثلاً فرض کنید دارید فایلی به نام GITServerHandler.m را به چند فایل تقسیم‌بندی می‌کنید که یکی از آن‌ها GITPackUpload.m است. با اجرای git blame روی GITPackUpload.m همراه گزینه -C می‌توانید ببینید بخش‌های مختلف کد قبلاً از کجا آمده‌اند:

$ git blame -C -L 141,153 GITPackUpload.m
f344f58d GITServerHandler.m (Scott 2009-01-04 141)
f344f58d GITServerHandler.m (Scott 2009-01-04 142) - (void) gatherObjectShasFromC
f344f58d GITServerHandler.m (Scott 2009-01-04 143) {
70befddd GITServerHandler.m (Scott 2009-03-22 144)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 145)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 146)         NSString *parentSha;
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 147)         GITCommit *commit = [g
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 148)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 149)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 150)
56ef2caf GITServerHandler.m (Scott 2009-01-05 151)         if(commit) {
56ef2caf GITServerHandler.m (Scott 2009-01-05 152)                 [refDict setOb
56ef2caf GITServerHandler.m (Scott 2009-01-05 153)
 این واقعاً مفید است.
معمولاً، شما به عنوان کامیت اصلی، کامیتی را می‌گیرید که کد را از آنجا کپی کرده‌اید، زیرا آن اولین باری است که آن خطوط را در این فایل تغییر داده‌اید.
گیت به شما کامیت اصلی که آن خطوط را نوشته‌اید نشان می‌دهد، حتی اگر آن کد در فایل دیگری بوده باشد.

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

فرض کنیم به تازگی نسخه‌ای از کد خود را به محیط تولید فرستاده‌اید، گزارش باگ دریافت می‌کنید که در محیط توسعه شما رخ نمی‌داده و نمی‌توانید تصور کنید چرا کد اینگونه رفتار می‌کند. به کد خود برمی‌گردید و متوجه می‌شوید که می‌توانید مشکل را بازتولید کنید، اما نمی‌توانید بفهمید چه چیزی اشتباه است. می‌توانید با bisect کردن کد، علت را پیدا کنید. ابتدا با دستور git bisect start کار را آغاز می‌کنید، سپس با git bisect bad به سیستم می‌گویید که کامیتی که در حال حاضر روی آن هستید خراب است. بعد باید به bisect بگویید آخرین وضعیت خوب کی بوده، با استفاده از دستور git bisect good <good_commit>:

$ git bisect start
$ git bisect bad
$ git bisect good v1.0
Bisecting: 6 revisions left to test after this
[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] Error handling on repo

گیت متوجه می‌شود که حدود ۱۲ کامیت بین کامیتی که به عنوان آخرین کامیت خوب مشخص کرده‌اید (مثلاً v1.0) و نسخه خراب فعلی وجود دارد، و کامیت وسط را برای شما چک‌اوت می‌کند. در این مرحله می‌توانید تست خود را اجرا کنید تا ببینید مشکل در این کامیت وجود دارد یا خیر. اگر وجود داشت، یعنی مشکل قبل از این کامیت وسطی رخ داده؛ اگر نبود، مشکل بعد از این کامیت وسطی ایجاد شده است. فرض کنید در این کامیت مشکلی نیست، پس با تایپ git bisect good به گیت اطلاع می‌دهید و ادامه می‌دهید:

$ git bisect good
Bisecting: 3 revisions left to test after this
[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] Secure this thing

حالا روی کامیت دیگری هستید، دقیقاً در نیمه‌راه بین کامیتی که تست کردید و کامیت خراب. دوباره تست را اجرا می‌کنید و می‌بینید این کامیت خراب است، پس با git bisect bad به گیت اطلاع می‌دهید:

$ git bisect bad
Bisecting: 1 revisions left to test after this
[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] Drop exceptions table

این کامیت خوب است و حالا گیت تمام اطلاعات لازم را دارد تا محل ایجاد مشکل را مشخص کند. گیت شناسه SHA-1 اولین کامیت خراب را نشان می‌دهد و بخشی از اطلاعات کامیت و فایل‌هایی که در آن تغییر کرده‌اند را نمایش می‌دهد تا بتوانید بفهمید چه اتفاقی افتاده که این باگ ایجاد شده است:

$ git bisect good
b047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commit
commit b047b02ea83310a70fd603dc8cd7a6cd13d15c04
Author: PJ Hyett <pjhyett@example.com>
Date:   Tue Jan 27 14:48:32 2009 -0800

    Secure this thing

:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730
f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M  config

وقتی کارتان تمام شد، باید دستور git bisect reset را اجرا کنید تا HEAD به جایی که قبل از شروع بودید بازگردد، وگرنه در وضعیت عجیبی قرار می‌گیرید:

$ git bisect reset

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

$ git bisect start HEAD v1.0
$ git bisect run test-error.sh

با این کار، به طور خودکار اسکریپت test-error.sh روی هر کامیت چک‌اوت شده اجرا می‌شود تا گیت اولین کامیت خراب را پیدا کند. همچنین می‌توانید چیزی مانند make یا make tests یا هر چیزی که برای اجرای تست‌های خودکار دارید را اجرا کنید.

scroll-to-top