آموزش گیت – قسمت هفتم

نویسنده : سید ایوب کوکبی ۲۳ اردیبهشت ۱۳۹۸
آموزش گیت - قسمت هفتم

در قسمت قبلی آموزش گیت تا حدودی با rebasing برای جابه‌جا کردن کامیت‌ها و ویرایش اولیۀ آن‌ها آشنا شدید. در این قسمت کارهای سطح بالاتری را مثل تقسیم کامیت‌ها، بازیابی اسنپ‌شات‌های حذف شده و نهایتاً بازنویسی کامل تاریخچۀ مخزن توضیح می‌دهیم.

ساخت یک صفحۀ جدید

ابتدا شاخۀ جدیدی می‌سازیم تا چند صفحۀ HTML اضافه کنیم:

git checkout -b new-pages
git branch

توجه کنید که ساخت شاخۀ جدید و چک‌اوت کردن آن را همزمان با استفاده از پرچم b- انجام داده‌ایم. حالا در شاخۀ جدید هستیم. فایل red.html را با محتوای زیر در ریشۀ فولدر پروژه ایجاد کنید:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>The Red Page</title>
  <link rel="stylesheet" href="style.css" />
  <meta charset="utf-8" />
</head>
<body>
  <h1 style="color: #C00">The Red Page</h1>
  <p>Red is the color of <span style="color: #C00">passion</span>!</p>
    
  <p><a href="index.html">Return to home page</a></p>
</body>
</html>

فعلا تغییرات را کامیت نمی‌کنیم.

ساخت یک صفحۀ دیگر

فایل دیگری به نام yellow.html بسازید و محتوای زیر را در آن قرار دهید:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>The Yellow Page</title>
  <link rel="stylesheet" href="style.css" />
  <meta charset="utf-8" />
</head>
<body>
  <h1 style="color: #FF0">The Yellow Page</h1>
  <p>Yellow is the color of <span style="color: #FF0">the sun</span>!</p>
    
  <p><a href="index.html">Return to home page</a></p>
</body>
</html>

لینک‌سازی و کامیت کردن صفحات جدید

در صفحۀ ایندکس به دو صفحه‌ای که ساخته‌ایم لینک می‌کنیم:

<li style="color: #C00">
  <a href="red.html">The Red Page</a>
</li>
<li style="color: #FF0">
  <a href="yellow.html">The Yellow Page</a>
</li>

حالا همۀ این تغییرات را داخل یک اسنپ‌شات، کامیت می‌کنیم:

git add red.html yellow.html index.html
git status
git commit -m "Add new HTML pages"

این مثالی از یک کامیت نامناسب بود. چند کار نامرتبط با را داخل یک کامیت با پیامی عمومی قرار داده‌ایم که اشتباه است. تا الان توضیح نداده‌ایم که چه زمانی باید کامیت بگیریم ولی قواعد کلی شبیه همان اصولی است که برای ساخت شاخه‌ها بیان کردیم:

  • برای هر تغییر چشمگیری، کامیت جدید بسازید؛
  • زمانی که نمی‌توانید برای یک کامیت پیام مناسبی انتخاب کنید، آن اسنپ‌شات را کامیت نکنید.

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

ساخت و کامیت یک صفحۀ جدید

قبل از تقسیم کامیت نامناسب، یک صفحۀ دیگر می‌سازیم. اسمش را green.html می‌گذاریم با این محتوا:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>The Green Page</title>
  <link rel="stylesheet" href="style.css" />
<meta charset="utf-8" />
</head>
<body>
  <h1 style="color: #0C0">The Green Page</h1>
  <p><span style="color: #0C0">Green</span> is the color of earth.</p>
    
  <p><a href="index.html">Return to home page</a></p>
</body>
</html>

لینک صفحۀ green را به ایندکس اضافه می‌کنیم:

<li style="color: #0C0">
  <a href="green.html">The Green Page</a>
</li>

نهایتاً تغییرات را استیج و کامیت می‌کنیم:

git add green.html index.html
git status
git commit -m "Add green page"

انجام یک Interactive Rebase

کامیت‌های ساخته شده در شاخۀ new-pages به شرح ذیل است:

۴c3027c Add green page
db96c72 Add new HTML pages

ولی ما می‌خواهیم کامیت‌های به شکل زیر درآید:

۴c3027c Add green page
۹b1a64f Add yellow page
۷۷a1cf1 Add red page

برای حصول این خواسته می‌توانیم از همان interactive rebase که در درس قبلی به آن پرداختیم استفاده کنیم. ولی این بار در میانۀ عملیات rebase یک کامیت جدید می‌سازیم.

git rebase -i master

