🧑‍💻 Git & Diff Tools

تولید و اعمال تغییرات با diff و patch

چگونه تغییرات بین دو فایل را ذخیره و به دیگران منتقل کنیم و سپس آن تغییرات را روی فایل اصلی اعمال کنیم

📂 diff • patch • redirect • standard input • unified format

🗺️ مسیر یادگیری تولید و اعمال تغییرات

  1. 🐛 مشکل: ارسال کد باگ‌دار و توضیح شفاهی
  2. 📄 راه‌حل: ارسال diff به جای توضیح
  3. 🛠️ تولید فایل diff با دستور diff -u
  4. 📥 دریافت diff و اعمال دستی vs خودکار
  5. 🧩 معرفی دستور patch
  6. 📂 مثال واقعی: اسکریپت cpu_usage.py
  7. 🔍 بررسی diff ارسالی همکار
  8. 📖 درک سرآیند قطعه در خروجی unified
  9. ⚙️ اعمال patch با دستور patch
  10. ❓ چرا diff بفرستیم به جای کل فایل؟

🐛 گام ۱: مشکل – ارسال کد باگ‌دار و توضیح شفاهی

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

مثلاً می‌توانی بگویی: «تو فقط درون توابع می‌توانی مقدار برگردانی، فکر کنم منظورت استفاده از sys.exit بوده. همچنین داری دو بار به گیگابایت تبدیل می‌کنی، بنابراین اسکریپت همیشه خطا می‌دهد.»

اما اگر کد پیچیده باشد، این توضیحات شفاهی ممکن است برای همکارت به سختی قابل درک باشد. او ممکن است نفهمد دقیقاً کدام خط باید تغییر کند یا تغییرات چگونه باید اعمال شوند.

💡 توضیح شفاهی تغییرات، مخصوصاً در کدهای طولانی یا پیچیده، معمولاً کافی نیست و ممکن است باعث سردرگمی شود.

📄 گام ۲: راه‌حل – ارسال diff به جای توضیح

برای اینکه تغییرات را به‌وضوح نشان دهی، می‌توانی یک فایل diff از تغییراتی که باید اعمال شوند، برای همکارت بفرستی. به این ترتیب او دقیقاً می‌بیند که کدام خط‌ها تغییر کرده‌اند و کد نهایی چه شکلی خواهد بود.

استفاده از diff مزیت بزرگی دارد: ابهام را از بین می‌برد و همکارت بدون نیاز به حدس زدن، می‌تواند تغییرات را اعمال کند.

ارسال diff برای تغییرات، روش استاندارد در بسیاری از تیم‌های توسعه نرم‌افزار است، زیرا دقیق‌ترین و کم‌خطاترین راه برای انتقال اصلاحات است.

🛠️ گام ۳: تولید فایل diff با دستور diff -u

برای تولید فایل diff از دو نسخه (قدیمی و جدید) از دستور زیر استفاده می‌کنیم:

$ diff -u old_file new_file > change.diff

در این دستور:

این فایل معمولاً فایل diff یا فایل patch نامیده می‌شود و شامل همه تغییرات بین فایل قدیم و جدید، به علاوه زمینه لازم برای درک و اعمال تغییرات است.

استفاده از پرچم -u باعث می‌شود فردی که فایل را می‌خواند، درک بهتری از تغییرات داشته باشد، چون چند خط قبل و بعد از تغییرات را هم می‌بیند.

📥 گام ۴: دریافت diff و اعمال دستی vs خودکار

حالا فرض کن خودت یک فایل diff از همکارت دریافت کرده‌ای و می‌خواهی تغییرات را روی اسکریپت خودت اعمال کنی.

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

خوشبختانه این فرایند را می‌توان خودکار کرد. ابزاری به نام patch وجود دارد که دقیقاً برای همین کار طراحی شده است.

patch یک دستور خط فرمان است که فایل تولید شده توسط diff را می‌گیرد و تغییرات را روی فایل اصلی اعمال می‌کند.

🧩 گام ۵: معرفی دستور patch

دستور patch دقیقاً کاری را انجام می‌دهد که از نامش پیداست: یک فایل diff را می‌گیرد و تغییرات را روی فایل یا فایل‌های مربوطه اعمال می‌کند.

نحوه استفاده از patch به این صورت است که نام فایل مورد نظر برای اصلاح را به عنوان اولین پارامتر به آن می‌دهیم و محتوای فایل diff را از طریق ورودی استاندارد (standard input) به آن می‌فرستیم.

