Chapters ▾ 2nd Edition

7.1 ابزارهای گیت (Git Tools) - انتخاب بازبینی (Revision Selection)

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

انتخاب بازبینی (Revision Selection)

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

بازبینی‌های تکی (Single Revisions)

واضح است که می‌توانید به هر کامیت تکی با استفاده از هش کامل SHA-1 با ۴۰ کاراکتر آن اشاره کنید، اما راه‌های انسانی‌پسندتری نیز برای اشاره به کامیت‌ها وجود دارد. این بخش روش‌های مختلفی که می‌توانید به هر کامیت اشاره کنید را توضیح می‌دهد.

SHA-1 کوتاه (Short SHA-1)

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

برای مثال، اگر بخواهید کامیت خاصی را که می‌دانید در آن عملکرد خاصی اضافه کرده‌اید بررسی کنید، ابتدا ممکن است فرمان git log را اجرا کنید تا آن کامیت را پیدا کنید:

$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    Fix refs handling, add gc auto, update tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    Add some blame and merge stuff

در این حالت، فرض کنید به کامیتی علاقه‌مندید که هش آن با 1c002dd…​ شروع می‌شود. می‌توانید آن کامیت را با هر یک از نسخه‌های زیر از فرمان git show بررسی کنید (به شرطی که نسخه‌های کوتاه‌تر بدون ابهام باشند):

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d

گیت می‌تواند یک اختصار کوتاه و یکتا برای مقادیر SHA-1 شما پیدا کند. اگر گزینه --abbrev-commit را به دستور git log بدهید، خروجی از مقادیر کوتاه‌تر اما یکتا استفاده خواهد کرد؛ به‌طور پیش‌فرض هفت کاراکتر را به کار می‌گیرد اما در صورت نیاز برای حفظ یکتایی SHA-1 آن‌ها را طولانی‌تر می‌کند.

$ git log --abbrev-commit --pretty=oneline
ca82a6d Change the version number
085bb3b Remove unnecessary test code
a11bef0 Initial commit

به‌طور کلی، هشت تا ده کاراکتر به‌راحتی برای یکتا بودن در یک پروژه کافی است. برای مثال، تا فوریه ۲۰۱۹، هسته لینوکس (که پروژه نسبتاً بزرگی است) بیش از ۸۷۵,۰۰۰ کامیت و نزدیک به هفت میلیون شیء در پایگاه داده اشیاء خود دارد، بدون اینکه دو شیء وجود داشته باشند که SHA-1 آن‌ها در ۱۲ کاراکتر اول یکسان باشد.

یادداشت
A SHORT NOTE ABOUT SHA-1

بسیاری از افراد در مقطعی نگران این می‌شوند که شاید به‌طور تصادفی دو شیء متفاوت در مخزن خود داشته باشند که هش SHA-1 یکسانی داشته باشند. در این صورت چه اتفاقی می‌افتد؟

اگر شما به‌طور اتفاقی شیئی را کامیت کنید که هش SHA-1 آن با شیء متفاوت قبلی در مخزن شما یکسان باشد، گیت شیء قبلی را که در پایگاه داده گیت شما موجود است، می‌بیند، فرض می‌کند که آن قبلاً نوشته شده و به‌سادگی از همان استفاده می‌کند. اگر بعداً بخواهید آن شیء را چک‌اوت کنید، همیشه داده‌های شیء اول را دریافت خواهید کرد.

با این حال، باید بدانید که این سناریو چقدر غیرممکن و بعید است. خلاصه SHA-1 بیست بایت یا ۱۶۰ بیت است. تعداد اشیاء تصادفی لازم برای تضمین احتمال ۵۰٪ برخورد یکسان، تقریباً ۲ به توان ۸۰ است (فرمول تعیین احتمال برخورد p = (n(n-1)/2) * (1/2^{160}) است). ۲ به توان ۸۰ برابر است با ۱.۲ × ۱۰^{۲۴} یا یک میلیون میلیارد میلیارد. این عدد ۱۲۰۰ برابر تعداد دانه‌های شن روی زمین است.

در اینجا مثالی برای درک بهتر از اینکه چه مقدار لازم است تا برخورد SHA-1 رخ دهد آورده شده است. اگر همه ۶.۵ میلیارد انسان روی زمین برنامه‌نویس بودند، و هر ثانیه هر کدام کدی به اندازه کل تاریخچه هسته لینوکس (۶.۵ میلیون شیء گیت) تولید و به یک مخزن عظیم گیت فشار می‌دادند، حدوداً دو سال طول می‌کشید تا آن مخزن به اندازه کافی شیء داشته باشد که احتمال ۵۰٪ برخورد یکسان SHA-1 در آن وجود داشته باشد. بنابراین، برخورد طبیعی SHA-1 کمتر از این است که همه اعضای تیم برنامه‌نویسی شما در یک شب به‌طور جداگانه توسط گرگ‌ها مورد حمله قرار گیرند و کشته شوند.

اگر چند هزار دلار توان محاسباتی صرف کنید، امکان ساخت دو فایل با هش یکسان وجود دارد، همان‌طور که در فوریه ۲۰۱۷ در https://shattered.io/ ثابت شده است. گیت به سمت استفاده از SHA256 به‌عنوان الگوریتم هش پیش‌فرض حرکت می‌کند که بسیار مقاوم‌تر در برابر حملات برخورد است و کدی برای کاهش این حمله دارد (اگرچه نمی‌تواند به‌طور کامل آن را حذف کند).

ارجاعات شاخه‌ها (Branch References)

یک روش ساده برای اشاره به یک کامیت خاص این است که اگر آن کامیت سر شاخه یک شاخه باشد؛ در این صورت می‌توانید به‌سادگی از نام شاخه در هر دستور گیت که انتظار دارد مرجعی به یک کامیت داده شود استفاده کنید. برای مثال، اگر می‌خواهید آخرین شیء کامیت روی یک شاخه را بررسی کنید، دستورات زیر معادل هم هستند، با فرض اینکه شاخه topic1 به کامیت ca82a6d…​ اشاره کند:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

اگر می‌خواهید ببینید شاخه دقیقاً به کدام SHA-1 اشاره می‌کند، یا می‌خواهید ببینید هر یک از این مثال‌ها در واقع به کدام SHA-1 ختم می‌شود، می‌توانید از ابزار داخلی گیت به نام rev-parse استفاده کنید. برای اطلاعات بیشتر درباره ابزارهای داخلی به (Git Internals) مراجعه کنید؛ اساساً، rev-parse برای عملیات سطح پایین‌تر وجود دارد و برای استفاده روزمره طراحی نشده است. با این حال، گاهی اوقات زمانی که نیاز دارید ببینید واقعاً چه اتفاقی می‌افتد، می‌تواند مفید باشد. در اینجا می‌توانید rev-parse را روی شاخه‌تان اجرا کنید.

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

نام‌های کوتاه RefLog (RefLog Shortnames)

یکی از کارهایی که گیت در پس‌زمینه انجام می‌دهد در حالی که شما مشغول کار هستید، نگهداری از "reflog" است - گزارشی از مکان‌های مرجع HEAD و شاخه‌های شما در چند ماه گذشته.

شما می‌توانید با استفاده از git reflog reflog خود را مشاهده کنید:

$ git reflog
734713b HEAD@{0}: commit: Fix refs handling, add gc auto, update tests
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' strategy.
1c002dd HEAD@{2}: commit: Add some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD
 هر بار که نوک شاخه‌تان به هر دلیلی به‌روزرسانی می‌شود، گیت آن اطلاعات را در تاریخچه موقت خود ذخیره می‌کند.
شما می‌توانید از داده‌های reflog خود برای ارجاع به کامیت‌های قدیمی‌تر نیز استفاده کنید.
برای مثال، اگر بخواهید پنجمین مقدار قبلی HEAD مخزن خود را ببینید، می‌توانید از ارجاع `@{5}` که در خروجی reflog مشاهده می‌کنید، استفاده کنید:
$ git show HEAD@{5}

همچنین می‌توانید از این نحو برای مشاهده مکان شاخه در یک زمان مشخص استفاده کنید. برای نمونه، برای دیدن اینکه شاخه master شما دیروز کجا بوده است، می‌توانید تایپ کنید:

$ git show master@{yesterday}

این دستور به شما نشان می‌دهد که نوک شاخه master شما دیروز کجا بوده است. این روش فقط برای داده‌هایی که هنوز در reflog شما موجود هستند کار می‌کند، بنابراین نمی‌توانید از آن برای جستجوی کامیت‌های قدیمی‌تر از چند ماه استفاده کنید.

برای مشاهده اطلاعات reflog به فرمی شبیه خروجی git log، می‌توانید دستور git log -g را اجرا کنید:

$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: Fix refs handling, add gc auto, update tests
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    Fix refs handling, add gc auto, update tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

مهم است بدانید که اطلاعات reflog کاملاً محلی است — این فقط یک لاگ از کاری است که شما در مخزن خودتان انجام داده‌اید. ارجاعات در نسخه کسی دیگر از مخزن مشابه نخواهند بود؛ همچنین، درست بعد از اینکه مخزن را کلون می‌کنید، reflog شما خالی است چون هیچ فعالیتی هنوز در مخزن شما انجام نشده است. اجرای دستور git show HEAD@{2.months.ago} فقط در صورتی کامیت مربوطه را نشان می‌دهد که حداقل دو ماه پیش پروژه را کلون کرده باشید — اگر جدیدتر از آن کلون کرده باشید، فقط اولین کامیت محلی خود را خواهید دید.