لیست خروجی این دستور را به شکل زیر تغییر دهید، سپس تغییرات را ذخیره و خارج شوید (برای آشنایی اولیه با ادیتور vim و نحوۀ ذخیره و خروج به درس قبل مراجعه کنید).

edit db96c72 Add new HTML pages
pick 4c3027c Add green page

Undo کردن کامیت عمومی

ابتدا لاگ می‌گیریم (git log -oneline) تا بفهمیم کجا هستیم:

db96c72 Add new HTML pages
۷۰۷۰b0e Add link to about section in home page
...

وقتی گیت به دستور edit در تنظیمات rebase برخورد می‌کند توقف می‌کند تا بتوانیم آن کامیت را ویرایش کنیم. به همین خاطر کامیت green را هنوز در گزارش مشاهده نمی‌کنیم. این بار به جای اصلاح کامیت جاری (commit amending)، آن را کاملاً حذف می‌کنیم:

git reset --mixed HEAD~1
git log --oneline
git status

دستور git reset همانطور که در درس‌هایی قبلی به آن پرداختیم، اسنپ‌شات چک‌اوت شده را به کامیت جدید منتقل می‌کند. پارامتر Head~1 دستور می‌دهد که به کامیت قبل از Head ریست کند. (Head~2 به دو کامیت قبل از Head اشاره دارد). در مثال ما کامیت قبلی با master مصادف می‌شود شبیه تصویر پایین:

عمل ریست در گیت
ریست کردن به HEAD~1

احتمالاً از درس‌های قبلی به یاد دارید که برای برگرداندن تغییرات از gir reset –hard استفاده می‌کردیم. دقت کنید اینجا از پرچم hard– استفاده نکردیم چون استفاده از آن باعث می‌شود working directory نیز دقیقاً مانند کامیتی شود که به آن ریست می‌کنیم؛ این یعنی از دست دادن تمام تغییرات کامیت نشده که شامل فایل‌های yellow و red ما نیز می‌شود.

بخوانید  آمار جالبی از موبایل در سال 2018

برای حل این مشکل از فلگ mixed– استفاده می‌کنیم. استفاده از این فلگ به گیت می‌گوید که فقط Head را جابه‌جا کن و کاری به محتویات Working directory نداشته باش. این کار باعث می‌شود تغییرات کامیت نشده‌ای در مخزن پدیدار شود. ما اکنون فرصت افزودن فایل‌های red.html و yellow.html را در دو کامیت مجزا داریم.

تقسیم کامیت عمومی

با صفحۀ red.html شروع می‌کنیم. از آنجایی که فقط می‌خواهیم تغییرات مرتبط با این صفحه را در کامیت قرار دهیم. لینک صفحۀ yellow را از صفحۀ ایندکس حذف می‌کنیم. به صورت زیر:

<h2>Navigation</h2>
<ul>
  <li>
    <a href="about/index.html">About Us</a>
  </li>
  <li style="color: #F90">
    <a href="orange.html">The Orange Page</a>
  </li>
  <li style="color: #00F">
    <a href="blue.html">The Blue Page</a>
  </li>
  <li>
<a href="rainbow.html">The Rainbow Page</a>
  </li>
  <li style="color: #C00">
    <a href="red.html">The Red Page</a>
  </li>
</ul>

آپدیت انجام شده را با یک پیام مناسب کامیت می‌کنیم:

git add red.html index.html
git status
git commit -m "Add red page"

مرحلۀ بعدی اضافه کردن کامیت صفحۀ yellow است. به صفحۀ ایندکس بروید و لینک صفحۀ Yellow را مجددا به فایل اضافه کنید:

<li style="color: #FF0">
  <a href="yellow.html">The Yellow Page</a>
</li>

حالا تغییرات را استیج و کامیت کنید:

git add yellow.html index.html
git status
git commit -m "Add yellow page"

با موفقیت توانستیم کامیت عمومی را به دو کامیت مجزا بشکنیم. تصویر نهایی به شکل زیر خواهد بود:

ساخت دو کامیت جدید در اثنای عملیات rebase

اما همانطور که می‌بینید، Head (دایره قرمز) در کامیت آخر شاخۀ new-pages قرار نگرفته چون هنوز عمل Rebase تمام نشده است. ادامۀ عملیات Rebase را برای افزودن صفحۀ green وارد کنید:

git rebase --continue

به طور خلاصه، ما یک کامیت نامناسب را با دستور git reset از شاخۀ جاری حذف کردیم و با فلگ mixed– فایل‌های پروژه را دست‌نخورده باقی گذاشتیم. سپس با git add و git commit تغییرات مرتبط را در دو کامیت مجزا به مخزن اضافه کردیم. نکتۀ اصلی اینجاست که طی عملیات Rebase می‌توانید کامیت‌های جدید اضافه کنید، آن‌ها را ویرایش یا حذف کنید و سپس شاخه را به base جدید منتقل نمایید.

