منبع اصلی نوشتار زیر در این لینک قرار دارد

بررسی کد قبل از کامیت

چند وقتی هست که کار به روزرسانی یک کد نسبتا بزرگ به من سپرده شده و به شدت درگیرش هستم. چون این کد الان دو سه ساله داره کار میکنه من معمولا کارم اصلاح کده طوری که با کد قبلی هماهنگ باشه.
از git استفاده کردم برای کنترل پروژه و معمولا از قابلیت format-patch استفاده میکنم برای ایجاد پچ، ولی به دلیل بی دقتی، گاهی اشتباهاتی انجام میدم. مثلا خیلی پیش میاد که تابع xdebug_break رو استفاده کنم و بعد یادم بره که بردارمش، یا مثلا var_dump یا print_r و حتی چند مورد خیلی خاص هم پیش اومد که کدم syntax error داشت :) البته من سعی میکنم این مشکلات نباشه، ولی بودنشون اجتناب ناپذیره.
این شد که تصمیم گرفتم چاره‌ای براش پیدا کنم و آخر سر توی git پیداش کردم. قبلا هم با svn از اینکارها (نه به این صورت خاص) انجام داده بودم.

هدف این بود که من نتونم کد رو کامیت کنم، اگر کد خطای دستوری داشت یا اینکه یک سری عبارات خاص توی کد پیدا میشد. خوب برای اینکار hook ها در git به دادم رسیدند.
برای اینکه مفهوم هوکها رو در گیت بهتر درک کنید، اینجا رو بخونید. اما اگر حوصله ندارید، بدونید که توی هر رپوزیتوری گیت یک پوشه قرار داره به اسم .git (دات گیت) داخل اون یک پوشه هست به نام hooks که یک سری اسکریپت اونجا هست. در حالت عادی اون اسکریپتها همه یک پسوند sample هم دارند و همین باعث میشه که اجرا نشن. حالا اگه شما بیاید اسم این اسکریپتها رو تغییر بدید، و عبارت sample رو بردارید درزمان درست اجرا میشن. مثلا post-commit بعد از کامیت کد اجرا میشه. یا pre-commit قبل از کامیت کد.
مهم هم نیست که شل اسکریپت باشن، یا هر جور فایل اجرایی دیگه‌ای. فقط باید حتما فلگ x روی اونها ست شده باشه که پیشفرضشون هست، ولی اگه خودتون فایل رو ساختید اینطوری عمل کنید :

chmod a+x post-commit

تا فایل (اینجا فایل post-commit ) اجرایی بشه.
برای مورد خاص من، بهترین هوک، pre-commit بود. این هوک قبل از کامیت اجرا میشه و اگر مقدار بازگشتیش صفر نباشه، کامیت متوقف میشه.
حالا مشکل دوم این بود که باید فایلهایی که قراربود کامیت بشن رو میگرفتم. خوب اینجا بازم میریم سراغ خود گیت :

git diff --cached --name-status

که خروجیش میشه چیزی شبیه به این :

M       base/app/lib/view/___Prefix___BaseView.class.php
M       base/templates/app/modules/actions/Action.class.php.tmpl
M       base/templates/app/modules/lib/action/BaseAction.class.php.tmpl
M       base/templates/app/modules/lib/model/BaseModel.class.php.tmpl