برای هدایت محتوای فایل diff به ورودی استاندارد، از علامت < (کوچک‌تر) استفاده می‌کنیم:

$ patch cpu_usage.py < cpu_usage.diff

با اجرای این دستور، patch تغییرات موجود در فایل cpu_usage.diff را روی فایل cpu_usage.py اعمال می‌کند.

علامت < محتویات فایل سمت راست را به عنوان ورودی استاندارد به دستور سمت چپ می‌دهد.

📂 گام ۶: مثال واقعی – اسکریپت cpu_usage.py

بیایید یک مثال عملی را با هم مرور کنیم. فرض کن اسکریپت کوچکی داریم که بررسی می‌کند آیا کامپیوتر بیش از حد تحت فشار (overloaded) است یا نه. کد آن به این شکل است:

$ cat cpu_usage.py
#!/usr/bin/env python3

import psutil

def check_cpu_usage(percent):
    usage = psutil.cpu_percent()
    return usage < percent

if not check_cpu_usage(75):
    print("ERROR! CPU is overloaded")
else:
    print("Everything ok")

این اسکریپت از ماژول psutil برای دریافت درصد استفاده از CPU استفاده می‌کند. اگر بار (load) از آستانه ۷۵ درصد بیشتر باشد، پیام خطا چاپ می‌شود، در غیر این صورت پیام «همه چیز خوب است» را نشان می‌دهد.

اما همکارت به تو می‌گوید که این اسکریپت به‌درستی کار نمی‌کند. حتی اگر کامپیوتر کاملاً سنگین باشد، اسکریپت باز هم می‌گوید «همه چیز خوب است». چه مشکلی وجود دارد؟

مشکل این است که تابع psutil.cpu_percent() بدون پارامتر، مقدار استفاده از CPU را در لحظه برمی‌گرداند و نه میانگین یک بازه زمانی. در بسیاری از سیستم‌ها، این مقدار ممکن است همیشه صفر باشد، بنابراین اسکریپت همیشه شرط را درست (True) در نظر می‌گیرد و خطا نمی‌دهد.

⚠️ تابع cpu_percent() بدون پارامتر، معمولاً مقدار لحظه‌ای را نشان می‌دهد که می‌تواند گمراه‌کننده باشد. برای گرفتن میانگین، باید یک بازه زمانی (به ثانیه) به آن بدهیم.

🔍 گام ۷: بررسی diff ارسالی همکار

همکار مهربانت یک فایل diff برایت فرستاده است که مشکل را برطرف می‌کند. بیایید محتوای آن را ببینیم:

$ cat cpu_usage.diff
--- cpu_usage.py	2019-06-23 08:16:04.666457429 -0700
+++ cpu_usage_fixed.py	2019-06-23 08:15:37.534370071 -0700
@@ -2,7 +2,8 @@
 import psutil

 def check_cpu_usage(percent):
-    usage = psutil.cpu_percent()
+    usage = psutil.cpu_percent(1)
+    print("DEBUG: usage: {}".format(usage))
     return usage < percent

 if not check_cpu_usage(75):

همان‌طور که می‌بینی، همکارت دو تغییر ایجاد کرده است:

توضیح همکارت: «وقتی cpu_percent را بدون پارامتر صدا می‌زنیم، میانگین یک بازه زمانی گرفته نمی‌شود و بنابراین همیشه صفر برمی‌گرداند.»

اضافه کردن خط دیباگ، یک روش رایج برای فهمیدن مقدار واقعی متغیرها در حین اجراست و به عیب‌یابی کمک می‌کند.

📖 گام ۸: درک سرآیند قطعه در خروجی unified

اگر به خط دوم خروجی diff دقت کنی، یک خط عجیب با علامت‌های @@ می‌بینی. بیایید این خط را تکه‌تکه باز کنیم و بفهمیم دقیقاً به ما چه می‌گوید.

@@ -2,7 +2,8 @@

این خط، سرآیند قطعه (chunk header) یا شماره‌گذار خطوط است و به ما می‌گوید که تغییرات دقیقاً در کجای فایل‌ها اتفاق افتاده‌اند. بیایید هر بخش را جداگانه بررسی کنیم:

🧩 بخش اول: -2,7 (فایل قدیمی)

🧩 بخش دوم: +2,8 (فایل جدید)

🧩 بخش سوم: @@ و فضای آخر

@@ -2,7 +2,8 @@ def check_cpu_usage(percent):

