رفتن به مطلب
جامعه‌ی برنامه‌نویسان مُدرن ایران

فرهاد شیری

مدیران مرجع
  • تعداد ارسال ها

    92
  • تاریخ عضویت

  • آخرین بازدید

  • روز های برد

    23

تمامی مطالب نوشته شده توسط فرهاد شیری

  1. فرهاد شیری

    بله می توانید تعریف کنید وقتی نوع شمارشی بدون نام در یک کلاس ویا یک استراکچر می سازید در صورتی که حوزه دسترسی این نوع شمارشی عمومی تعریف شده باشه می توانید به صورت ثابت های عضو عمومی کلاس بهشون دسترسی پیدا کنید. #ifndef TESTENUM_H #define TESTENUM_H class testEnum { public: testEnum(); virtual ~testEnum(); enum { a, b, c = 0, d = a + 2 }; enum { z, e, g = 0, f = z + 2 }; protected: private: }; #endif // TESTENUM_H testEnum t1; cout << t1.d << endl; و اگر هم برای داده شمارشی نام تعریف کنید به این صورت هم میتوانید استفاده کنید #ifndef TESTENUM_H #define TESTENUM_H class testEnum { public: testEnum(); virtual ~testEnum(); enum T1 { a, b, c = 0, d = a + 2 }; enum T2 { z, e, g = 0, f = z + 2 }; protected: private: }; #endif // TESTENUM_H testEnum t1; cout << t1.T1::a << endl; این سوال هم خیلی کلی هست ولی معمولا تعریف داده شمارشی بدون نام خیلی مرسوم نیست یا حداقل من خودم که این کار را انجام نمیدهم اونهم به این علت که نگهداری برنامه و پیچیدگی را افزایش میده وقتی روش غیر استانداردی را برای یک نوع استاندارد به کار ببریم. ولی در مجموع داده های شمارشی بر اساس ساختمانی که دارند هزینه نگهداری پایینتر و همچنین اتلاف حافظه کمتری نسبت به انواع ثابت ها را دارند.
  2. فرهاد شیری

    کدوم نارضایتی دوست عزیز! کاربرها ناراضی بودن که نمیرفتن آیفون بخرن 20 میلیون اونها مارا تحریم میکنن ما چیکار میکنیم میریم توصف خرید آیفون... شد یکبار ما اونها را تحریم کنیم و نخریم نه دوست گرامی مطمن باش حتی اگر خود گوشی را هم تحریم کنن یعنی تو جغرافیای ایران روشن نشه باز ملت میرن میخرن انقد کلنجار میرن تا بالاخره روشنش کنن یا اینکه همونطوری خاموش 15 میلیون پول میدن میخرن دیدم که میگم
  3. فرهاد شیری

    گل بود به سبزه نیز آراسته شد! دیگه عادت کردیم!! همونطوری که برای آی او اس جیلبریک نوشتن برای اینهم یه راهی پیدا میکنیم... انقد تحریم دور زدیم که دیگه اگر یک روز تحریم نباشیم نمیتونیم تو خط راست حرکت کنیم چه خواهیم کشید!
  4. فرهاد شیری

    اگر از CLR ویا universal window platform tools در ویژوال استودیو استفاده کرده باشید قطعا برای اجرای برنامه هاتون به فریم وورک های دات نت نیاز خواهید داشت در ماشین مقصد. ولی اگر از MFC , Win32 استفاده کنید نیازی به دات نت نخواهید داشت حالا چه فایل باینری stand alone exe بسازید ویا کتابخانه های Dynamic Link Library بسازید
  5. فرهاد شیری

    به مقاله زیر مراجعه نمایید. فرق بین کامپایل استاتیک و داینامیک
  6. ادامه مقاله امنیت در نرم افزارهای تولید شده با زبان ++C به یک شی cv-qualified توسط یک شی cv-unqualified دسترسی پیدا نکنید! به برنامه زیر توجه کنید، یک روش با شرایط ثابت، که برای ذخیره سازی پنهان نتایج ناشی از دورانداختن شرایط ثابت مذکور تلاش میکند، فراخوانی میشود. به دلیل آنکه کلاس S به عنوان یک ثابت اعلام شده است. تغییر ناگهانی و جهش مقدار پنهان ذخیره شده، منجر به بروز رفتار نامشخص میگردد. #include <iostream> class S{ private: int cachedValue; int compute_value() const; // expensive public: S() : cachedValue(0) {} int get_value() const{ if (!cachedValue){ const_cast<S *>(this)->cachedValue = compute_value(); } return cachedValue; } }; void f(){ const S s; std::cout << s.get_value() << std::endl; } اکنون برای رفع این مشکل کافی است که نوع متغیر عضو کلاس را به صورت mutable تعریف نماییم. #include <iostream> class S{ private: mutable int cachedValue; int compute_value() const; // expensive public: S() : cachedValue(0) {} int get_value() const{ if (!cachedValue){ cachedValue = compute_value(); } return cachedValue; } }; void f(){ const S s; std::cout << s.get_value() << std::endl; } نباید یک شی لاندا بیشتر از اشیایی رجوع شده به خود، عمر کند! این امکان وجود دارد که عبارت لاندا، اشیای دارای مدت زمان ذخیره سازی خودکاری را از مجموعههای ازحوزه های پیوسته حوزه ی دسترسی، برای استفاده در بدنه ی تابع، رصد و دریافت کند. ممکن است این رصد به صورت صریح و از طریق مشخص کردن شی در capture-list لاندا، یا به صورت ضمنی و با استفاده از capture-default و ارجاع به شی در بدنه ی تابع لاندا صورت پذیرد. هنگامی که یک شی به وسیله ی کپی رصد میشود، شی لاندا شاملیک عضو داده ای غیرایستای بدون نام خواهد شد، که مقدار اولیه برابر با مقدار شی مورد رصد است. طولعمر عضو داده ای غیرایستا نیز برابر با طول عمر شی لاندا خواهد بود. با این وجود، هنگامیکه یک شیتوسط مرجع رصد می شود، طول عمر مورد ارجاع به طول عمر شی لاندا گره نخورده است. بنابراین هنگامیکه یک شی لاندا از شی مورد ارجاع و رصد خود بیشتر عمر کند، مادامیکه شی مورد ارجاع و رصد در دسترس است، اجرای عملگر فراخوانی تابع شی لاندا منجر به بروز رفتار نامشخص میشود.بنابراین، شی لاندا نباید طول عمری بیشتر از شی مورد ارجاع و رصد خود داشته باشد. به مثال زیر توجه نمایید، یک مرجع لاندا، یک متغیر محلی را از یک لاندای بیرونی رصد میکند. با این وجود، طولعمر این لاندای داخلی بیشتر از لاندای بیرونی و متغیرهای محلی خودکار تعریف شده ی آن است. هنگامیکه شی لاندای داخلی در ()f اجرا میشود، رفتار نامشخصی روی خواهد داد. auto g(int val){ auto outer = [val]{ int i = val; auto inner = [&]{ i += 30; return i; }; return inner; }; return outer(); } void f(){ auto fn = g(12); int j = fn(); } برای رفع این اشکال کافی است که لاندای inner متغیر i را نه از طریق ارجاع بلکه از طریق کپی رصد نماید. auto g(int val){ auto outer = [val]{ int i = val; //auto inner = [=] () mutable { //OR auto inner = [i]{ i += 30; return i; }; return inner; }; return outer(); } void f(){ auto fn = g(12); int j = fn(); } زمانی که عملگر new را سربارگذاری میکنید حتما باید عملگر delete را هم سربارگذاری نمایید به برنامه زیر توجه نمایید، #include <Windows.h> #include <new> void *operator new(std::size_t size) noexcept(false){ // Private, expandable heap. static HANDLE h = ::HeapCreate(0, 0, 0); if (h){ return ::HeapAlloc(h, 0, size); } throw std::bad_alloc(); } // No corresponding global delete operator defined. در صورتی که هیچ کلاس کمکی برای سربار گذاری در نظر نگرفته باشید قطعا نمی توانید از عملگر delete بر روی حافظه رزرو شده توسط این تابع استفاده کنید. بنابراین بهتره از یک کلاس پوشاننده استفاده کنیم.. #include <Windows.h> #include <new> class HeapAllocator{ private: static HANDLE h; static bool init; public: static void *alloc(std::size_t size) noexcept(false){ if(!init){ h = ::HeapCreate(0, 0, 0); // Private, expandable heap. init = true; } if(h){ return ::HeapAlloc(h, 0, size); } throw std::bad_alloc(); } static void dealloc(void *ptr) noexcept{ if (h){ (void)::HeapFree(h, 0, ptr); } } }; HANDLE HeapAllocator::h = nullptr; bool HeapAllocator::init = false; void *operator new(std::size_t size) noexcept(false){ return HeapAllocator::alloc(size); } void operator delete(void *ptr) noexcept{ return HeapAllocator::dealloc(ptr); }
  7. با توجه به اهمیت امنیت نرم افزار، شرکت های بزرگ دنیا به ارائه راهکارهایی چون طراحی زبان ها و محیط های برنامه نویسی و مفسر و مترجم هایی با قابلیت های کنترل امنیتی بر روی سیستم عامل و بسیاری از راهکارهای دیگر پرداخته اند اما با توجه به عدم توانایی راهکارها در کنترل تمامی موارد امنیتی، عدم امکان پیاده سازی راهکارهای امنیتی بر روی برخی ساختارها، ایجاد محدودیت برای دسترسی به برخی منابع و امکانات و مشکلات کوچک و بزرگ دیگر برنامه نویسی یک برنامه به صورت ایمن بهترین راهکار برای محافظت از یک برنامه است. یکی از زبان هایی که در کنار محبوبیت در میان برنامه نویسان، همیشه یکی از زبان های پر بحث در برنامه نویسی ایمن بوده است، خانواده زبان های C به خصوص ++C است. در این زبان ها عمده مدیریت منابع به برنامه نویس واگذار شده که در صورت عدم مدیریت درست آن ها، آسیب پذیری های مختلفی رخ می دهد. بهترین راهکار برای جلوگیری از بروز آسیب پذیری نرم افزارها، برنامه نویسی پدافندی و ایمن آن نرم افزار از ابتداست. دراین مستندات، باتوجه به جامعیت و کاربرد فراوان زبان ++C درکنار محبوبیت، مباحث ونکات اساسی در برنامه نویسی پدافندی و ایمن این زبان مطرح شده و انواع آسیب پذیری و شیوه جلوگیری از بروز آن ها و رفع آن ها در صورت بروز، توضیح داده می شود. همچنین سعی می شود تا راهکارهای ارائه شده تا حد امکان قابل پیاده سازی در زبان C نیز باشند. با توجه به گستردگی ابزارهای برنامه نویسی این زبان و وجود کامپایلرهای مختلف، زبان معیاری برای این مستند مدنظر قرار گرفته شده است و ساختار ارائه شده مربوط به ابزار یا کامپایلر خاصی نیست اما بنا بر نیاز مثال هایی در کنار زبان معیار از ابزارهایی خاص نیز ارائه می گردد. رفتار تعریف نشده ممکن است شامل مختل شدن عملکرد برنامه(Crash) خروجی نامربوط و غلط، بروز آسیب پذیری های نرم افزاری و موارد دیگر می باشد. وجود رفتار نامتعارف در یک برنامه نه تنها امنیت خود آن برنامه ، بلکه ممکن است امنیت سیستم عامل، شبکه را نیز به خظر بیندازد. جلوگیری از بروز رفتارهای تعریف نشده و مقابله با آن ها از مباحث مهم برنامه نویسی تدافعی و ایمن است. توابع بدون آرگومان برای تعریف یک تابع بدون آرگومان باید از کلمه کلیدی void در زمان تعریف تابع استفاده نمایید.با این کار تزریق کد توسط هکرها را مختل می کنید. int getValue(void) { return 1; } اعداد تصادفی در صورت نیاز به اعداد تصادفی از تابع ()rand استفاده نکنید به این علت که خروجی این تابع در تکرارهای بالا دچار تکرار می شود. بهتراست از تابع ()srand استفاده کنید می توانید برای آن seed تعریف کنید تا احتمال تکرار را به حداقل برسانید. در ویندوز هم می توانید از تابع ()CryptGenRandom استفاده کنید و در لینوکس هم تابع ()random و تابع ()srandom استفاده نمایید. #include <windows.h> #include <wincrypt.h> #include <iostream> int main(void) { HCRYPTPROV hcp; CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, 0); long int li = 0; CryptGenRandom(hcp, sizeof(li), (BYTE *)&li); printf("Random number is -> %ld\n", li); return 0; } عدم استفاده از تابع بازگشتی جهت مقدار دهی اولیه به آرایه ای با کلاس حافظه استاتیک در مثال زیر زمان ساخته شدن و مقدار دهی اولیه آرایه cache تابع fact مجددا فراخوانی شده واین عمل به دلیل ایستا بودن آرایه باعث بروز رفتار تعریف نشده و خطا خواهد شد. #include <stdexcept> int fact(int i) noexcept(false) { if (i < 0) { throw std::domain_error("i must be >=0"); } static const int cache[] = { fact (0), fact(1), fact(2), fact(3), fact(4), fact (5), fact (6), fact(7), fact(8), fact(9), fact(10), fact (11), fact (12), fact(13), fact(14), fact(15), fact(16) }; if (i < (sizeof(cache) / sizeof(int))) { return cache[i]; } return i > 0 ? i * fact (i - 1) : 1; } حال برای رفع این اشکال طبق نمونه کد زیر آرایه را بدون استفاده initializer list با استفاده از یک متغیر ثابت که تعداد عضو های آرایه را معین میکند تعریف شده است و ازآنجا که کامپایلر آرایه های از جنس کلاس حافظه استاتیک را خود با عدد 0 مقداردهی میکند دیگر یک آرایه از قبل پر شده نخواهیم داشت و در مرحله با استفاده از تکنیک lazy به هر یک از عضوهای آرایه مقدار مناسب را با استفاده از تابع بازگشتی مقدار دهی خواهیم کرد. #include <stdexcept> const int arraySize = 17 int fact(int i) noexcept(false) { if (i < 0) { throw std::domain_error("i must be >=0"); } static int cache[arraySize]; if (i < (sizeof(cache) / sizeof(int))) { if (0 == cache[i]) { cache[i] = i > 0 ? i * fact(i - 1) : 1; } return cache[i]; } return i > 0 ? i * fact(i - 1) : 1; } الحاق مضاعف هدر فایل ها الحاق مضاعف زمانی رخ می دهد که یک هدر دو ویا چند بار به برنامه اضافه شوند. در مثال زیر در کلاس c هدرهای a , b الحاق می شوند در حالی که در کلاس b هم هدر a الحاق شده است که الحاق مضاعف رخ داده است. //a.h struct a { int membe }; //b.h #include "a.h" //c.c #include "a.h" #include "b.h" برای جلوگیری از این الحاق های مضاعف می توانید از روش زیر استفاده نمایید //a.h #ifdef A_H #define A_H struct a { int member; }; #endif ویا می توانید از دستور pragma استفاده کنید البته این دستور جز دستورات استاندارد ++c / c نمی باشد ولی اکثر کامپایلرها این دستور را اجرا میکنند. //a.h #pragma once struct a { int member; }; رمزنگاری اصولی جهت رمزنگاری داده های حساس در برنامه های خود می توانید از کتابخانه ++Crypto وهمچنین کتابخانه libcrypto از OpenSSL نیز استفاده نمایید. رمز نگاری به چند دسته اصلی تقسیم می شود: 1- رمزنگاری درهم سازHash که میتوان به الگوریتم های MD6 , MD5 , SHA-1,SHA-0 اشاره کرد. 2- رمزنگاری با کلید متقارن که می توان به الگوریتم های RC4 , AES , DES , 3DES اشاره کرد. 3- رمزنگاری با کلید عمومی نا متقارن که می توان به الگوریتم های RSA , DSA , DSS اشاره کرد. 4- کد گذاری دودویی به متن که می توان به الگوریتم های Base32 , Base58 , Base64 ,Base85 اشاره کرد. مدیریت مقدار و نوع داده ها و مقدار دهی اولیه در مثال زیر متغیر هایی تعریف شده اند که مقدار اولیه ندارند (البته درست است که در برخی از کامپایلرها این متغیرها را مقدار دهی خواهند کرد، ولی توجه داشته باشید که تکنیک های برنامه نویسی تدافعی جدای از امکانات کامپایلر می باشد) int main (void) { int a; float b; char c; bool d; return 0; } اکنون مشاهده میکنید که بعد از اجرای برنامه چه مقدار هایی در متغیرها ذخیره شده است. پس بنابراین مقدار دهی اولیه متغیرها یا باید برحسب نیاز در همان ابتدا تعریف صورت گیرد یا در صورت عدم نیاز به وجود مقدار اولیه خاص، مقدار دهی با استفاده از تابع همان نوع داده انجام خواهد شد. int main (void) { int a = int(); float b = float(); char c = char(); bool d = bool(); return 0; } و بعد از اجرا به این صورت خواهد بود مقدار دهی اولیه به آرایه ها int main (void) { int a[5]; float b[5]; char c[5]; bool d[5]; return 0; } که بعد از اجرا بدین صورت خواهد بود... و برای رفع این اشکال باید همیشه آرایه ها را مقدار دهی اولیه نمایید. int main (void) { int a[5] = {}; float b[5] = {}; char c[5] = {}; bool d[5] = {}; return 0; } وبعد از مقدار دهی اولیه به آرایه ها خواهیم داشت ... ادامه خواهد داشت این مقاله...
  8. فرهاد شیری

    یک سر به لینک زیر بزنید Using ActiveX controls and COM in Qt
  9. سلام به نظرم در این برهه از زمان تسلط در یک زبان ویا یک Frame Work به هیچ وجه نمیتونه تضمینی برای آینده یک مهندس نرم افزار ویا یک برنامه نویس باشه! بنابراین بهتره سعی کنید ++C را شروع کنید و بصورت موازی Frame Work Qt را هم استفاده کنید، ولی به هیچ وجه از تکنولوژی های سمت زبان جاوا غافل نشید. صرف تسلط به زبان ++C الان نمیتونه خیلی مهم باشه، (البته شرایط تجاری بازار IT کشور خودمون را عرض میکنم) اونهم به این علت که قطعا درصد بسیار کمی از شرکتهای معتبر که الان در ایران فعال هستند، زبان ++C را به عنوان تکنولوژی توسعه ای نرم افزارها شون انتخاب میکنن که اونهم بخاطر خیلی از دلایل که قبلا بارها بحث شده. که البته شانس استخدام در این شرکتها هم خیلی پایین هست. متاسفانه مشکل هم از اونجا شروع میشه که، کسانی که بدون فکر و منطق هجوم میارن سمت زبانهایی مثل سی پلاس پلاس فکر میکنن با خوندن دوتا کتاب و دوتا پروژه روی گیت ایجاد کردن و مطالعه سایتهای رفرنس می تونن در زبان سی پلاس پلاس تجربه کسب کنند که قطعا نمیتونه اینطور باشه! هرچند نام کیوت و سی پلاس پلاس خیلی بزرگ جلوه میکند اما واقعیت چیزه دیگه ای هست. پس تا وقتی هم که شما شرایط قرار گرفتن دریک تیم توسعه ای نرم افزاری که تکنولوژی توسعه ی نرم افزارهای تجاری شون با زبان سی پلاس پلاس ویا فریم وورک کیوت هست، را نداشته باشید قطعا نمیتونید دراین زمینه به یک دانش خوب و قوی دست پیدا کنید. به همین علت اگر برنامه نویسی که بخواد در این بازار مشغول باشه بهتره حتما از تکنولوژی های دات نت و جاوا و تکنولوژی های سمت وب اطلاعات کافی داشته باشه.
  10. فرهاد شیری

    احسنت! دست مریضاد...
  11. فرهاد شیری

    یک سوال نسبتا پیچیده! مربوط به معماری کامپیوتر در مقطع ارشد برخوردم، من راه حلی که به ذهنم می رسید را نوشتم. دوستان اگر کسی علاقه داشت و راه حل دیگه ای داره ویا حتی اگر راه حل من صحیح نیست خوشحال می شوم، بیان کنید. سوال: برنامه زیر را در نظر بگیرید. A یک آرایه با آدرس شروع 0 است. اگر یک Data Cache با دسترسی Direct Mapping با گنجایش 4 کلمه در اختیار داشته باشیم، Hit Rate را محاسبه نمایید؟ فرضیات: متغیر های i , j , t در ثباتهای پردازشگر ذخیره شده اند. محتویات خانه های حافظه در ثباتهای SS,SP ذخیره نمی شوند، یعنی به ازای هر بار فراخوانی آرایه A یکبار به حافظه Cache دسترسی خواهیم داشت. فرض کنید آرایه به صورت صعودی مرتب شده است. For (int I = 0 ; i < 5 ; i++) For (int j = i ; j < 5 ; i++) If ( A[i] < A[j] ) { T=A[i]; A[i] = A[j]; A[j] = t; }
  12. فرهاد شیری

    جوابم را سعی کردم بهتر نشون بدم! دوستان اگر نظری داشتید بنده را بی نصیب نذارید! بنابراین می توان نتیجه گرفت در صورتی که آرایه A int A[]{1,2,3,4,5}; با این مقادیر در حافظه وجود داشته باشد، استنباط این است در اولین آدرس دهی Cache وبا هربار فراخوانی Cache مقادیر 1 و 3 و 5 با نرخ Hit 100 وجود خواهند داشت. و مقادیر 2 و 4 در Cache در دسترس نخواهند بود ومجددا آدرسی دهی خواهد شد.
  13. فرهاد شیری

    سپاسگذار زحمات شما هستم
  14. فرهاد شیری

    عالی مثل همیشه!
  15. فرهاد شیری

    جناب اسدازاده! تشکر از مقاله ها و اشتراک گذاری دانش فنی خودتون در اختیار افرادی امثال من، قدردان زحمات شما هستیم!
  16. فرهاد شیری

    تشکر! و سپاس فراوان
  17. فرهاد شیری

    عالی! مثل همیشه
  18. فرهاد شیری

    عالی! سپاسگذار زحمات شما هستیم
  19. فرهاد شیری

    تشکر مثل همیشه عالی! موفق باشید!
  20. فرهاد شیری

    سرکار خانم انصاری! از زحمات شما در ارائه این مقالات فنی و پربار کمال تشکر دارم. البته بر اساس تجربه 18 ساله در دنیای نرم افزاری که دارم عرض کردم. با سپاس
  21. با سلام! در این تاپیک قصد دارم هرچه ساده ابزارهایی که معمولا برنامه نویسان حرفه ای جهت طراحی بهتر و بهینه تر نرم افزارهای خود از آنها بهره می برند را معرفی کنم! خواهشمندم دوستان اگر کسی نرم افزاری که توسط خودش استفاده میشه! و در راستای این تاپیک هست لطفا همکاری کنید! با تشکر! فقط دوستان خواهشا کپی نباشه از جای دیگه! اگر نرم افزاری که خودتون استفاده میکنید معرفی کنید!
  22. اگر مثل من خیلی وقتها سخت تون که همه قوانین سختگیرانه طراحی مدل های نرم افزاری که در برنامه های مثل EA , Visual Paradigm هست را رعایت کنید، و خیلی سریع و سر راست میخواهید یک مدل خوب UML بسازید که در عین کاربردی بودن خیلی هم کار راه انداز باشه! حتما از Umbrello استفاده کنید البته تحت سیستم عامل لینوکس. لینک دریافت : https://umbrello.kde.org/
  23. اگر تابحال از نرم افزارهای کنترل پروژه استفاده نکردید! بنابراین اگر می خواهید بتونید تو یک تیم که از متدلوژی های طراحی نرم افزار Agile استفاده میکنند هم گروه شوید وبتونید پروژه های خودتون را کنترل کنید، نمودار گانت ترسیم کنید ، Task های به نام Sprint ایجاد کنید تا بتونید dead line های نرم افزار هایی که تولید میکنید مدیریت کنید تا بتونید بهره وری خودتون را بیشتر کنید. البته می تونید با جاوا و جاوا اسکریپت خودتون هم براش plugin طراحی کنید، پس حتما پیشنهاد میکنم JIRA را امتحان کنید. لینک دریافت Jira | Issue & Project Tracking Software | Atlassian
  24. اگر شماهم به اینکه Comment ها ویا Pre / Post condition هایی که در سورس کدها نوشته اید اهمیت میدید ودوست دارید در یک قالب فرمت شده مرتب و کاربردی مثلا خروجی Html این مستندات را داشته باشید پیشنهاد میکنم از DoxyGen حتما استفاده کنید! لینک دریافت http://www.doxygen.nl/index.html
  25. البته نرم افزار TortoiseGit هیچ ربطی به نرم افزار Beyond Compare ندارد! نرم افزار سورس کنترلی از والد قدیمی خود TortoiseSVN می باشد. البته در نرم افزاری های سورس کنترل حرف اول را Smart Git میزند! لینک دانلود SmartGit – Git Client for Windows, macOS, Linux
×