چگونه تغییرات بین دو فایل را ذخیره و به دیگران منتقل کنیم و سپس آن تغییرات را روی فایل اصلی اعمال کنیم
تصور کن یک همکار برایت اسکریپتی میفرستد که یک اشکال (bug) دارد و از تو میخواهد که کمکش کنی تا مشکل را برطرف کند. تو بعد از بررسی، متوجه میشوی که مشکل چیست و میخواهی به او بگویی چطور آن را اصلاح کند.
مثلاً میتوانی بگویی: «تو فقط درون توابع میتوانی مقدار برگردانی، فکر کنم منظورت استفاده از sys.exit بوده. همچنین داری دو بار به گیگابایت تبدیل میکنی، بنابراین اسکریپت همیشه خطا میدهد.»
اما اگر کد پیچیده باشد، این توضیحات شفاهی ممکن است برای همکارت به سختی قابل درک باشد. او ممکن است نفهمد دقیقاً کدام خط باید تغییر کند یا تغییرات چگونه باید اعمال شوند.
💡 توضیح شفاهی تغییرات، مخصوصاً در کدهای طولانی یا پیچیده، معمولاً کافی نیست و ممکن است باعث سردرگمی شود.
برای اینکه تغییرات را بهوضوح نشان دهی، میتوانی یک فایل diff از تغییراتی که باید اعمال شوند، برای همکارت بفرستی. به این ترتیب او دقیقاً میبیند که کدام خطها تغییر کردهاند و کد نهایی چه شکلی خواهد بود.
استفاده از diff مزیت بزرگی دارد: ابهام را از بین میبرد و همکارت بدون نیاز به حدس زدن، میتواند تغییرات را اعمال کند.
ارسال diff برای تغییرات، روش استاندارد در بسیاری از تیمهای توسعه نرمافزار است، زیرا دقیقترین و کمخطاترین راه برای انتقال اصلاحات است.
برای تولید فایل diff از دو نسخه (قدیمی و جدید) از دستور زیر استفاده میکنیم:
$ diff -u old_file new_file > change.diff
در این دستور:
diff -u – دو فایل را با فرمت یکپارچه (unified) مقایسه میکند که شامل زمینه (context) بیشتری است.> – علامت بزرگتر، خروجی دستور diff را به جای نمایش در ترمینال، به یک فایل هدایت (redirect) میکند.change.diff – نام فایل خروجی که شامل تمام تفاوتها به همراه زمینه است.این فایل معمولاً فایل diff یا فایل patch نامیده میشود و شامل همه تغییرات بین فایل قدیم و جدید، به علاوه زمینه لازم برای درک و اعمال تغییرات است.
استفاده از پرچم -u باعث میشود فردی که فایل را میخواند، درک بهتری از تغییرات داشته باشد، چون چند خط قبل و بعد از تغییرات را هم میبیند.
حالا فرض کن خودت یک فایل diff از همکارت دریافت کردهای و میخواهی تغییرات را روی اسکریپت خودت اعمال کنی.
یک راه این است که فایل diff را با دقت بخوانی و سپس بهصورت دستی خطهای مورد نظر را در فایل اصلی تغییر دهی. اما این کار زمانبر و مستعد خطا است، مخصوصاً اگر تغییرات زیاد باشند.
خوشبختانه این فرایند را میتوان خودکار کرد. ابزاری به نام patch وجود دارد که دقیقاً برای همین کار طراحی شده است.
patch یک دستور خط فرمان است که فایل تولید شده توسط diff را میگیرد و تغییرات را روی فایل اصلی اعمال میکند.
دستور patch دقیقاً کاری را انجام میدهد که از نامش پیداست: یک فایل diff را میگیرد و تغییرات را روی فایل یا فایلهای مربوطه اعمال میکند.
نحوه استفاده از patch به این صورت است که نام فایل مورد نظر برای اصلاح را به عنوان اولین پارامتر به آن میدهیم و محتوای فایل diff را از طریق ورودی استاندارد (standard input) به آن میفرستیم.
برای هدایت محتوای فایل diff به ورودی استاندارد، از علامت < (کوچکتر) استفاده میکنیم:
$ patch cpu_usage.py < cpu_usage.diff
با اجرای این دستور، patch تغییرات موجود در فایل cpu_usage.diff را روی فایل 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 برایت فرستاده است که مشکل را برطرف میکند. بیایید محتوای آن را ببینیم:
$ 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 یک پارامتر 1 اضافه کرده است تا میانگین یک ثانیهای را محاسبه کند.
توضیح همکارت: «وقتی cpu_percent را بدون پارامتر صدا میزنیم، میانگین یک بازه زمانی گرفته نمیشود و بنابراین همیشه صفر برمیگرداند.»
اضافه کردن خط دیباگ، یک روش رایج برای فهمیدن مقدار واقعی متغیرها در حین اجراست و به عیبیابی کمک میکند.
اگر به خط دوم خروجی diff دقت کنی، یک خط عجیب با علامتهای @@ میبینی. بیایید این خط را تکهتکه باز کنیم و بفهمیم دقیقاً به ما چه میگوید.
@@ -2,7 +2,8 @@
این خط، سرآیند قطعه (chunk header) یا شمارهگذار خطوط است و به ما میگوید که تغییرات دقیقاً در کجای فایلها اتفاق افتادهاند. بیایید هر بخش را جداگانه بررسی کنیم:
-2,7 (فایل قدیمی)- به فایل قدیمی اشاره دارد (همان فایل اولی که در دستور diff آوردیم، مثلاً cpu_usage.py).2 یعنی در فایل قدیمی، تغییرات از خط شماره ۲ شروع میشوند.7 یعنی در فایل قدیمی، ۷ خط (از خط ۲ به بعد) در این قطعه تغییر نقش دارند. این ۷ خط شامل هم خود خطهای تغییرکرده و هم چند خط بالا و پایین آن (همان زمینه یا context) میشود تا خواننده بفهمد تغییر در چه بخشی از کد رخ داده است.+2,8 (فایل جدید)+ به فایل جدید اشاره دارد (همان فایل دوم، مثلاً cpu_usage_fixed.py).2 یعنی در فایل جدید، تغییرات از خط شماره ۲ شروع میشوند.8 یعنی در فایل جدید، ۸ خط (از خط ۲ به بعد) برای نشان دادن این تغییرات استفاده شدهاند. (چون یک خط جدید اضافه شده، تعداد خطوط بیشتر شده است).@@ و فضای آخر@@ در ابتدا و انتها فقط یک محدودهنما (separator) هستند تا شروع و پایان اطلاعات شمارهگذاری را مشخص کنند.@@ و بخش آخر (که در اینجا خالی است) معمولاً نام تابع یا کلاس را نشان میدهد تا بدانیم این تغییر مربوط به کجای کد است. مثلاً اگر تغییر داخل تابعی به نام check_cpu_usage باشد، این خط به این شکل دیده میشود:@@ -2,7 +2,8 @@ def check_cpu_usage(percent):
این سرآیند دقیقاً به ابزار patch میگوید که کجا و چقدر از فایل را باید تغییر دهد، بدون اینکه نیاز باشد کل فایل را اسکن کند.
۱. جبران جابهجایی خطوط (Offset): وقتی در بخشهای قبلی کد، خطی اضافه یا حذف میشود، شماره خطوط بعدی به همان تعداد جلو یا عقب میروند. patch با دیدن این اعداد، دقیقاً میفهمد که تغییر باید روی خط درست اعمال شود.
۲. اطلاع از تعداد خطوط: اعداد ۷ و ۸ نشان میدهند که در فایل قدیمی، یک بلوک ۷ خطی با یک بلوک ۸ خطی در فایل جدید جایگزین شده است. یعنی یک خط جدید به کد اضافه شده است.
فرض کن دو نسخه از یک لیست خرید داری:
نسخه قدیم:
۱. شیر
۲. نان
۳. پنیر
نسخه جدید:
۱. شیر
۲. نان
۳. کره
۴. پنیر
اگر diff -u بگیری، سرآیند میگوید:
@@ -2,2 +2,3 @@
یعنی: در نسخه قدیم از خط ۲، ۲ خط (نان و پنیر) و در نسخه جدید از خط ۲، ۳ خط (نان، کره، پنیر) در این تغییر نقش دارند.
حالا که فایل 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 و patch، چرا کل فایل را نمیفرستیم؟» جواب این سؤال در دو نکته کلیدی خلاصه میشود:
ممکن است همکارت فایل را از روی نسخهای که با آخرین نسخهات تفاوت دارد، اصلاح کرده باشد. با ارسال diff، او دقیقاً مشخص میکند که کدام خطها را تغییر داده، صرفنظر از اینکه نسخهاش با تو یکی باشد یا نه.
دستور patch هوشمند است و اگر فایل اصلی تغییراتی داشته باشد، باز هم سعی میکند تا حد امکان diff را اعمال کند. البته همیشه موفق نمیشود، اما در بسیاری از موارد جواب میدهد.
در این مثال، فقط یک فایل کوچک را اصلاح کردیم. اما گاهی ممکن است در یک پروژه بزرگ، چهار فایل از میان ۱۰۰ فایل را تغییر دهی که در دایرکتوریهای مختلف قرار دارند. اگر بخواهی کل فایلها را بفرستی، باید مشخص کنی هر کدام در کدام پوشه قرار میگیرند که کار را پیچیده میکند.
با diff کل پوشهها میتوانی یک فایل diff تولید کنی که خودش بهطور خودکار مسیر هر فایل تغییر یافته را مشخص میکند. به این ترتیب نیازی به جابهجایی دستی فایلها نخواهی داشت.
استفاده از diff و patch به ما امکان میدهد تغییرات را دقیق، قابلفهم و مستقل از نسخه فعلی منتقل کنیم و در پروژههای بزرگ، مدیریت ساختار را آسانتر میکند.
عالی! حالا میدانیم چطور فایلهای diff تولید کنیم، سرآیند آن را تفسیر کنیم و با دستور patch تغییرات را اعمال کنیم. در ویدیوی بعدی، این ابزارها را در یک مثال واقعی کنار هم خواهیم دید.
🎉 با اتمام این جلسه، شما با ابزارهای diff و patch برای تولید و اعمال دقیق تغییرات آشنا شدید.