آموزش مبتدی Git: بخش اول

Distributed version-control systemدر این مطلب قصد دارم نحوه کار با گیت را به شما توضیح بدهم. پیش نیاز این مبحث مطلب زیر ‌می‌باشد: https://vrgl.ir/gExZt مفاهیمهمانطور که در پست قبلی اشاره کردم:"گیت یک سیستم کنترل نسخه برای نظارت بر روی تغییرات اعمال شده در طول توسعه نرم افزار می‌باشد."ابتدا با مفهوم کنترل نسخه (Version Control) آشنا خواهیم شد. وظیفه کنترل نسخه ثبت تغییرات و ارائه تاریخچه ای از آن ها است. به شرح تصویر برای جلوگیری از چنین کاری:کنترل نسخه دستی =))اما ویژگی بسیار مهم گیت امکان کار گروهی می‌باشد و می‌توانید ببینید چه تغییراتی توسط چه شخصی در چه زمانی صورت گرفته است. احتمالا قبل از گیت برای کار تیمی از طریق فلش یا آپلود و دانلود کردن فایل جا به جا می‌کردید که شیوه قابل اطمینانی نیست. کنترل های نسخه دو دسته هستند: متمرکز (Centralized) و توزیع (Distributed) شده. در نوع متمرکز فایل های پروژه و تاریخچه آن بر روی سرور مرکزی نگهداری شده و سیستم ها با اتصال به سرور به داده ها دسترسی پیدا می‌کنند. ایراد اصلی این نوع کنترل نسخه غیر قابل اعتماد بودن آن است زیرا در صورت اختلال در اتصال به سرور یا قطعی سرور مرکزی، تاریخچه و دیگر ویژگی های کنترل نسخه از دسترس خارج خواهد شد. برای حل این مشکل، کنترل نسخه توزیع شده به میان آمد که فایل های پروژه و تاریخچه آن علاوه بر سرور، بر روی کلاینت کاربر هم قابل دسترس است. با این توضیحات، گیت یک سیستم کنترل نسخه توزیع شده است که از ویژگی های آن می‌توان به موارد زیر اشاره کرد:سریع و کم حجمشاخه بندیتاریخچه عملیاتبازگردانی تغییراتمقایسه تغییرات?مباحث این مطلب در محیط خط فرمان Git Bash توضیح داده شده‌اند اما در نرم افزار های دیگر هم قابل استفاده هستند.git-bash.exeتنظیماتسیستم گیت مانند هر نرم افزار دیگری دارای تنظیمات مختص به خود می‌باشد. تنظیمات آن به سه بخش تقسیم می‌شود:تنظیم مجزا برای هر پروژه (Local)تنظیم کلی برای تمام پروژه های کاربر فعلی (Global)تنظیم کلی برای تمام پروژه های کاربران سیستم عامل (System)یک بار تنظیم Global اکثر نیاز های شما را پاسخ می‌دهد. این تنظیمات در یک فایل کانفیگ با نام .gitconfig در دایرکتوری یوزر شما (C:\Users\<Username>\.gitconfig) ذخیره می‌شوند. این فایل از ترمینال Git Bash با این دستور قابل دسترس است:$ git config --global --editاگر در صورت نوشتن این دستور با محیط ترمینال وحشتناکی مواجه شدید حتما دارید از Vim استفاده می‌کنید:؟?؟نگران نباشید برای خروج از Vim متن زیر را تایپ و کلید اینتر را بزنید::qa!توصیه می‌شود در صورت نصب بودن از Visual Studio Code استفاده کنید:$ code .gitconfigتغییرات اعمال شده در تاریخچه گیت شامل نام و آدرس ایمیل کاربر هستند. برای تعریف نام:$ git config --global user.name 'foo bar'و برای تعریف ایمیل:$ git config --global user.email 'foo@bar.baz'کانفیگ گلوبال در git bashحال اگر دوباره به gitconfig نگاه کنید شاهد ذخیره شدن این تغییرات خواهید بودcode .gitconfigبرای مشاهده تمام تنظیمات اعمال شده:$ git config --listتنظیم Local هم شبیه به Global است با این تفاوت که gitconfig فقط config نامیده شده و داخل پوشه مخفی git در هر پروژه قرار دارد. نوشتن دستور git config بدون --global بصورت local اعمال می‌شود.ایجادمی‌خواهیم با دستورات یونیکس پوشه ای ایجاد کرده و درون آن یک سیستم گیت راه اندازی کنیم. خوب است که هرزگاهی صفحه ترمینال را تمیز کنیم:$ clearبصورت پیش فرض Git Bash در دایرکتوری کاربر یعنی C:\Users اجرا می‌شود که در یونیکس معادل ~ است. برای رفتن به دسکتاپ می‌توان از دستور Change Directory استفاده کرد:$ cd ~/Desktopمی‌خواهیم با دستور Make Directory پوشه ای بر روی صفحه دسکتاپ ایجاد کرده و به درون آن برویم:$ mkdir myGitو برای رفتن به داخل آن:$ cd myGitدر صورت نیاز برای خارج شدن از یک فولدر کافی است روبروی دستور cd از دو کاراکتر نقطه استفاده کنید. همچنین می‌توان از دستور Print Working Directory از دایکرتوری فعلی اطمینان حاصل کرد:$ pwdایجاد فولدر بر روی دسکتاپ و باز کردن آنبه پروژه هایی که توسط گیت مدیریت می‌شوند اصطلاحا ریپو (ابتدای کلمه Repository) گفته می‌شود. برای ایجاد یک repo خالی کافی است از دستور زیر استفاده کنیم:$ git initاین دستور یک پوشه مخفی git درون دایکرتوری فعلی ایجاد می‌کند. با دستور List Structure دیده می‌شود:$ ls -a.gitتا اینجای کار یک ریپو ایجاد کردیم. ریپوی گیت برای ارائه کنترل نسخه نیاز به محتوا برای کار کردن دارد پس:$ touch note.txtاین دستور یک فایل متنی با نام note و پسوند txt در مسیر جاری ایجاد می‌کند. (برای حذف هم دستور rm). این فایل در حال حاضر خالی است و محتوایی برای نمایش ندارد اما در هر صورت می‌خواهیم ایجاد شدن آن را در تاریخچه گیت ثبت کنیم. تمام فایل هایی که در ریپوی گیت ایجاد می‌شوند Untracked هستند به این معنا که گیت هیچ تعاملی با آن ها ندارد. چطور می‌توان از وضعیت هر فایل در گیت خبردار شد؟ با دستور:$ git statusایجاد فایل و بررسی وضعیت آنبه متن قرمز رنگ دقت کنید، اسامی فایل های Untracked در اینجا نمایان می‌شوند. قبل از ثبت شدن فایل ها به تاریخچه گیت، فایل ها باید آماده ثبت شوند که به آن Stage کردن می‌گویند:$ git add note.txtآوردن فایل به Stageوضعیت فایل از Untracked تغییر کرده و به رنگ سبز در آمده است. حال فایل Stage شده آماده ثبت در تاریخچه گیت می‌باشد که به آن Commit گفته می‌شود. عمل Commit نیازمند ارائه توضیحاتی در مورد تغییرات رخ داده در ریپو است که بطور متداول ابتدای آن با فعل امری از عمل رخ داده نوشته می‌شود:$ git commit -m &quotCreate note.txt&quotاولین کامیت به گیت ارسال شده است و در آینده می‌توان وضعیت فایل را به وضعیت کامیت های ثبت شده بازگرداند. این نکته را هم به یاد داشته باشید که مرسوم است متن اولین کامیت "Initial commit" ثبت شود. برای مشاهده کامیت های ثبت شده در ریپو از دستور زیر استفاده کنید:$ git logدستور log اطلاعاتی همچون مشخصه کامیت به همراه شاخه متعلق به آن، نام و آدرس ایمیل فرد ثبت کننده، تاریخ دقیق و جزئیات کامیت را چاپ می‌کند. مشخصه های کامیت با الگوریتم رمزنگاری SHA-1 تولید و Hash می‌شوند. مشاهده log مرتب و خطی نیز بدین صورت است:$ git log --onelineدو نوع نمایش git logتصویر زیر نحوه ثبت شدن فایل در ریپو را شرح می‌دهد، آن را به خاطر بسپارید:How git worksدر ادامه برای مرور می‌خواهیم چندین فایل جدید به ریپو اضافه کنیم:$ touch file1 file2 file3لزوما نیازی نیست فایل ها پسوندی داشته باشند. همچنین لزومی هم ندارد خودمان فایل ها را ایجاد کنیم، می‌توانیم فایل های موجود در سیستم را به پوشه ریپو کپی کنیم.ایجاد چندین فایلفایل های جدید در حالت Untracked قرار گرفتند. می‌توان تمام تغییرات را همزمان به Staging اضافه کرد:$ git add .در صورت منصرف شدن از Stage کردن:$ git restore --staged .و مانند قبل برای کامیت فایل های Stage شده:$ git commit -m &quotAdd multiple files&quotکامیت چندین فایلکامیت ها دنباله ای از لیست های پیوندی هستند که علاوه بر تغییرات فعلی، کامیت های قبل خود را نیز شامل می‌شوند. کامیت ها فقط می‌توانند یک والد (Parent) داشته باشند مگر اینکه از دو شاخه (Branch) ادغام (Merge) شده باشند.لیست پیوندیحذفحذف یک یا چند فایل از ریپو نیز با کامیت یک مجزا امکان پذیر است. در حالاتی که می‌خواهید تمام فایل ها را با git add به stage برده و آن ها را کامیت کنید، این دستور جایگزین سریعتری است:$ git commit -am &quotRemove file&quotاما استفاده از این نوع کامیت دقت کار را کاهش داده و از همین رو استفاده از آن پیشنهاد نمی‌شود.(git add + git commit)کنترل نسخه گیت فقط به حذف و اضافه پرونده ها محدود نمی‌شود بلکه محتویات درون آن ها را هم نظارت می‌کند که در ادامه خواهیم دید.تغییرمی‌خواهیم تغییری در یکی از فایل ها ایجاد کرده و آن را به تاریخچه گیت اضافه کنیم. یکی از خصوصیات برنامه Visual Studio Code تشخیص خودکار ریپو و باز کردن آن بصورت Workspace می‌باشد. کافی است داخل دایرکتوری ریپو دستور زیر را بنویسیم:$ code .یکی از فایل های Workspace باز شده را از پنل Explorer سمت چپ باز کرده و متنی داخل آن می‌نویسیم. فایل را با کلید میانبر Ctrl + S ذخیره می‌کنیم. نوشتن متن در note.txt و ذخیره آنسپس دوباره به ترمینال Git Bash رفته و status می‌گیریم:بررسی تغییراتهمانطور که مشاهده می‌کنید دیگر متن Untracked نمایان نمی‌شود زیرا گیت در حال Track کردن تغییرات آن است و متن not staged در اینجا دیده می‌شود. این بدان معناست که گیت بر روی این فایل نظارت دارد و تغییرات آن را با متن قرمز رنگ modified اعلام می‌کند. مانند قبل برای ثبت تغییرات:$ git add note.txtسعی کنید برای کامیت پیامی بنویسید که حداقل برای خودتان و در پروژه های تیمی برای همه واضح باشد:$ git commit -m &quotUpdate note.txt with greetings&quot?دستورات گیت در ترمینال Microsoft Visual Studio Code نیز قابل اجرا هستند: Ctrl + Shift + `می‌خواهیم با یکی دیگر از دستورات مفید گیت آشنا شویم. تغییرات بیشتری داخل note.txt ایجاد می‌کنیم:چند خط به note.txt اضافه کردیمبرای اینکه از جرئیات تغییرات اعمال شده مطلع شویم کافی است دستور زیر را بنویسیم:$ git diffتغییرات فایل note.txtتغییرات قبل و بعد نمایش داده می‌شود. دقت کنید تغییرات note.txt در Stage قرار نگرفته‌اند. پس از اضافه کردن فایل به Stage اگر از دستور git diff استفاده کنیم با همچین رخدادی روبرو خواهیم شد:تغییر بی تغییر؟باید برای مشاهده جزئیات تغییرات فایل های Stage شده از دستور زیر استفاده کنیم:$ git diff --stagedپس از مطمئن شدن از تغییرات آن ها را کامیت می‌کنیم:$ git commit -m &quotAdd new lines to note.txt&quotاز دیگر مشکلات این چنین که ممکن است با آن روبرو شویم هنگام تغییر نام فایل است. از دستور یونیکس Move برای انتقال فایل به فایل دیگری و از این رو تغییر نام آن استفاده می‌کنیم: (چند بار جمله قبل رو توی ذهنتون تکرار کنید ? )$ mv file2 file_newپارامتر اول نام فایل قدیمی و پارامتر دوم نام فایل جدید است یعنی file2 به file_new تغییر نام داده است:rename with mvاما از دید گیت اتفاق دیگری رخ داده به این صورت که file2 حذف و file_new ایجاد شده است.file2 =/= file_newشیوه صحیح تعریف کردن این عمل تغییر نام به گیت بدین صورت است که ابتدا فایل جدید را به Staging می‌بریم:$ git add file_newو سپس فایل قبلی را پاک می‌کنیم:$ git rm file2file2 == file_newتوانستیم با موفقیت گیت را از این تغییر با خبر کنیم و حالا نوبت ثبت این تغییر است:$ git commit -m &quotRename file2 to file_new&quotبه یاد داشته باشید می‌توانید تعداد نتایج git log خود را با نوشتن یک عدد مقابل آن محدود کنید:$ git log -3 --onelineفقط نمایش سه کامیت آخربازگردانیهمانطور که در ابتدای مطلب اشاره شد، گیت یک سیستم کنترل نسخه است به این معنی که تاریخچه ای از تمام تغییرات نگهداری می‌کند. این تغییرات قابل بازیابی هستند و گیت می‌تواند حالت فعلی ریپو را به یک نسخه‌ی قدیمی‌تر بازگرداند. برای این کار باید از دستور checkout به همراه کل یا بخشی از مشخصه SHA-1 کامیت (مانند مشخصه oneline) استفاده کنیم.$ git checkout <SHA>سفر در زمانمی‌خواهیم به قبل اضافه شدن خطوط جدید به فایل note.txt بازگردیم. مشخصه آن برای شما متفاوت است.جدا شدن HEAD تا به الان با کلمه HEAD زیاد مواجه شده‌ایم. در واقع HEAD یک اشاره گر (pointer) به شاخه فعلی است. متن آبی رنگ در تصویر را با دقت نگاه کنید، این اشاره گر با دستور Concatenate هم قابل فراخوانی است:$ cat .git/HEADاشاره HEAD به کامیت در تاریخچهدر حالت عادی HEAD به refs/heads/master اشاره می‌کند. فایل note.txt را باز می‌کنیم.مشاهده کامیتتمام تغییرات بعد از کامیت مشخصی که به آن checkout کرده‌ایم از بین رفته‌اند. تغییرات جدیدی ایجاد و آن را کامیت می‌کنیم:کامیت جدیدپس از تغییر در زمان گذشته می‌خواهیم به زمان حال برگردیم:$ git checkout masterاما اتفاقی ناخوشایند در انتظار ما است!تغییر در گذشته زمان حال را تغییر نمی‌دهدتغییراتی که در گذشته اعمال کردیم به زمان حال انتقال پیدا نکرده‌اند. فایل note.txt را باز کنید و ببینید.$ cat note.txtمحتویات فایل تغییری نکردچه اتفاقی رخ داده است؟master branchتغییرات detached به شاخه master تعلق ندارند و کامیت 1f0517d به هیچ شاخه ای متصل نیست. می‌خواهیم تغییراتی که در گذشته اعمال کردیم جایگزین محتوای فعلی note.txt شوند اما checkout کردن کامیت از طریق SHA غیرمتصل ممکن نیست. از تاریخچه HEAD با دستور reflog می‌توان آن را پیدا کرد: $ git reflogبررسی تاریخچه قرارگیری HEADکافی است مشخصه اشاره گر را checkout کنیم:$ git checkout HEAD@{1}محتوای فایل را در صورت نیاز تغییر داده و پس از ذخیره آن را کامیت می‌کنیم:تغییر کلی متن و کامیت آنبرای اطمینان log را بررسی می‌کنیم:کامیت های detached نوبت ایجاد یک شاخه جدید است. برای این کار از سویچ b مقابل دستور checkout استفاده می‌کنیم:$ git checkout -b <NAME>ایجاد شاخه و ورود به آنشاخه جدید و مجزا از master ایجاد کردیم. برای دیدن لیست شاخه ها از دستور branch استفاده می‌کنیم:$ git branchلیست شاخه هامتن سبز رنگ و کاراکتر asterisk کنار آن نشانگر شاخه فعلی است. شاخه مانند دو جهان موازی در کنار هم وجود دارند اما سرانجام باید به شاخه اصلی یعنی master ختم شوند. برای اعمال تغییرات شاخه جدید در master باید عمل ادغام (merge) صورت گیرد. برای ادغام به شاخه ای که قرار است عمل merge بر روی آن انجام شود checkout می‌کنیم. این بار تغییرات اعمال شده بر روی branch قرار دارند و مستقیما قابل دستیابی هستند. می‌توانید برای متوجه شدن این عمل جفت فایل note.txt در master و شاخه جدید را cat کنید:$ cat note.txtتغییر شاخه$ git checkout masterبررسی مجدد$ cat note.txtتفاوت note.txt در دو شاخه موازیتغییرات temp-fix-note را در master ادغام می‌کنیم:$ git merge <temp-fix-note>اما با مشکل رایجی تحت نام Conflict برخورد خواهیم کرد.تعارض در ادغامهمانطور که مشاهده می‌کنید گیت پیغام وجود conflict در فایل note.txt را گزارش می‌دهد. این اتفاق زمانی رخ می‌دهد که محتوای فایل در شاخه های مختلف احتمال جایگزینی و از بین رفتن محتویات فعلی شوند. اگر از دستور cat دوباره استفاده کنید متوجه خواهید شد که گیت تفاوت این دو فایل را نمایش می‌دهد:تفاوت در ادغاممتن Hello, World در هر دو یکسان است اما از جایی که مشخص گردیده تفاوت دو شاخه دیده می‌شود. برنامه VS Code این تفاوت را بهتر نمایش می‌دهد:$ code note.txtافزونه GitLensرفع conflict به عهده خودتان هست. باید یکی از محتویات یا هر دو را پاک کنید. برای سادگی کار تمام متن بعد از Hello, World را پاک و آن را کامیت می‌کنیم:$ git commit -a -m &quotResolve merge conflict by deleting both&quotو در نهایت برای اطمینان عمل ادغام را تکرار می‌کنیم:$ git merge temp-fix-noteادغام موفقیت آمیزدر بخش بعدی نگاه عمیق تری به شاخه ها و دستوراتی همچون reset و revert خواهیم داشت: https://vrgl.ir/0JsOI موفق باشید ?