که M نشونه وضعیت فایله، و بعد یک تب بعد آدرس فایل و درنهایت آخر هر خط کاراکتر انتهای خط .
M,A,D وضعیتهای فایل هستن که D قاعدتا نباید چک بشه. (فایلی که حذف شده فلگ D میخوره، و طبیعیه که فایلی که حذف بشه برای من مهم نیست)
تصمیم گرفتم برای اطمینان از نبود Syntax error از خود php استفاده کنم که با سوییچ l از لحاظ سینتکس فایل رو بررسی میکنه. بعد هم برای راحتی کار ترجیح دادم از grep استفاده کنم برای پیداکردن توابعی که خطا نیستن ولی من نمیخوام که باشن.
حالا اگر کد از اول برای خودم بود، حتما از یه Code Sniffer استفاده میکردم برای بررسی کدها، ولی خوب، چون کد قدیمیه و من نمیخوام زیاد سینتکس فعلی فایل رو دست بزنم، فعلا بیخیال Code Sniffer میشم (توی برنامم هست دربارش بنویسم :) خوب شد یادم اومد، اینم مدرک که دیگه یادم نره) . خوب کد نهایی میشه این، و لازم نیست که بگم من PHP رو بیشتر از bash دوست دارم :

#!/usr/bin/php
<?php
echo "pre-commit hook\n";
$staged = <code>git diff --cached --name-status</code>;
$staged = explode(&quot;\n&quot;, $staged);
foreach ($staged as &amp;$single)
	$single = explode(&quot;\t&quot;, $single);

//We have files now
foreach ($staged as $file) {
	if (count($file) != 2)
		continue;
	if($file[0] == 'D') { //Ignore deleted files
		continue;
	}
	if (preg_match(&quot;/php$/i&quot;, $file[1]) == 0)
		continue;
	$address = $file[1];
	$result = array();
	$return = 0;
	$code = exec(&quot;php -l $address&quot;, $result, $return);
	if ($return != 0 ) {
		echo &quot;Check for file $address failed, result is : &quot; . print_r($result, true);
		exit(1);
	}
	//Now check for some things in file, like var_dump and xdebug_break
	$invalidKeys = array (
		'xdebug_break',
		'var_dump',
		'print_r',
		);

	foreach ($invalidKeys as $key) {
		$result = array();
		$return = 0;
		exec(&quot;grep -iHn $key $address&quot;, $result, $return);
		if ($return ==0) {
			echo &quot;Found invalid keyword : &quot; . print_r($result, true);
			exit(1);
		}
	}
}

exit(0);

خود کد خیلی سادست :) php -l وقتی کد مشکل داشته باشه بازگشتیش صفر نیست، و grep هم وقتی چیزی رو که میخواد پیدا کنه، بازگشتیش صفره.
باقیشم که مشخصه. حالا اگر کدی که میخوام کامیت کنم، خطای دستوری داشته باشه، یا عباراتی که من نمیخوام توش باشه، با یه پیغام متوقف میشم.

برخلاف svn یک رپو در git ماهیت مستقلی داره نسبت به همه رپوهای دیگه. یعنی شما میتونید این چک رو توی رپوی خودتون داشته باشی، ولی یک رپوی دیگه از همین کد کلون کنید نخواهد داشتش. و همینطور اگه تغییرات خودتون رو به یک رپوی دیگه push کنید هم این هوکها در رپوی مقصد به وجود نمیاد.
نکته دیگر هم اینکه ممکنه یک فایل تغییر کرده باشه ولی بعد از اینکه تغییرات add شد بازم تغییر کرده باشه، درسته که وقت کامیت اگر از سوییچ a استفاده نشه git تغییرات دوم رو نمیبینه، ولی این اسکریپت اونها رو هم میبینه، و من بنابه دلایلی همین رو نیاز دارم.

هوکها توی کنترل نسخه، خیلی میتونن مفید باشن. این که من نوشتم، فقط یه ایده بود برای شما که گسترشش بدید. مثلا من قبلتر تو یک شرایط مشابه، میخواستم یک سایت به صورت اتوماتیک با هر کامیت من دوباره ساخته بشه، (با اجرای یک اسکریپت، که فایل رو آپلود میکرد، و یک سری کارهای دیگه) اون اسکریپت رو گذاشته بودیم توی post-commit رپوزیتوری svn و بعد از هر کامیت svn خود به خود اون رو اجرا میکرد.

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



برچسب ها : , , , , , , , , , ,