بخوانید  پیغام خطا را اینطور بنویسید. (11 نکته برای نوشتن پیغام خطای کاربرپسند)

حذف آخرین کامیت

فرض کنید به صورت اتفاقی کامیت مربوط به صفحۀ green یعنی آخرین کامیت شاخۀ new-pages را حذف کرده‌اید. برای شبیه‌سازی این واقعه دستورات زیر را وارد می‌کنیم:

git reset --hard HEAD~1
git status
git log --oneline

بعد از اجرای دستورات فوق، کامت چک‌اوت شده به اسنپ‌شات قبلی برمی‌گردد. اکنون اگر git status بزنیم هیچ تغییری برای کامیت گرفتن وجود ندارد چون از فلگ hard– استفاده کرده‌ایم و هر تغییری در working directory از دست رفته است. در خروجی git log نیز اثری از کامیت green وجود ندارد. حال چگونه این کامیت را بازیابی کنیم؟

حذف آخرین کامیت

کامیتی که به این شکل حذف شده در گیت اصطلاحاً dangling commit گفته می‌شود. یعنی یک کامیت سرگردان است که متعلق به هیچ شاخه‌ای نیست. این کامیت‌ها در معرض نابودی هستند.

باز کردن Reflog

گیت از چیری به نام reflog برای ثبت تمام تغییرات اعمال شده روی مخزن استفاده می‌کند. دستور git reflog را می‌زنیم تا خروجی آن را مشاهده کنیم:

git reflog

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

۹b1a64f HEAD@{0}: reset: moving to HEAD~1
۰۰۲۱۸۵c HEAD@{1}: rebase -i (finish): returning to refs/heads/new-pages
۰۰۲۱۸۵c HEAD@{2}: rebase -i (pick): Add green page
۹b1a64f HEAD@{3}: commit: Add yellow page
۷۷a1cf1 HEAD@{4}: commit: Add red page
۷۰۷۰b0e HEAD@{5}: reset: moving to HEAD~1
...