این سرآیند دقیقاً به ابزار patch می‌گوید که کجا و چقدر از فایل را باید تغییر دهد، بدون اینکه نیاز باشد کل فایل را اسکن کند.

📌 چرا این اعداد مهم هستند؟

۱. جبران جابه‌جایی خطوط (Offset): وقتی در بخش‌های قبلی کد، خطی اضافه یا حذف می‌شود، شماره خطوط بعدی به همان تعداد جلو یا عقب می‌روند. patch با دیدن این اعداد، دقیقاً می‌فهمد که تغییر باید روی خط درست اعمال شود.

۲. اطلاع از تعداد خطوط: اعداد ۷ و ۸ نشان می‌دهند که در فایل قدیمی، یک بلوک ۷ خطی با یک بلوک ۸ خطی در فایل جدید جایگزین شده است. یعنی یک خط جدید به کد اضافه شده است.

فرض کن دو نسخه از یک لیست خرید داری:

نسخه قدیم:
۱. شیر
۲. نان
۳. پنیر

نسخه جدید:
۱. شیر
۲. نان
۳. کره
۴. پنیر

اگر diff -u بگیری، سرآیند می‌گوید: @@ -2,2 +2,3 @@
یعنی: در نسخه قدیم از خط ۲، ۲ خط (نان و پنیر) و در نسخه جدید از خط ۲، ۳ خط (نان، کره، پنیر) در این تغییر نقش دارند.

⚙️ گام ۹: اعمال patch با دستور patch

حالا که فایل diff را داریم، می‌خواهیم تغییرات را روی اسکریپت خودمان اعمال کنیم. برای این کار از دستور patch استفاده می‌کنیم:

$ patch cpu_usage.py < cpu_usage.diff
patching file cpu_usage.py

با اجرای این دستور، patch پیام «patching file» را نشان می‌دهد که یعنی تغییرات با موفقیت اعمال شده‌اند. حالا بیایید محتوای فایل را دوباره بررسی کنیم تا مطمئن شویم:

$ cat cpu_usage.py
#!/usr/bin/env python3

import psutil

def check_cpu_usage(percent):
    usage = psutil.cpu_percent(1)
    print("DEBUG: usage: {}".format(usage))
    return usage < percent

if not check_cpu_usage(75):
    print("ERROR! CPU is overloaded")
else:
    print("Everything ok")

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

✅ با استفاده از patch، تغییرات را به‌سرعت و بدون خطا روی فایل اصلی اعمال کردیم.

❓ گام ۱۰: چرا diff بفرستیم به جای کل فایل؟

شاید بپرسی: «به جای این همه زحمت diff و patch، چرا کل فایل را نمی‌فرستیم؟» جواب این سؤال در دو نکته کلیدی خلاصه می‌شود:

دلیل اول: کد اصلی ممکن است تغییر کرده باشد

ممکن است همکارت فایل را از روی نسخه‌ای که با آخرین نسخه‌ات تفاوت دارد، اصلاح کرده باشد. با ارسال diff، او دقیقاً مشخص می‌کند که کدام خط‌ها را تغییر داده، صرف‌نظر از اینکه نسخه‌اش با تو یکی باشد یا نه.

دستور patch هوشمند است و اگر فایل اصلی تغییراتی داشته باشد، باز هم سعی می‌کند تا حد امکان diff را اعمال کند. البته همیشه موفق نمی‌شود، اما در بسیاری از موارد جواب می‌دهد.

دلیل دوم: ساختار و پروژه‌های بزرگ

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

با diff کل پوشه‌ها می‌توانی یک فایل diff تولید کنی که خودش به‌طور خودکار مسیر هر فایل تغییر یافته را مشخص می‌کند. به این ترتیب نیازی به جابه‌جایی دستی فایل‌ها نخواهی داشت.

استفاده از diff و patch به ما امکان می‌دهد تغییرات را دقیق، قابل‌فهم و مستقل از نسخه فعلی منتقل کنیم و در پروژه‌های بزرگ، مدیریت ساختار را آسان‌تر می‌کند.

عالی! حالا می‌دانیم چطور فایل‌های diff تولید کنیم، سرآیند آن را تفسیر کنیم و با دستور patch تغییرات را اعمال کنیم. در ویدیوی بعدی، این ابزارها را در یک مثال واقعی کنار هم خواهیم دید.

🎉 با اتمام این جلسه، شما با ابزارهای diff و patch برای تولید و اعمال دقیق تغییرات آشنا شدید.

🚀 رفتن به جلسه‌ی بعدی 🚀

🌐 با ما همراه باشید

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