نکته
Think of the reflog as Git’s version of shell history

اگر سابقه کار با یونیکس یا لینوکس دارید، می‌توانید reflog را نسخه گیت از تاریخچه شل در نظر بگیرید، که تأکید می‌کند محتوای آن فقط برای شما و «جلسه» شما مرتبط است و هیچ ارتباطی با دیگران که ممکن است روی همان دستگاه کار کنند ندارد.

یادداشت
Escaping braces in PowerShell

زمانی که از پاورشل استفاده می‌کنید، آکولادها مانند { and } کاراکترهای ویژه‌ای هستند و باید فرار داده شوند. می‌توانید آن‌ها را با بک‌تیک ` فرار دهید یا ارجاع کامیت را داخل نقل‌قول قرار دهید:

$ git show HEAD@{0}     # will NOT work
$ git show HEAD@`{0`}   # OK
$ git show "HEAD@{0}"   # OK

ارجاعات اجدادی (Ancestry References)

روش اصلی دیگر برای مشخص کردن یک کامیت، استفاده از اجداد آن است. اگر در انتهای یک مرجع علامت ^ (کرت) قرار دهید، گیت آن را به معنی والد آن کامیت تفسیر می‌کند. فرض کنید تاریخچه پروژه‌تان را نگاه می‌کنید:

$ git log --pretty=format:'%h %s' --graph
* 734713b Fix refs handling, add gc auto, update tests
*   d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd Add some blame and merge stuff
|/
* 1c36188 Ignore *.gem
* 9b29157 Add open3_detach to gemspec file list

سپس می‌توانید با مشخص کردن HEAD^، کامیت قبلی را ببینید که به معنی «والد HEAD» است:

$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'
یادداشت
Escaping the caret on Windows

روی ویندوز در cmd.exe، کاراکتر ^ یک کاراکتر ویژه است و باید به شکل متفاوتی با آن برخورد شود. می‌توانید یا آن را دوبار بنویسید یا رفرنس کامیت را داخل کوتیشن قرار دهید:

$ git show HEAD^     # will NOT work on Windows
$ git show HEAD^^    # OK
$ git show "HEAD^"   # OK

همچنین می‌توانید عددی بعد از ^ قرار دهید تا مشخص کنید کدام والد را می‌خواهید؛ برای مثال، d921970^2 به معنی «والد دوم کامیت d921970» است. این نحو فقط برای کامیت‌های ادغام مفید است، که بیش از یک والد دارند — والد اول یک کامیت ادغام از شاخه‌ای است که هنگام ادغام روی آن بودید (معمولاً master)، در حالی که والد دوم کامیت ادغام از شاخه‌ای است که ادغام شده است (مثلاً topic):

$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    Add some blame and merge stuff

$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date:   Wed Dec 10 22:22:03 2008 +0000

    Some rdoc changes
 تعیین اصلیت دیگر اصلی، علامت `~` (تیدل) است.
این علامت نیز به والد اول اشاره دارد، بنابراین `HEAD~` و `HEAD^` معادل یکدیگرند.
تفاوت زمانی آشکار می‌شود که عددی را مشخص کنید.
`HEAD~2` به معنی «والد اول والد اول» یا «پدربزرگ» است — یعنی به تعداد مشخص شده، والد اول را دنبال می‌کند.
برای مثال، در تاریخچه‌ای که قبلاً ذکر شد، `HEAD~3` به این شکل خواهد بود:
$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    Ignore *.gem

این را می‌توان به صورت HEAD~ نیز نوشت که باز هم به معنای والد اول والد اول والد اول است:

$ git show HEAD~~~
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    Ignore *.gem

شما همچنین می‌توانید این نحوها را ترکیب کنید — مثلاً برای گرفتن والد دوم مرجع قبلی (فرض کنید یک کامیت ادغام باشد)، می‌توانید از HEAD~3^2 استفاده کنید، و به همین ترتیب.

بازه‌های کامیت (Commit Ranges)

حال که می‌توانید کامیت‌های تکی را مشخص کنید، بیایید ببینیم چگونه می‌توان بازه‌ای از کامیت‌ها را تعیین کرد. این موضوع به خصوص برای مدیریت شاخه‌ها بسیار مفید است — اگر شاخه‌های زیادی داشته باشید، می‌توانید با تعیین بازه‌ها به سوالاتی مانند «چه کارهایی روی این شاخه انجام شده که هنوز به شاخه اصلی من ادغام نشده‌اند؟» پاسخ دهید.

دو نقطه (Double Dot)

رایج‌ترین نحو تعیین بازه، نحو دو نقطه دوگانه است. این در واقع از گیت می‌خواهد بازه‌ای از کامیت‌ها را که از یک کامیت قابل دسترسی‌اند اما از کامیت دیگر قابل دسترسی نیستند، مشخص کند. برای مثال، فرض کنید تاریخچه کامیت شما به شکل Example history for range selection است. فرض کنید می‌خواهید ببینید روی شاخه experiment چه چیزهایی هست که هنوز به شاخه master ادغام نشده‌اند.

Example history for range selection
نمودار 136. Example history for range selection

می‌توانید از گیت بخواهید فقط آن کامیت‌ها را با دستور master..experiment نمایش دهد — یعنی «همه کامیت‌هایی که از experiment قابل دسترسی‌اند ولی از master نیستند.» برای اختصار و وضوح در این مثال‌ها، حروف اشیای کامیت از نمودار به جای خروجی واقعی لاگ به ترتیب نمایش استفاده شده‌اند:

$ git log master..experiment
D
C

اگر بخواهید برعکس آن را ببینید — یعنی همه کامیت‌های موجود در master که در experiment نیستند — می‌توانید نام شاخه‌ها را معکوس کنید. experiment..master همه چیز در master را نشان می‌دهد که از experiment قابل دسترسی نیست:

$ git log experiment..master
F
E
 این روش زمانی مفید است که بخواهید شاخه‌ی `experiment` را به‌روز نگه دارید و پیش‌نمایشی از آنچه می‌خواهید ادغام کنید داشته باشید.
یکی دیگر از کاربردهای رایج این نحو، دیدن آن چیزی است که قصد دارید به مخزن راه دور ارسال کنید:
$ git log origin/master..HEAD

این فرمان هر کامیتی را که در شاخه‌ی فعلی شما وجود دارد ولی در شاخه‌ی master در مخزن راه دور origin نیست، نمایش می‌دهد. اگر دستور git push را اجرا کنید و شاخه‌ی فعلی شما در حال دنبال کردن origin/master باشد، کامیت‌هایی که توسط git log origin/master..HEAD فهرست شده‌اند، کامیت‌هایی هستند که به سرور منتقل خواهند شد. همچنین می‌توانید یکی از طرفین نحو را حذف کنید تا Git به طور پیش‌فرض HEAD را فرض کند. برای مثال، می‌توانید همان نتایج مثال قبلی را با تایپ git log origin/master.. به دست آورید — اگر یکی از طرف‌ها حذف شود، Git به جای آن HEAD را جایگزین می‌کند.

چند نقطه (Multiple Points)

نحو دو نقطه به عنوان یک روش کوتاه مفید است، اما ممکن است بخواهید بیش از دو شاخه برای مشخص کردن بازنگری خود تعیین کنید، مثلاً ببینید چه کامیت‌هایی در هر یک از چند شاخه وجود دارد که در شاخه‌ای که اکنون روی آن هستید نیست. Git به شما اجازه می‌دهد این کار را با استفاده از کاراکتر ^ یا --not قبل از هر مرجعی که نمی‌خواهید کامیت‌های قابل دسترسی از آن را ببینید انجام دهید. بنابراین، سه دستور زیر معادل یکدیگر هستند:

$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA

این روش خوب است چون با این نحو می‌توانید بیش از دو مرجع را در جستجوی خود مشخص کنید، کاری که با نحو دو نقطه نمی‌توانید انجام دهید. برای نمونه، اگر بخواهید همه‌ی کامیت‌هایی که از refA یا refB قابل دسترسی هستند ولی از refC نیستند را ببینید، می‌توانید از یکی از دستورات زیر استفاده کنید:

$ git log refA refB ^refC
$ git log refA refB --not refC

این سیستم پرس‌وجوی بازنگری بسیار قدرتمندی ایجاد می‌کند که به شما کمک می‌کند بفهمید چه چیزهایی در شاخه‌های شما وجود دارد.

سه نقطه (Triple Dot)

آخرین نحو اصلی انتخاب بازه، نحو سه نقطه است که همه‌ی کامیت‌هایی را مشخص می‌کند که توسط هر یک از دو مرجع قابل دسترسی هستند اما توسط هر دوی آن‌ها نیستند. به تاریخچه‌ی کامیت مثال در Example history for range selection نگاه کنید. اگر بخواهید ببینید چه چیزهایی در master یا experiment وجود دارد ولی در مرجع‌های مشترک نیست، می‌توانید این دستور را اجرا کنید:

$ git log master...experiment
F
E
D
C

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

یکی از گزینه‌های رایج برای استفاده با دستور log در این حالت، --left-right است که نشان می‌دهد هر کامیت در کدام طرف بازه قرار دارد. این باعث می‌شود خروجی مفیدتر شود:

$ git log --left-right master...experiment
< F
< E
> D
> C

با این ابزارها، می‌توانید بسیار راحت‌تر به Git بگویید که کدام کامیت یا کامیت‌ها را می‌خواهید بررسی کنید.

scroll-to-top