همانطور که می‌بینید، چند دستور آخر که وارد کرده‌ایم اینجا ثبت شده است. برای مثال هد جاری با {Head@{0 نمایش داده شده است. دلیلش این است که در آخرین دستور، reset کرده بودیم به Head~1. به همین ترتیب، Yellow page در هد سوم اعمال شده است. رفلاگ، دستورات وارد شده را صرفنظر از اینکه در چه شاخه‌ای هستیم به ترتیبی که وارد شده نمایش می‌دهد.

بازیابی کامیت حذف شده

در ابتدای هر خط در رفلاگ، Commit ID مشخص شده است. همانطور که می‌بینید کامیت green در {Head@{2 توسط دستور rebase اضافه شده است (با دستورالعمل pick). آی.دی فوق را چک‌اوت کنید:

git checkout 002185c

این دستور ما در در حالت detached Head قرار می‌دهد یعنی هد دیگر در نوک شاخه قرار ندارد. در واقع درست برعکس عمل Undo داریم عمل می‌کنیم. آنجا به کامیت قبلی برمی‌گشتیم. اینجا دنبال کامیت بعدی هستیم. در هر دو حالت وضعیت به detached HEAD تغییر می‌کند.

چک‌اوت کردن dangling commit

برای تبدیل dangling commit به یک شاخه، کافی است تا شاخۀ جدیدی بسازید:

git checkout -b green-page

ما اکنون شاخه‌ای داریم که به راحتی می‌توانیم با پروژه ادغام کنیم:

ساخت یک شاخه از dangling commit

دیاگرام بالا به خوبی نشان می‌دهد که شاخۀ green-page در امتداد شاخۀ new-pages قرار گرفته است.

فیلتر کردن لاگ

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

git log new-pages..green-page

این دستور تمام کامیت‌هایی که در green-page هست ولی در new-pages نیست را نمایش می‌دهد. دستور بالا به ما نشان می‌دهد که green-page یک اسنپ‌شات بیشتر از new-pages دارد: یعنی همان dangling commit. (که در حال حاضر dangling commit نیست چون یک شاخه برایش ساخته‌ایم).

همچنین می‌توانید خروجی دستور لاگ را محدود کنید. مثلاً برای نمایش ۴ کامیت آخر در شاخۀ جاری دستور زیر را وارد کنید:

git log HEAD~4..HEAD

با این حال برای چنین کار رایج و مرسومی استفاده از دستور بالا کمی خسته‌کننده است. گیت برای سادگی کار فلگ n- را معرفی کرده است. یعنی به جای دستور بالا می‌توانیم از این یکی استفاده کنیم:

git log -n 4

پارامتر ۴ n- به گیت می‌گوید فقط ۴ کامیت آخراز هد فعلی را نمایش بده. به همین صورت می‌توانیم ۳ n- برای نمایش سه کامیت، ۲ n- برای نمایش ۲ کامیت و ۱ n- را برای نمایش یک کامیت به کار بریم. این دستور برای مواقعی که تاریخچه طولانی بوده و در یک صفحه جا نمی‌شود، کاربرد فراوانی دارد.

بخوانید  آموزش گیت - قسمت اول

ادغام شاخۀ احیاء شده

پس از احیاء کامیت حذف شده، زمان آن رسیده تا تغییرات را با شاخۀ مستر ادغام کنیم. قبل از این کار ببینیم دقیقاً چه چیزی را می‌خواهیم ادغام کنیم:

git checkout master
git log HEAD..green-page --stat

دستور git log Head..green-page، فقط آن کامیت‌هایی از green-page را نشان می‌دهد که در شاخۀ مستر نباشد. (بنابراین چون مستر شاخۀ جاری است می‌توانیم با Head به آن اشاره کنیم). فلگ stat– اطلاعاتی از آخرین فایل‌های تغییر کرده در آخرین کامیت را به ما نشان می‌دهد. مثلاً آخرین کامیت می‌گوید ۱۴ خط به فایل green.html اضافه شده و ۳ خط نیز به فایل index.html افزوده‌ایم:

commit 002185c71e6674915eb75be2afb4ca52c2c7fd1b
Author: Ryan <ryan.example@rypress.com>
Date:   Wed Jan 11 06:49:50 2012 -0600
Add green page

 green.html |   14 ++++++++++++++
 index.html |    3 +++
 ۲ files changed, 17 insertions(+), 0 deletions(-)

در صورتی که ندانیم در کامیت جدید چه اتفاقی افتاده، دستور لاگ به ما نشان می‌دهد که چه کارهایی انجام شده و باید دنبال کدام فایل‌ها باشیم. اما در اینجا می‌خواهیم همۀ تغییرات green-page را در شاخۀ مستر ادغام کنیم.

git merge green-page

دیاگرام پایین، وضعیت تاریخچه را بعد از عمل merge نشان می‌دهد:

Fast-forward شدن شاخۀ مستر به شاخۀ green-page

توجه کنید که شاخۀ green-page از قبل حامل تمام تاریخچۀ new-pages هست به همین خاطر آن را ادغام کرده‌ایم. اگر ادغام دو شاخه باعث از دست رفتن تغییراتی شود گیت حتماً به ما اعلام می‌کند.

نهایتاً شاخه‌های اضافه را حذف می‌کنیم:

git branch -d new-pages
git branch -d green-page

جمع‌بندی

در این درس نگاهی عمیقی به دستور rebase, reset و reflog انداختیم. یاد گرفتیم چگونه یک کامیت را به دو کامیت مستقل بشکنیم و چطور کامیت‌های حذف شده را احیاء کنیم. این توضیحات درک شما را از تعامل میان working directory، stage, branch و committed snapshot افزایش می‌دهد. بعلاوه با آپشن‌های جدیدی برای نمایش بهتر تاریخچۀ کامیت‌ها آشنا شدیم که در پروژه‌های بزرگ واقعاً ضروری است. کارهای زیادی با شاخه‌ها انجام دادیم. فهمیدیم که شاخه‌ در گیت فقط یک اشاره‌گر به یک کامیت مشخص است و این طور نیست که مثل یک ظرف تعدادی کامیت در آن ریخته باشیم. تصویر زیر منظور ما را بهتر می‌رساند:

شاخه، اشاره‌گری به کامیت است نه ظرفی حامل کامیت‌ها

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

چکیدۀ دستورات

  • git reflog : نمایش تاریخچۀ عملیات انجام شده روی مخزن لوکال؛
  • <git reset –mixed HEAD~<n : جابه‌جا کردن HEAD به n کامیت قبلی بدون دست زدن به working directory؛
  • <git reset –hard HEAD~<n : جابه‌جا کردن HEAD به n کامیت قبلی و البته تغییر working directory؛
  • <git log <since>..<until : نمایش آن دسته از کامیت‌های until که در since وجود ندارد. پارامترها می‌توانند ID کامیت یا نام شاخه نیز باشد؛
  • git log –stat : نمایش اطلاعات اضافه دربارۀ فایل‌های ویرایش شده در خروجی لاگ.

سید ایوب کوکبی

نویسنده و مترجم...

0 دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *