المحتوى عن 'repository'.



مزيد من الخيارات

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المُحتوى


التصنيفات

  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • نصائح وإرشادات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • مقالات عامة
  • التجارة الإلكترونية

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
    • Magento
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
  • HTML5
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • برمجة أندرويد
  • لغة R
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات عامّة

التصنيفات

  • تجربة المستخدم
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
    • كوريل درو
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
  • مقالات عامّة

التصنيفات

  • خواديم
    • الويب HTTP
    • قواعد البيانات
    • البريد الإلكتروني
    • DNS
    • Samba
  • الحوسبة السّحابية
    • Docker
  • إدارة الإعدادات والنّشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • مقالات عامة

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات

التصنيفات

  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • مقالات عامة

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجينتو
  • أندرويد
  • iOS
  • macOS
  • ويندوز

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

  • الأقسام
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة البرمجة
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات
    • أسئلة الشهادات المتخصصة

التصنيفات

  • ريادة الأعمال
  • العمل الحر
  • التسويق والمبيعات
  • البرمجة
  • التصميم
  • DevOps

تمّ العثور على 10 نتائج

  1. هذا الدرس هو الجزء الثالث والأخير من سلسلة دروس تُعنى بكيفيّة بناء تطبيق عملي بسيط لإدارة جهات اتصال ببيانات أوليّة وهو بطبيعة الحال جزء من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms. ستحتاج إلى قراءة الجزأين الأوّل والثاني السابقين لكي تستطيع المتابعة في هذا الدرس. سنهتم في هذا الدرس ببناء واجهتي التطبيق: الواجهة الرئيسيّة التي يمكن من خلالها البحث عن جهات الاتصال حسب الاسم والكنية، وأيضًا إضافة جهة اتصال جديدة. الواجهة الخاصة بعرض تفاصيل جهة الاتصال التي تمّ اختيارها من الواجهة الرئيسيّة، ومن ثمّ إمكانيّة تعديل بياناتها أو حتى حذفها. بالإضافة إلى تعديل الصنف App لكي يصبح التطبيق قابل للعمل. الواجهة الرئيسيّة للتطبيق ContactsPage سنحتاج في البداية إلى إضافة مجلّد جديد سنسمّه Pages سنضع فيه أي صفحة جديدة للتطبيق، وهذا الإجراء هو من باب تنظيم مكوّنات التطبيق فحسب. من نافذة مستكشف الحل Solution Explorer انقر بزر الفأرة الأيمن على اسم المشروع ContactsApp (Portal) ثم اختر من القائمة التي ستظهر الخيار Add. من القائمة الفرعية، اختر New Folder ثمّ سمّه Pages. انقر بزر الفأرة الأيمن على المجلّد الذي أضفته توًا وهو Pages واختر من القائمة Add ثم اختر من القائمة الفرعية New Item لتظهر نافذة تسمح باختيار العنصر الجديد الذي تودّ إضافته. اختر صفحة محتوى تعتمد على الرماز كما وأن سبق لنا أن فعلنا ذلك في هذا الدرس. سمّ هذه الصفحة ContactsPage. انتقل الآن إلى الملف ContactsPage.xaml واحرص على أن تكون محتوياته على الشكل التالي: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="ContactsApp.Pages.ContactsPage" Appearing ="ContactsPage_Appearing"> <StackLayout> <StackLayout Padding="5,25,5,5"> <StackLayout Orientation="Horizontal"> <Label Text="First Name" /> <Editor x:Name="txtFirstName" HorizontalOptions="FillAndExpand" TextChanged="Editor_TextChanged"/> </StackLayout> <StackLayout Orientation="Horizontal"> <Label Text="Last Name" /> <Editor x:Name="txtLastName" HorizontalOptions="FillAndExpand" TextChanged="Editor_TextChanged"/> </StackLayout> <Button Text="Find" x:Name="btnFind" Clicked="btnFind_Clicked"/> </StackLayout> <StackLayout> <ListView x:Name="lsvContacts" ItemTapped="lsvContacts_ItemTapped"/> </StackLayout> <Button Text="+ New Contact" HorizontalOptions="FillAndExpand" Clicked="btnNewContact_Clicked"/> </StackLayout> </ContentPage> لقد استخدمنا أربعة أحداث مختلفة في هذه الصفحة سنورد وصفًا قصيرًا لكل منها فيما يلي: حدث الظهور Appearing للصفحة ContactsPage وقد ربطناه بالمعالج ContactsPage_Appearing وذلك لتحديث بيانات الصفحة عند العودة من الصفحة المسؤولة عن عرض تفاصيل جهة الاتصال كما سنرى لاحقًا في هذا الدرس. حدث تغيّر النص TextChangedلكلّ من العنصرين txtFirstName و txtLastName وقد ربطناه بالمعالج Editor_TextChanged وذلك لمسح نتائج البحث التي تظهر ضمن القائمة lsvContacts عند أي تعديل يجريه المستخدم فيهما. حدث لمس المُدخل ضمن القائمة lsvContacts وربطناه بالمعالج lsvContacts_ItemTapped وذلك لكي ينتقل البرنامج إلى الواجهة المسؤولة عن عرض تفاصيل جهة الاتصال عند لمسها. حدث النقر Clicked لزر إضافة جهة اتصال جديدة وربطناه بالمعالج btnNewContact_Clicked. انتقل بعد ذلك إلى ملف الشيفرة البرمجيّة الموافق للملف السابق وهو ContactsPage.xaml.cs واحرص على أن تكون محتوياته على الشكل التالي: using System; using Xamarin.Forms; using ContactsApp.Entities; namespace ContactsApp.Pages { public partial class ContactsPage : ContentPage { public ContactsPage() { InitializeComponent(); } private async void DoFind() { string firstName = txtFirstName.Text == null ? "" : txtFirstName.Text; string lastName = txtLastName.Text == null ? "" : txtLastName.Text; lsvContacts.ItemsSource = await((App)Application.Current).ContactsRepository .GetContactsAsync(firstName, lastName); } private void btnFind_Clicked(object sender, EventArgs e) { DoFind(); } private void Editor_TextChanged(object sender, TextChangedEventArgs e) { if (lsvContacts.ItemsSource != null) lsvContacts.ItemsSource = null; } private async void lsvContacts_ItemTapped(object sender, ItemTappedEventArgs e) { Contact selectedContact = (Contact)e.Item; ContactDetailsPage ContactDetailsPage = new ContactDetailsPage(selectedContact); await Navigation.PushAsync(ContactDetailsPage); } private void ContactsPage_Appearing(object sender, EventArgs e) { DoFind(); } private async void btnNewContact_Clicked(object sender, EventArgs e) { ContactDetailsPage ContactDetailsPage = new ContactDetailsPage(null); await Navigation.PushAsync(ContactDetailsPage); } } } يحتوي هذا الملف على الصنف ContactsPage والذي يحتوي على معالجات الأحداث التي صرّحنا عنها في صفحة الرماز الموافقة له كما رأينا قبل قليل، بالإضافة إلى وجود تابع خدمي وهو DoFind ووظيفته إجراء عمليّة بحث وفقًا للمعايير التي يرغبها المستخدم (الاسم والكنية). الشيفرة البرمجيّة الموجودة ضمن معالجات الأحداث بسيطة وواضحة. ولكن أريد أن أتوقّف قليلًا عند الشيفرة البرمجيّة التي يستخدمها التابع الخدمي DoFind التي تنفّذ عمليّة البحث: string firstName = txtFirstName.Text == null ? "" : txtFirstName.Text; string lastName = txtLastName.Text == null ? "" : txtLastName.Text; lsvContacts.ItemsSource = await((App)Application.Current).ContactsRepository .GetContactsAsync(firstName, lastName); أوّل سطرين واضحان حيث يعملان على الحصول على معايير البحث التي يريدها المستخدم من مربّعي النص txtFirstName و txtLastName. أمّا السطر الأخير فهو يعمل على الاتصال بالمستودع لتنفيذ عمليّة البحث وإرجاع النتائج وذلك بإجراء استدعاء إلى التابع GetContactsAsync وتمرير معياري البحث إليه. انظر هذه العبارة: await((App)Application.Current).ContactsRepository .GetContactsAsync(firstName, lastName); تعمل الخاصية Application.Current على إرجاع كائن من النوع Application يمثّل التطبيق الحالي الذي نعمل من خلاله. والذي يحتاج بدوره إلى عملية تحويل cast باستخدام التحويل (App) إلى كائن من النوع App وهو الصنف الأساسي في التطبيق. في الحقيقة لقد صرّحت عن الخاصيّة ContactsRepository في الصنف App ليتم الوصول للمستودع من أيّ مكان من تطبيقنا. سنرى التصريح عن هذه الخاصيّة بعد قليل. واجهة تفاصيل جهة الاتصال ContactDetailsPage أضف هذه الواجهة بنفس الأسلوب الذي اتبعناه عند إضافة ملف الواجهة الرئيسيّة، أي إلى المجلّد Pages. على أن يكون اسمها ContactDetailsPage. سيصبح مستكشف الحل لديك شبيه بما يلي: انتقل إلى ملف الرماز ContactDetailsPage.xaml واحرص على أن تكون محتوياته على الشكل التالي: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="ContactsApp.Pages.ContactDetailsPage"> <StackLayout Orientation="Vertical" Padding="5,25,5,5"> <Label Text="FirstName" /> <Entry x:Name ="txtFirstName"/> <Label Text="LastName" /> <Entry x:Name ="txtLastName"/> <Label Text="Tel" /> <Entry x:Name ="txtTel"/> <Label Text="EMail" /> <Entry x:Name ="txtEMail"/> <Label Text="Hobbies" /> <Editor x:Name ="txtHobbies"/> <StackLayout Orientation="Horizontal"> <Button Text="Save" x:Name="btnSave" Clicked="btnSave_Clicked"/> <Button Text="Delete" x:Name="btnDelete" Clicked="btnDelete_Clicked"/> </StackLayout> </StackLayout> </ContentPage> واضح أنّه ملف بسيط. هناك حدثان قد صرّحنا عنهما في هذه الصفحة وهما: حدث النقر لزر الحفظ Clicked وقد ربطناه بالمعالج btnSave_Clicked وهو مسؤول عن عمليتي الحفظ والإضافة. حدث النقر لزر الحذف Clicked وقد ربطناه بالمعالج btnDelete_Clicked وهو مسؤول عن حذف جهة الاتصال. انتقل إلى ملف الشيفرة البرمجيّة الموافق لملف الرماز السابق واسمه ContactDetailsPage.xaml.cs واحرص على أن تكون محتوياته على الشكل التالي: using System; using Xamarin.Forms; using ContactsApp.Entities; namespace ContactsApp.Pages { public partial class ContactDetailsPage : ContentPage { private Contact currentContact; public ContactDetailsPage(Contact contact) { InitializeComponent(); this.currentContact = contact; if (this.currentContact != null) { txtFirstName.Text = contact.FirstName; txtLastName.Text = contact.LastName; txtEMail.Text = contact.EMail; txtTel.Text = contact.Tel; txtHobbies.Text = contact.Hobbies; Title = contact.ToString() + " Details"; btnDelete.IsVisible = true; } else { Title = "New Contact"; btnDelete.IsVisible = false; } } private async void btnSave_Clicked(object sender, EventArgs e) { string firstName = txtFirstName.Text == null ? "" : txtFirstName.Text; string lastName = txtLastName.Text == null ? "" : txtLastName.Text; string tel = txtTel.Text == null ? "" : txtTel.Text; string email = txtEMail.Text == null ? "" : txtEMail.Text; string hobbies = txtHobbies.Text == null ? "" : txtHobbies.Text; if (this.currentContact != null) { this.currentContact.FirstName = firstName; this.currentContact.LastName = lastName; this.currentContact.EMail = tel; this.currentContact.Tel = email; this.currentContact.Hobbies = hobbies; await ((App)Application.Current).ContactsRepository .UpdateContactAsync(this.currentContact); } else { Contact contact = new Contact { FirstName = firstName, LastName = lastName, Tel = tel, EMail = email, Hobbies = hobbies, }; await ((App)Application.Current).ContactsRepository .AddContactAsync(contact); } await Navigation.PopAsync(); } private async void btnDelete_Clicked(object sender, EventArgs e) { var result = await DisplayAlert("Delete Confirmation", "Are you sure you want to delete this contact?", "Yes", "No"); if (result) { await ((App)Application.Current).ContactsRepository .DeleteContactAsync(this.currentContact); await Navigation.PopAsync(); } } } } كما وأوضحنا مسبقًا أنّ وظيفة هذه الواجهة هي عرض تفاصيل جهة اتصال وتعديلها، مع إمكانيّة حذفها، بالإضافة إلى استخدام هذه الواجهة في إضافة جهة اتصال جديدة أيضًا. تميّز هذه الواجهة الغرض المطلوب منها عن طريق كائن من النوع Contact يُمرّر كوسيط إلى بانيتها. إذا كان هذا الوسيط يحتوي على كائن صالح من النوع Contact فهذا يعني أنّنا نريد عرض جهة الاتصال التي يمثّلها هذا الكائن، ومن ثمّ تعديلها أو حذفها، ويقتضي ذلك بالطبع إظهار زر الحذف Delete. أمّا في حال تمّ تمرير null كوسيط إلى البانية، فهذا يعني أنّه تمّ استدعاء الصفحة لإنشاء جهة اتصال جديدة تمامًا، وبالتالي إخفاء زر الحذف Delete لأنّه لن يكون له معنى في هذه الحالة. يتم حفظ نسخة من الكائن الممرّر ضمن حقل خاص ضمن الصنف اسمه currentContact. ويتم تحديد الغاية من الواجهة ضمن البانية نفسها. انظر إلى تعريف معالج الحدث btnSave_Clicked من الشيفرة السابقة وانظر كيف يتعامل مع الحالتين السابقتين (تعديل أو إضافة). الصنف App انتقل إلى الملف App.cs واحرص على أن تكون محتوياته على الشكل التالي: using Xamarin.Forms; using ContactsApp.Abstract; using ContactsApp.Concrete; using ContactsApp.Pages; namespace ContactsApp { public class App : Application { public IContactsRepository ContactsRepository { get; set; } public App() { ContactsRepository = new MemoryContactsRepository(); // The root page of your application MainPage = new NavigationPage(new ContactsPage()); } protected override void OnStart() { // Handle when your app starts } protected override void OnSleep() { // Handle when your app sleeps } protected override void OnResume() { // Handle when your app resumes } } } لاحظ بدايةّ الخاصيّة ContactsRepository من النوع IContactsRepository الموجود ضمن تعريف الصنف App: public IContactsRepository ContactsRepository { get; set; } في الحقيقة ستمثّل هذه الخاصيّة المستودع الذي سنتبادل البيانات من خلاله. سنعمل على إنشاء كائن جديد من النوع MemoryContactsRepository ومن ثمّ نسنده إلى هذه الخاصيّة وذلك ضمن بانية الصنف App كما يلي: ContactsRepository = new MemoryContactsRepository(); وهذا الأمر جائز تمامًا لأنّ الصنف MemoryContactsRepositroy يحقّق الواجهة IContactsRepository فهو بمثابة وارث منها. بما أنّ هذه الخاصيّة عامّة public فسيكون بإمكان جميع أجزاء التطبيق الوصول للكائن ContactsRepository وبالتالي التعامل مع البيانات من خلال مكان واحد. لاحظ أيضًا من بانية الصنف App كيف أنشأنا كائن جديد من الصنف NavigationPage وأسندناه إلى الخاصيّة MainPage للصنف App. وذلك لأنّنا نريد أن يدعم تطبيقنا ميزة التنقّل بين الصفحات التي تحدثنا عنها في هذا الدرس. يمكنك الآن تنفيذ التطبيق وتجربة جميع المزايا التي يتمتّع بها. الخلاصة نكون بهذا الدرس قد أنهينا بناء تطبيق جهات الاتصال، حيث تحدثنا عن كيفيّة بناء واجهتي التطبيق، وكيفيّة التنقّل بين هاتين الواجهتين، وكيف تتصّلان بالمستودع عند الحاجة للتعامل مع مصدر البيانات الذي يكون في تطبيقنا هذا عبارة عن مجموعة تتكوّن من عناصر من النوع Contact موجودة في ذاكرة التطبيق.
  2. سنبدأ في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms ببناء تطبيق عملي بمعايير تقنيّة عالية. حيث سنستخدم المعارف التي حصلنا عليها من الدروس السابقة في بناء تطبيق جهات اتصال بسيط لكنّه يستخدم تقنيّات ومفاهيم متقدّمة نسبيًّا. سنتناول هذا التطبيق على ثلاثة أجزاء متتالية، إليك وصف مختصر لمحتوى كلّ منها: الجزء الأوّل: شرح الغاية من التطبيق، وتوضيح فكرة نموذج المستودع Repository في بناء التطبيقات، مع بناء الهيكل العام للتطبيق، وهذا هو محتوى هذا الدرس. الجزء الثاني: تجهيز النواحي الوظيفيّة للمستودع وجعله قابلًا للاستخدام. الجزء الثالث: تنفيذ واجهتي التطبيق الرئيسية والفرعيّة الخاصّة بعرض التفاصيل. وتنفيذ عمليّة التنقّل بين الواجهتين الغاية من التطبيق وكيف يعمل فكرة التطبيق بسيطة للغاية، تتلخّص بعرض جهات اتصال موجودة مسبقًا وإمكانية البحث ضمنها، مع إمكانيّة إضافة جهات اتصال جديدة وتحريرها وحذفها. يعتمد التطبيق على وجود واجهتين. الواجهة الأولى هي الواجهة الرئيسيّة وتحتوي على قسم خاص بالبحث حسب الاسم أو الكنية عن أيّ جهة اتصال موجودة مسبقًا، بالإضافة إلى قائمة لعرض جهات الاتصال الناتجة عن عمليّة البحث، وأخيرًا زر خاص بإضافة جهات اتصال جديدة. انظر الشكل التالي الذي ينتج عند ضغط زر البحث FIND عند عدم تحديد أي معيار للبحث: عندما يقوم المستخدم بنقر زر البحث FIND دون أن يحدّد أي معيار، سيقوم التطبيق بعرض جميع جهات الاتصال الموجودة لديه، والتي ستكون في هذه النسخة من البرنامج عبارة عن بيانات وهمية موجودة ضمن ذاكرة التطبيق. أمّا عند تحديد المستخدم للاسم أو الكنيّة فسيعمل التطبيق على البحث مستخدمًا منطق AND. أمّا الواجهة الثانية، فتظهر عندما يلمس المستخدم إحدى جهات الاتصال من القائمة السابقة، حيث تعرض هذه الواجهة بيانات تفصيليّة حول جهة الاتصال هذه: الاسم والكنية ورقم الهاتف وعنوان البريد الإلكتروني والهوايات. انظر إلى الشكل التالي: من الممكن تعديل أيّ من هذه البيانات ثم ينقر المستخدم زر الحفظ لحفظها، أو أن ينقر زر الرجوع إلى الواجهة السابقة الموجود في الأعلى بجانب أيقونة البرنامج في حال لم يرغب بتعديل البيانات. كما يمكن للمستخدم أن يحذف جهة الاتصال هذه بنقره على زر الحذف Delete كما يظهر من الشكل السابق. وهذه ببساطة فكرة التطبيق. نموذج المستودع Repository عندما تكبر التطبيقات وتتنوّع المهام المطلوبة منها تبرز الحاجة لوسيلة لتنظيم العمل داخل التطبيق. في الحقيقة توجد العديد من النماذج التي تدعمها Xamarin لهذه الغاية مثل نموذج MVVM الذي يستخدم بفعالية ضمن Xamarin لتنظيم وفصل الأجزاء المسؤولة عن الواجهات عن الأجزاء المسؤولة عن منطق العمل عن تلك المسؤولة عن التعامل مع مزودات البيانات البعيدة أو المحلية باختلاف أنواعها. من النماذج التي أفضلها شخصيًّا هو نموذج المستودع Repository الذي أستخدمه على نحو واسع في جميع أنواع التطبيقات التي أعمل عليها. فهو أسلوب جميل ومنطقي ويسمح بتطوير التطبيق بشكل سلس وسريع للعمل في مختلف أنواع البيئات، وهو متوافق للعمل مع نموذج MVVM. يسمح نموذج المستودع بعزل الشيفرة البرمجيّة المسؤولة عن التعامل مع البيانات عن منطق البرنامج business logic. وفي هذا الأمر عدة فوائد من أهمّها: تنظيم البرنامج، وجعله أكثر قابليّة للفهم والتطوير. إجراء تطوير على أسلوب التعامل مع البيانات دون إجراء أي تغيير في منطق عمل البرنامج. إمكانيّة إجراء تغيير جذري لنوع الخدمة التي نستخدمها لتخزين البيانات دون تغيير يُذكر في منطق العمل. سأخوض مباشرةً في كيفية اعتماد هذا النموذج في تطبيقنا هذا. حيث سنحتاج إلى استخدام واجهة واحدة Interface مع صنف واحد يُحقّقها. لتنعش ذاكرتك حول الواجهات انظر هذا الدرس. لنبدأ الآن في بناء هذا التطبيق وذلك في الفقرة التالية. بناء التطبيق ابدأ بإنشاء مشروع جديد من النوع Blank App (Xamarin.Forms Portable) وسمّه ContactsApp ثم أبق فقط على المشروعين ContactsApp (Portable) و ContactsApp.Droid كما وسبق أن فعلنا في هذا الدرس. من نافذة مستكشف الحل Solution Explorer انقر بزر الفأرة الأيمن على المشروع ContactsApp واختر من القائمة التي ستظهر الخيار Add ثم من القائمة الفرعية الخيار New Folder لإضافة مجلّد جديد. سمّ هذا المجلّد بالاسم Entities، وبعد أن يظهر في نافذة الحل Solution Explore انقر عليه بزر الفأرة الأيمن واختر الخيار Add ومن القائمة الفرعية اختر Class. ستظهر نافذة تسمح لك بتعيين اسم لهذا الصنف. اختر الاسم Contact له. هذا الصنف هو حجر البناء الأساسي لهذا البرنامج والذي يمثّل منطق العمل فيه. احرص على جعل محتويات الملف Contact.cs كما يلي: namespace ContactsApp.Entities { public class Contact { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Tel { get; set; } public string EMail { get; set; } public string Hobbies { get; set; } public override string ToString() { return string.Concat(FirstName, " ", LastName); } } } يحتوي الصنف Contact كما يظهر من الشكل السابق على البيانات الأساسيّة التي تحتاجها أيّة جهة اتصال، بالإضافة إلى خاصيّة الهوايات Hobbies التي قد تبدو غريبة قليلًا بالنسبة لجهة اتصال. انقر مرّة أخرى بزر الفأرة الأيمن على المشروع ContactsApp ثم اختر من القائمة التي ستظهر الخيار Add ثم من القائمة الفرعية الخيار New Folder لإضافة مجلّد جديد. سمّ هذا المجلّد بالاسم Abstract، وبعد أن يظهر في نافذة الحل Solution Explore انقر عليه بزر الفأرة الأيمن واختر الخيار Add ومن القائمة الفرعية اختر New Item. ستظهر نافذة تسمح لك بتعيين نوع العنصر المراد إضافته. اختر واجهة Interface وعيّن الاسم IContactsRepository لها. واحرص على أن تكون محتويات الملف IContactsRepository.cs كما يلي: using System.Threading.Tasks; using System.Collections.ObjectModel; using ContactsApp.Entities; namespace ContactsApp.Abstract { public interface IContactsRepository { Task<ObservableCollection<Contact>> GetContactsAsync(string firstName, string lastName); Task<bool> AddContactAsync(Contact contactToAdd); Task<bool> UpdateContactAsync(Contact contactToUpdate); Task<bool> DeleteContactAsync(Contact contactToDelete); } } تُستَخدَم الواجهات عمومًا عندما نرغب بتجريد Abstraction الأمور وجعلها عموميّةً وفي ذلك فائدة كبيرة في جعل الشيفرة البرمجيّة أكثر قابليّة للفهم ولإعادة الاستخدام. وهذا سبب إضافة هذه الواجهة إلى المجلّد Abstract. لا تحتوي الواجهات على أيّة شيفرة برمجيّة كما نعلم، فكل ما تحتويه هو عبارة عن تصاريح لتوابع يجب تحقيقها ضمن أيّ صنف يرغب بتحقيق هذه الواجهة. تحتوي هذه الواجهة باختصار على العمليّات الأساسيّة التي يحتاجها تطبيقنا لإنجاز المهام المنوطة به وهي: الحصول على جهات الاتصال حسب الاسم والكنية GetContactsAsync، وإضافة جهة اتصال جديدة AddContactAsync، وتحديث جهة اتصال موجودة مسبقًا UpdateContactAsync، وحذف جهة اتصال DeleteContactAsync. أمّا سبب وجود الكلمة Async في كلّ من هذه التوابع فهو للإشارة إلى أنّه يُفترض بها أن تستخدم تقنيّة البرمجة غير المتزامنة Asynchronous Programming التي تحدثنا عنها في هذا الدرس. المثير في الأمر أنّ هذه الواجهة لا تهتم بمكان وجود البيانات أو كيفيّة الحصول عليها والتعامل معها. إنّما تهتم فقط بما يحتاجه التطبيق وبشكل مجرّد. سنكرّر الآن نفس العمليّة لإضافة مجلّد جديد ضمن المشروع ContactsApp.cs واسمه Concrete وهو الذي سيحتوي على الصنف الذي سيحقّق الواجهة IContactsRepository السابقة. انقر بزر الفأرة الأيمن على هذا المجلّد واختر Add. ومن القائمة الفرعية اختر Class. سمّ هذا الصنف بالاسم MemoryContactsRepository واحرص على أن تكون محتوياته كما يلي: using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Collections.ObjectModel; using ContactsApp.Abstract; using ContactsApp.Entities; namespace ContactsApp.Concrete { public class MemoryContactsRepository : IContactsRepository { private ObservableCollection<Contact> contacts; public MemoryContactsRepository() { contacts = new ObservableCollection<Contact>() { new Contact() { Id=1, FirstName = "Ahmad", LastName="Saeed", Tel="123456", EMail="admin@example.com", Hobbies="Swimming" }, new Contact() { Id=2, FirstName = "Mahmood", LastName="Maktabi", Tel="852136", EMail="info@example.com", Hobbies="Reading" }, new Contact() { Id=3, FirstName = "Mazen", LastName="Najem", Tel="987456", EMail="it@example.com", Hobbies="Swimming" }, new Contact() { Id=4, FirstName = "Sawsan", LastName="Hilal", Tel="741258", EMail="sales@example.com", Hobbies="Writing, Reading" }, new Contact() { Id=5, FirstName = "Musab", LastName="Aga", Tel="357159", EMail="admin@example.com", Hobbies="Sport" } }; } public async Task<ObservableCollection<Contact>> GetContactsAsync(string firstName, string lastName) { throw new System.NotImplementedException(); } public async Task<bool> AddContactAsync(Contact contactToAdd) { throw new System.NotImplementedException(); } public async Task<bool> UpdateContactAsync(Contact contactToUpdate) { throw new System.NotImplementedException(); } public async Task<bool> DeleteContactAsync(Contact contactToDelete) { throw new System.NotImplementedException(); } } } الأمر الملفت للنظر هنا أنّ بيانات جهات الاتصال موجودة ضمن هذا الصنف بالفعل وتحديدًا ضمن بانيته. وهي مخزّنة ضمن المتغيّر contacts وهو معرّف على مستوى الصنف ومن النوع العمومي ObservableCollection الذي سنتحدّث عنه في الدرس التالي. يكفي الآن أن تعرف أنّه عبارة عن مجموعة عناصرها كائنات من النوع Contact وهي تُفيد في التطبيقات التي تُستخدَم فيها البرمجة غير المتزامنة. من غير الواقعي بكل تأكيد وجود البيانات مخزّنة في الصنف بهذه الطريقة، فالمفترض أن تكون ضمن قاعدة بيانات محليّة أو بعيدة أو حتى ضمن ملف عادي. في الواقع تبرز هنا قوّة نموذج المستودع Repository في عزل أسلوب الحصول على البيانات عن البرنامج الفعلي. من الواضح أيضًا أنّ التوابع الموجودة هنا تحتوي في الحقيقة على شيفرة برمجيّة تؤدّي إلى رمي الاستثناء NotImplementedException وذلك لتذكرينا بعدم جاهزيتها بعد. سيبدو مستكشف الحل في نهاية المطاف شبيهًا بالشكل التالي: الخلاصة هذا الدرس هو المقدّمة لتطبيق جهات الاتصال الذي سنتابع بناءه على مدى الدرسين التاليين. تناولنا في هذا الدرس فكرة التطبيق الأساسيّة، وتوضيح فكرة نموذج المستودع Repository من خلال بناء واجهة تمثّله بالإضافة إلى صنف يحقّقها. سيمكننا من خلال هذا الصنف (صنف المستودع) التعامل مع بيانات موجودة ضمن ذاكرة التطبيق فقط. ورغم كون هذا الأسلوب غير واقعي، إلَّا أنّه ضروري في تبسيط الأمور وجعلها أسهل للفهم. كما أنشأنا صنف يمثّل جهة الاتصال في التطبيق. ورأينا ماهية العلاقة بينه وبين صنف المستودع، من خلال الاستدعاءات إلى التوابع الموجودة ضمن الصنف الأخير. حقوق الصورة البارزة محفوظة لـ Freepik
  3. سنتابع في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms العمل الذي بدأناه في الدرس السابق والمتمثّل ببناء تطبيق جهات الاتصال. قد أنهينا في الدرس السابق بناء نموذج المستودع من خلال التصريح عن الواجهة IContactsRepository وتحقيقها من خلال الصنف MemoryContactsRepository. كما أنشأنا الصنف Contacts الذي يمثّل حجر البناء الأساسي في التطبيق. سنضيف في هذا الدرس النواحي الوظيفيّة للصنف المستودع MemoryContactsRepository لكي يصبح تطبيقنا قابلًا للعمل. تجهيز النواحي الوظيفيّة للمستودع افتح الملف MemoryContactsRepository.cs واحرص على أن تكون محتوياته على الشكل التالي: using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Collections.ObjectModel; using ContactsApp.Abstract; using ContactsApp.Entities; namespace ContactsApp.Concrete { public class MemoryContactsRepository : IContactsRepository { private ObservableCollection<Contact> contacts; public MemoryContactsRepository() { contacts = new ObservableCollection<Contact>() { new Contact() { Id=1, FirstName = "Ahmad", LastName="Saeed", Tel="123456", EMail="admin@example.com", Hobbies="Swimming" }, new Contact() { Id=2, FirstName = "Mahmood", LastName="Maktabi", Tel="852136", EMail="info@example.com", Hobbies="Reading" }, new Contact() { Id=3, FirstName = "Mazen", LastName="Najem", Tel="987456", EMail="it@example.com", Hobbies="Swimming" }, new Contact() { Id=4, FirstName = "Sawsan", LastName="Hilal", Tel="741258", EMail="sales@example.com", Hobbies="Writing, Reading" }, new Contact() { Id=5, FirstName = "Musab", LastName="Aga", Tel="357159", EMail="admin@example.com", Hobbies="Sport" } }; } public async Task<ObservableCollection<Contact>> GetContactsAsync(string firstName, string lastName) { return await Task.Factory.StartNew(() => { IEnumerable<Contact> result = from contact in contacts where contact.FirstName .ToUpper() .Contains(firstName.ToUpper()) && contact.LastName .ToUpper() .Contains(lastName.ToUpper()) select contact; ObservableCollection<Contact> tmp = new ObservableCollection<Contact>(result); return tmp; }); } public async Task<bool> AddContactAsync(Contact contactToAdd) { return await Task.Factory.StartNew(() => { contactToAdd.Id = contacts.Count() + 1; contacts.Add(contactToAdd); return true; }); } public async Task<bool> UpdateContactAsync(Contact contactToUpdate) { return await Task.Factory.StartNew(() => { Contact result = (from contact in contacts where contact.Id == contactToUpdate.Id select contact).FirstOrDefault(); if (result != null) { contactToUpdate.FirstName = result.FirstName; contactToUpdate.LastName = result.LastName; contactToUpdate.EMail = result.EMail; contactToUpdate.Tel = result.Tel; contactToUpdate.Hobbies = result.Hobbies; return true; } else { return false; } }); } public async Task<bool> DeleteContactAsync(Contact contactToDelete) { return await Task.Factory.StartNew(() => { var result = (from contact in contacts where contact.Id != contactToDelete.Id select contact); if (result != null) { contacts = new ObservableCollection<Contact>(result); contactToDelete = null; return true; } else { return false; } }); } } } الجديد هنا أنّنا قد أسندنا شيفرة برمجيّة لكل من التوابع الأساسيّة الموجودة في المستودع. انظر إلى الفقرات التالية التي تشرح عمل الشيفرة البرمجيّة ضمن كل تابع. تابع البحث GetContactsAsync فيما يلي التابع GetContactsAsync والذي يتطلّب وسيطين من النوع string للبحث حسب الاسم والكنية لجهات الاتصال: public async Task<ObservableCollection<Contact>> GetContactsAsync(string firstName, string lastName) { return await Task.Factory.StartNew(() => { IEnumerable<Contact> result = from contact in contacts where contact.FirstName .ToUpper() .Contains(firstName.ToUpper()) && contact.LastName .ToUpper() .Contains(lastName.ToUpper()) select contact; ObservableCollection<Contact> tmp = new ObservableCollection<Contact>(result); return tmp; }); } واضح أنّ هذه الشيفرة تقوم بإنشاء مهمة جديدة (تابع على شكل تعبير lambda) عن طريق التابع Task.Factory.StartNew (ستحتاج إلى إنعاش ذاكرتك بهذا الدرس) وذلك باستخدام تقنية البرمجة غير المتزامنة لمنع جمود التطبيق كما نعلم. المتغيّر result الذي تراه في الشيفرة السابق هو من النوع IEnumerable<Contact> أي مجموعة قابلة للعد عناصرها من النوع Contact، وهو يحصل على هذه المجموعة من خلال استعلام LINQ To Objects بسيط: from contact in contacts where contact.FirstName .ToUpper() .Contains(firstName.ToUpper()) && contact.LastName .ToUpper() .Contains(lastName.ToUpper()) select contact; تعمل هذه الشيفرة ببساطة على الاستعلام عن جميع جهات الاتصال الموجودة ضمن المتغير contacts والتي تحتوي على عبارتي البحث الخاصّتين بالاسم والكنية بنفس الوقت. إذا كان لديك خبرة باستعلامات SQL فستجد LINQ to Objects مألوفة. لاحظ وجود عبارتي return ضمن هذا التابع GetContactsAsync. العبارة التي تأتي أولًا هي العبارة المسؤولة عن إرجاع كائن من النوع Task<ObservableCollection<Contact>> أمّا العبارة الثانيّة التي تأتي في الأسفل فهي المسؤولة عن إرجاع كائن من النوع Task<ObservableCollection<Contact>> من التابع الداخلي وهو عبارة عن تعبير lambda والذي يتم تنفيذه من خلال التابع Task.Factory.StartNew كما هو واضح. في آخر سطرين من التابع الداخلي (تعبير lambda) نعمل على تقديم نتيجة البحث على شكل مجموعة عموميّة وهي ObservableCollection<Contact> حيث يتم تحويلها ضمنيًّا إلى كائن من النوع Task<ObservableCollection<Contact>>. تابع إضافة جهة اتصال جديد AddContactAsync التابع AddContactAsync يتطلّب وسيطًا واحدًا فقط من النوع Contact ويُرجع كائن من النوع Task<bool> للإشارة إلى نجاح عمليّة الإضافة: public async Task<bool> AddContactAsync(Contact contactToAdd) { return await Task.Factory.StartNew(() => { contactToAdd.Id = contacts.Count() + 1; contacts.Add(contactToAdd); return true; }); } تعمل هذه الشيفرة على إضافة جهة اتصال جديدة إلى جهات الاتصال الموجودة مسبقًا ضمن المتغيّر contacts مع الانتباه إلى هذه العبارة البرمجيّة: contactToAdd.Id = contacts.Count() + 1; ما تفعله هذه العبارة غير موجود في أيّ تطبيق عمليّ حقيقي. فهي تُسند قيمة للخاصيّة Id لجهة الاتصال الممرّرة إلى التابع. لا ينبغي لأي تطبيق أن يقوم بهذا العمل، فهذا الرقم ينبغي أن يتم توليده تلقائيًّا عند إضافة جهة الاتصال إلى قاعدة البيانات. وبما أنّ تطبيقنا يعتمد على بيانات وهمية موجودة في الذاكرة فكان لزامًا علينا القيام بمثل هذا الأمر لتمييز جهات الاتصال المضافة حديثًا. تابع الحذف DeleteContactAsync التابع DeleteContactAsync يتطلّب وسيطًا واحدًا فقط من النوع Contact ويُرجع كائن من النوع Task<bool> للإشارة إلى نجاح عمليّة الحذف: public async Task<bool> DeleteContactAsync(Contact contactToDelete) { return await Task.Factory.StartNew(() => { var result = (from contact in contacts where contact.Id != contactToDelete.Id select contact); if (result != null) { contacts = new ObservableCollection<Contact>(result); contactToDelete = null; return true; } else { return false; } }); } تعمل الشيفرة السابقة في الواقع على فلترة جهة الاتصال المراد حذفها مما يعني ببساطة أنّ استعلام LINQ to Objects سيعمل على إرجاع جميع جهات الاتصال باستثناء تلك التي يكون قيمة الخاصية Id لها مطابقة لقيمة الخاصيّة Id لجهة الاتصال التي نرغب بحذفها. ومن ثمّ يتم إسناد النتيجة إلى المتغيّر contacts مما يعني فعليًّا أنّنا قد حذفنا جهة الاتصال هذه من الذاكرة. تابع التحديث UpdateContactAsync التابع UpdateContactAsync والذي يتطلّب وسيطًا واحدًا فقط من النوع Contact ويُرجع كائن من النوع Task<bool> للإشارة إلى نجاح عمليّة الحذف: public async Task<bool> UpdateContactAsync(Contact contactToUpdate) { return await Task.Factory.StartNew(() => { Contact result = (from contact in contacts where contact.Id == contactToUpdate.Id select contact).FirstOrDefault(); if (result != null) { contactToUpdate.FirstName = result.FirstName; contactToUpdate.LastName = result.LastName; contactToUpdate.EMail = result.EMail; contactToUpdate.Tel = result.Tel; contactToUpdate.Hobbies = result.Hobbies; return true; } else { return false; } }); } تخضع هذه الشيفرة البرمجيّة لنفس المبدأ: يبحث استعلام LINQ to Objects عن جهة الاتصال مطلوبة ضمن جهات الاتصال الموجودة ضمن المتغيّر contacts حسب قيمة الخاصيّة Id لجهة الاتصال، وبعد الحصول عليها، يتم تحديث قيم الخصائص الأخرى كما هو واضح. بهذه الطريقة أصبح نموذج المستودع جاهزًا لوضعه في الاستخدام. حيث ستتعامل معه جميع مكوّنات التطبيق لإنجاز أربعة أنواع مختلفة من العمليّات على البيانات: البحث، والإضافة، والتعديل، والحذف. الخلاصة تناولنا في هذا الدرس كيفيّة بناء نموذج المستودع الذي يمثّله الصنف MemoryContactsRepository والذي يحقّق كما نعلم الواجهة IContactsRepository حيث تعرّفنا على التوابع الأربعة ضمنه التي تسمح لمكوّنات التطبيق بالتعامل مع البيانات دون الاهتمام بمكان وجود هذه البيانات. اطلعنا أيضًا على كيفيّة توظيف تقنيّة الاستعلام LINQ to Objects ضمن التوابع الأربعة لتنفيذ المهام المطلوبة من هذا المستودع. سنبدأ في الدرس التالي ببناء واجهات التطبيق. حقوق الصورة البارزة محفوظة لـ Freepik
  4. قبل أن نذهب إلى الموضوع الرئيسيّ للمقال، سأعطيك لمحة قصيرة عن مشاكل التصميم التي قد تواجهها. لقد اشتكى لي أحد زبائني بأن بعض الصفحات تفتح ببطء شديد. وعندما أقول ببطء شديد، فإنني أعني ذلك! فقررت أن أصحح تلك الصفحة (بعمل debugging)، وما رأيته قد صدمني. لقد أظهر لي قسم الاستعلامات (queries) أن تلك الصفحة كانت تنفَّذ بعد القيام بكم هائل من الاستعلامات تعدّى 16500 استعلامًا!! لقد وجدت أن جزءًا من النصّ البرمجي هو سبب تلك المشكلة. لقد كانت هناك ثلاث حلقات foreach تستعلم عن خاصيّة والخواص الفرعيّة التابعة لها. لقد كانت تعمل جيدًا إلى أن صار في قاعدة البيانات 5500 عنصرًا. وفيما يلي ما كان يحدث: $main_object = MainObject::all(); foreach($main_object as $object) { echo $object->some_property; foreach($object->related_object as $related) { echo $related->some_property; echo $related->another_property; } foreach($object->another_related as $another) { echo $another->some_property; echo $another->another_property; } }إذا كان الاستعلام ;()main_object = MainObject::all$ يعيد 5500 نتيجة، فستعيد حلقة foreach الأولى ذلك القدر أيضًا، وكذلك بالنسبة للثانية والثالثة. باستخدام ORM، كثيرًا ما يقع المبرمجون في فخّ كتابة استعلامات قواعد بيانات غير كفؤة، وتجعلها ORM أكثر صعوبة في الاكتشاف. تُسمّى هذه المشكلة بمشكلة N+1 (بالإنجليزيّة N+1 problem). وأظن المطور السابق لم يكن على علم بذلك. ولتفادي هذه المشكلة، نستخدم التحميل الحثيث (eager loading). ما هو التحميل الحثيث؟لتبسيط الأمر، التحميل الحثيث طريقة تُعنى بعمل كل شيء عند الطلب. وهذه الطريقة أيضًا على العكس تمامًا من التحميل الكسول (lazy loading) عندما ننفذ المهام عند الحاجة. يساعدنا التحميل الحثيث على تجنب مشكلات الأداء، كما رأيت في مثالي أعلاه. ستفهم الأمر أكثر من خلال مثال، لذا لنتخيل الوضع التالي: لدينا نموذج علاقة هيئة محسّنة (بالإنجليزيّة: Enhanced Entity Relationship، واختصارًا EER)، بثلاث هيئات، كلّ منها مرتبطة بالأخرى. يمكنك أن تقرأ EER كما يلي: يمكن لكل عضو أن يملك العديد من المحلات، ولكن المحل الواحد ملك لعضو واحد فقط. يمكن للمحل الواحد أن يحوي العديد من المنتجات، ولكن المنتج الواحد لا يكون إلّا في محل واحد. الخطوة التالية هي إنشاء نماذج Eloquent لهذه الهيئات: العضو: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Member extends Model { protected $fillable = ['username', 'email', 'first_name', 'last_name']; public function stores() { return $this->hasMany('App\\Store'); } }المحلّ: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Store extends Model { protected $fillable = ['name', 'slug', 'site', 'member_id']; public function member() { return $this->belongsTo('App\\Member'); } public function products() { return $this->hasMany('App\\Product'); } }المُنتَج: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $fillable = ['name', 'short_desc', 'long_desc', 'price', 'store_id', 'member_id']; public function store() { return $this->belongsTo('App\\Store'); } }تخيّل أنك تبني تطبيقًا يسمح لمستخدميك أن يُنشئوا محالّهم التجاريّة الخاصّة. يمكن للمستخدمين –كما هو الحال بالنسبة للمحال الأخرى كلها طبعًا– أن يُنشئوا منتَجات عديدة. وأيضًا، يمكننا أن ننشئ صفحة واحدة تعرض كل المحلات وأفضل المنتجات لكل محلّ. شيء من قبيل هذا: يمكن أن ينتهي بك المطاف إلى الحصول على شيء كهذا في المتحكّم لديك: <?php namespace App\Http\Controllers; use App\Repositories\StoreRepository; class StoresController extends Controller { protected $stores; function __construct(StoreRepository $stores) { $this->stores = $stores; } public function index() { $stores = $this->stores->all(); return \View::make('stores.index')->with('stores', $stores); } }وفي العرض الذي ستقدم فيه تلك البيانات: @foreach($stores as $store) <h1>{{ $store->name }}</h1> <span>Owner: {{ $store->member->first_name . ' ' . $store->member->last_name }}</span><br> <h2>Products:</h2> @foreach($store->products as $product) <h3>{{$product->name}}</h3> <span>{{$product->short_desc}}</span><br/><br/> <span>Price: {{$product->price}}</span><br/> <?php Debugbar::info('Product displayed'); ?> @endforeach <br/>========================<br/> @endforeachوالنتيجة كالتالي: ومن اجل هذا المثال، زوّدت قاعدة البيانات بخمس مستخدمين، وثلاثة محالّ، وأربعة منتَجات. يقوم الاستعلام الأول باستدعاء كل المحال من قاعدة البيانات، وهذا هو الجزء +1 من مشكلة N+1. في هذا المثال تحديدًا، حرف N يمثّل عدد المحلات التي أرجعها لنا الاستعلام الأول، حيث أنها تمثل عدد المرات التي سنقوم فيها بالاستعلام select * from على جدولي products و members. وبما أن لدينا 3 محلات، فسنستعلم 3 مرات على جدول المستخدمين، وثلاث مرات على جدول المنتجات. وفي النهاية، قمنا بتنفيذ الاستعلامات بعدد مرات قدره 3+3+1. تخيل الآن ما الذي يمكن أن يحدث لو كان لديك 5000 أو 10000 محل؟ سيكون لديك في تلك الحالة عشرة آلاف إلى عشرين ألف استعلام في كل مرة يقوم فيها أحد المستخدمين بزيارة الصفحة. وماذا لو كانت لديك عشرة آلاف أو مئة ألف زيارة كلّ أربع وعشرين ساعة؟ هذا كابوس! من الواضح الآن أن هذا التوجّه مدمّر للأداء. وبغض النظر عن نوع قاعدة البيانات التي تستخدمها، وعن مدى قوة الخادم الذي لديك، فستصل دائمًا إل تلك النقطة التي يقف فيها العتاد القوي لديك عاجزًا. يمكنك أن تحسّن الأداء بعمل cache لهذه الاستعلامات، باستخدام Redis على سبيل المثال. سيؤدي هذا الغرض، ولكن لبعض الوقت فقط. وبتلك الطريقة، أنت فقط تؤجل النهاية الحتميّة التي ستكلّفك الكثير من المال والوقت، وفي الغالب ستفقد بعض الزبائن، أو أنّ قاعدة بياناتك ستضعف كثيرًا. وهنا يأتي التحميل الحثيث لينقذك من هذه الورطة. استخدام التحميل الحثيث في Laravel بسيط للغاية. العلاقات التي ترغب أن يتم تحميلها بشكل حثيث تحددها في طريقة with كما يلي: $stores = Store::with('member','products')->get();الآن، بدل استخدام 7 استعلامات، قلّلنا باستخدام التحميل الحثيث عدد الاستعلامات إلى 3 فقط: وستكون ثلاثة استعلامات حتى ولو كانت لديك عشرة آلاف مدخلة في جدول المحلات. وكما ترى، فإن الاستخدام السليم للتحميل الحثيث يمكن أن يؤدي إلى تحسين أداء تطبيقك بقدر هائل. ولكي نحصل على تحسن للأداء بالفعل، فعلينا أن نوجد فهرسًا لحقل الهويّة id في جدولي members و products. ومع وجود كمّ هائل من السجلات، فإن تنفيذ (... ,'in( '1', '2 على حقل غير مفهرس سيأخذ وقتًا طويلًا. وبعد هذه المقدمة عن التحميل الحثيث، هيا بنا نرى كيف يمكننا أن نستخدم العلاقات مع المستودعات. تمديد فئة المستودعسأريك طريقة واحدة يمكنك فيها أن تستخدم العلاقات في فئات مستودعات concrete. وهنا مثال عن النتيجة النهائيّة: function __construct(StoreRepository $stores) { $this->stores = $stores; } public function index() { $stores = $this->stores->with('member', 'products')->all(); .... }وكما ترى هنا، لدينا طريقة with يمكنك أن تسلسل فيها نموذج العلاقات. ستكون هذه الطريقة شبيهة بطريقة with في Laravel’s Query Builder. public function with($relations) { if (is_string($relations)) $relations = func_get_args(); $this->with = $relations; return $this; }نحتاج الآن لأن نربط كلّ علاقة من العلاقات التي قمنا بتقديمها بالنموذج: protected function eagerLoadRelations() { if(!is_null($this->with)) { foreach ($this->with as $relation) { $this->model->with($relation); } } return $this; }وها هو ذا، والشيء الوحيد الذي تبقّى هو أن نحدّث طريقة مستودع ()all (وأي شيء آخر ترغب بتحديثه) لاستخدام التحميل الحثيث: public function all($columns = array('*')) { $this->applyCriteria(); $this->newQuery()->eagerLoadRelations(); return $this->model->get($columns); }وكما سبق وذكرت، فيمكنك أن تضيف عدّة علاقات ضمن طريقة ()with. وفيما يلي مثال على StoresControler: <?php namespace App\Http\Controllers; use App\Repositories\StoreRepository; class StoresController extends Controller { protected $stores; function __construct(StoreRepository $stores) { $this->stores = $stores; } public function index() { $stores = $this->stores->with('member', 'products')->all(); return \View::make('stores.index')->with('stores', $stores); } }وفي العرض يمكنك أن تعرض البيانات بالطريقة التي تريدها، ولغرض التجربة يكفي هذا: @foreach($stores as $store) <h1>{{ $store->name }}</h1> <span>Owner: {{ $store->member->first_name . ' ' . $store->member->last_name }}</span><br> <h2>Products:</h2> @foreach($store->products as $product) <h3>{{$product->name}}</h3> <span>{{$product->short_desc}}</span><br/><br/> <span>Price: {{$product->price}}</span><br/> <?php Debugbar::info('Product displayed'); ?> @endforeach <br/>========================<br/> @endforeachوكما هو متوقع، لدينا الآن هذه الاستعلامات الثلاثة فقط: الخلاصةيمكنك باستخدام التحميل الحثيث أن تحسّن أداء تطبيقك. وأحيانًا، عندما يكبر التطبيق، حتى التحميل الحثيث ليس كافيًا للحفاظ على أعلى أداء. في الدرس التالي سأريك كيف يمكنك تجميل مستودعاتك لتقوم بعمل cache للاستعلامات من أجل أداء أفضل. ترجمة -وبتصرف- للمقال: Using Repository Pattern in Laravel 5 - Eloquent Relations and Eager Loading.
  5. نواصل معكم في هذا المقال استعراض أساسيات إدارة الحزم في أوبنتو ودبيان. إذا لم تقرأ الجزء الأول فأنصحك بقراءته أوّلا قبل مواصلة قراءة هذا المقال. إزالة الحزم وحذف الملفات العمليات المعاكسة لعملية التنصيب والتحميل الحِزم متاحة أيضًا من مدير الحِزم. هذا القسم سيناقش كيفية إزالة تنصيب الحِزم وتنظيف الملفات التي قد تبقى كنتيجة لعمل الحزمة. إزالة حزمة لإزالة حزمة منصبة، يجب إضافة الأمر الفرعي remove إلى الأمر apt-get. سيقوم هذا الأمر بإزالة الملفات التي قامت الحزمة بتنصيبها على نظامك مع استثناء جدير بالاهتمام. الأمر يُبقي ملفات الإعدادات محفوظة في مكانِ ما في حال تم إعادة تنصيب الحزمة مرة أخرى فيما بعد. تعتبر هذه الطريقة مفيدة في حالة الحذف المفاجئ للحزمة؛ حيث لا تضيع جهودك هدرًا في إعادة ضبط الإعدادات وتخصيصها. لإكمال العملية يلزمك تزويد الأمر باسم الحزمة المراد إزالتها أو إلغاء تصيبها: sudo apt-get remove package ستتم إزالة الحزمة، باستثناء ملف الإعدادات. إزالة حزمة وكل ملفات الإعدادات ذات العلاقة إذا كنت ترغب في إزالة حزمة وكل الملفات المتعلقة بها من نظامك، متضمنة ملفات الإعدادات، يمكنك استخدام الأمر الفرعي purge من الأمر apt-get. بخلاف الأمر remove -الذي تم ذكره بالأعلى- فإن الأمر purge يزيل كل شيء. تعتبر هذه الطريقة مفيدة في حال عدم رغبتك الاحتفاظ بملفات الإعدادات، أو في حال ما كانت لديك بعض المشاكل وترغب في البدء من الصفر. تجدر الإشارة هنا إلى أن ملفات الإعدادات لا يمكن استرجاعها في حال تم حذفها: sudo apt-get purge package في هذه الحالة، عندما تقوم بإعادة تنصيب الحزمة مرة أخرى فسيتم استخدام الإعدادات الافتراضية. إزالة الاعتماديات التلقائية التي لم تعد لها حاجة تتم إزالة الحِزم من النظام باستخدام الأوامر apt-get remove أو apt-get purge، لكن بعض هذه الحزم كان قد تطلّب وقت التنصيب بعض الاعتماديات لاستكمال التنصيب، وبعد إزالة الحزمة تبقى هذه الاعتماديات. لإزالة أي حِزم تم تنصيبها كاعتماديات ولم تعد مطلوبة لأي حزمة أخرى، يمكنك تنفيذ الأمر autoremove كما يلي: sudo apt-get autoremove إذا كنت ترغب في إزالة كل ملفات الإعدادات المتعلقة من الاعتماديات التي تمت إزالتها، يجب إضافة purge-- إلى الأمر autoremove. سيقوم هذا الأمر بإزالة ملفات الإعدادات مثل وظيفة الأمر purge: sudo apt-get --purge autoremove تنظيف ملفات الحزم المهجورة Obsolete بعض الحِزم تُصبح قديمة ومهجورة مع مرور الوقت. بإمكان الأداة apt-get إزالة أيٍ من ملفات هذه الحِزم عن النظام المحلي، تلك الملفات المرتبطة بالحِزم والتي لم تعد متاحة من قِبل المستودعات باستخدام الأمر autoclean. يقوم هذا الأمر بتحرير المزيد من المساحة على الخادوم، ويسمح بتحديث الذاكرة المؤقتة على النظام المحلي وتجنّب الاحتفاظ ببيانات غير مستخدمة. sudo apt-get autoclean الحصول على معلومات حول الحزم كل حزمة تحتوي على كمية كبيرة من البيانات التي يمكن الوصول إليها من خلال أدوات إدارة الحِزم. في هذا القسم سنقوم بتسليط الضوء على بعض الطرق الشائعة للحصول على المعلومات حول الحزم المتاحة والمنصبة. عرض المعلومات حول حزمة لعرض المعلومات التفصيلية حول حزمة في التوزيعة التي تستخدمها، يمكنك استخدام الأمر الفرعي show من الأمر apt-cache. هذا الأمر يعتمد على اسم الحزمة في المستودع: apt-cache show package سيقوم الأمر بعرض معلومات حول الحِزم التي تظهر في نتاج البحث عن تلك الحزمة التي يتم الاستفسار عنها. كل حزمة مرشحة تتضمن معلومات حول اعتمادياتها، الإصدار، التركيبة أو المعمارية، التعارضات، الاسم الفعلي للحزمة، حجم الحزمة وحجم تنصيبها، الوصف التفصيلي، بالإضافة إلى معلومات أخرى. لعرض المزيد من المعلومات حول كل حزمة مرشحة، شاملة لقائمة كاملة للاعتماديات العكسية ( قائمة الحِزم التي تعتمد على الحزمة التي نستفسر عنها) استخدم الأمر showpkg بدلا من الأمر المذكور أعلاه، والذي سيتضمن الكثير من المعلومات حول علاقة هذه الحزمة بالحِزم الأخرى: apt-cache showpkg package عرض المعلومات حول حزمة deb. لعرض تفاصيل حول ملف deb. يمكنك استخدام المؤشر info-- مع الأمر dpkg، الهدف لهذا الأمر يجب أن يكون مسار ملف deb.: dpkg --info debfile.deb سيعرض لك هذا الأمر بعض البيانات الوصفية حول الحزمة التي تستفسر عنها، المعلومات تتضمن اسم الحزمة وإصدارها، المعمارية التي بُنيت لها، الحجم والاعتماديات المطلوبة، الوصف والتعارضات. عرض الاعتماديات والاعتماديات العكسية لعرض الاعتماديات (اعتماد حزمة على أخرى) بشكل دقيق، وكذلك الاعتماديات العكسية (الحِزم التي تعتمد على هذه الحزمة) يمكنك استخدام الأداة apt-cache. لعرض معلومات الاعتماديات بشكل جيد، يمكنك استخدام الأمر الفرعي depends: apt-cache depends package سيقوم الأمر بعرض معلومات حول كل حِزمة تم التعرف عليها كاعتمادية، بالإضافة إلى الاقتراحات، التوصيات أو التعارضات. إذا كنت ترغب في معرفة أي حِزم تعتمد على حزمة معينة، يمكنك إضافة اسم الحزمة إلى الأمر الفرعي rdepends: apt-cache rdepends package عرض إصدارات الحزمة المنصبة والمتاحة في الكثير من الأحيان تتوفر عدّة إصدارات من حزمة ما في المستودع، مع حزمة واحدة افتراضية. لعرض الإصدارات المتاحة لحزمة ما، يمكنك استخدام الأمر الفرعي policy من الأمر apt-cache: apt-cache policy package سيقوم الأمر بعرض أي إصدار تم تنصيبه (إذا كان الحزمة منصبة)، الحزمة المرشحة والتي سيتم تنصيبها بشكل افتراضي إذا لم تقم بتحديد إصدار في أمر التنصيب، بالإضافة إلى جدول إصدارات الحزم، منتهيًا بمؤشر يشير إلى أولوية كل إصدار. يمكنك استخدام تلك الطريقة لتحديد أي إصدار سيتم تنصيبه والبدائل المتاحة. ولأن هذه الطريقة ترصد المستودعات التي يتواجد فيها كل إصدار، فإنه يمكن استخدامها لتحديد إذا ما توافرت مستودعات أخرى أو الأرشيف الشخصي للحِزم (PPAs) تقوم بنسخ الحِزم من المستودع الافتراضي. عرض الحزم المنصبة باستخدام الأمر dpkg -l لعرض الحِزم التي تم تنصيبها على نظامك، لديك القليل من الخيارات، اعتمادًا على هيئة المخرجات، وكذلك حجم المعلومات المطلوبة. الطريقة الأولى تتضمن استخدام dpkg أو dpkg-query مع l-. المخرجات من الأمرين كليهما ستكون متطابقة، بلا شك ستعطي قائمة لكل حزمة منصبة (جزئيا أو كليّا) على النظام، وشكلها كما يلي: dpkg -l Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-===========================================-=======================================-============-===================================================================================================================== ii account-plugin-generic-oauth 0.10bzr13.03.26-0ubuntu1.1 amd64 GNOME Control Center account plugin for single signon - generic OAuth ii accountsservice 0.6.34-0ubuntu6 amd64 query and manipulate user account information ii acl 2.2.52-1 amd64 Access control list utilities ii acpi-support 0.142 amd64 scripts for handling many ACPI events ii acpid 1:2.0.18-1ubuntu2 amd64 Advanced Configuration and Power Interface event daemon . . . ستستمر المخرجات لكل حزمة في النظام على المخرجات يمكنك رؤية مدلولات الحروف الثلاثة الأولى في كل سطر. الحرف الأولى يشير إلى الحالة المرغوب فيها للحزمة، وتكون أحد ما يلي: u: غير معروف i: تم التنصيب r: تمت إزالتها. p: تمت إزالتها كليّا ( تنظيف بعد الإزالة) h: إصدار معلّق. الحرف الثاني يشير إلى الحالة الحقيقة للحزمة كما هو معروف لنظام التحزيم Packaging system، يمكن أن تكون إحدى الحالات التالية: n: لم يتم تنصيبها. i: تم تنصيبها. c: ملف الإعدادات موجود، لكن الحزمة تمت إزالتها. u: تم فك ضغط الحزمة لكن لم يُشرع في إعدادها بعد. f: الحزمة منصبة (جزئيًا) مما يعني حدوث فشل أثناء عملية التنصيب مما لم يسمح باكتمال عملية التنصيب. w: الحزمة تنتظر حدثًا من حزمة منفصلة. p: الحزمة تم تفعيلها من قِبل حزمة أخرى. الحرف الثالث، والذي يكون عبارة عن مسافة فارغة لأغلب الحِزم، له خيار واحد كما يلي: r: إشارة إلى ضرورة إعادة التنصيب، مما يفيد أن الحزمة غير مكتملة ولا يمكن الاستفادة من وظائفها. باقي الأعمدة تحتوي اسم الحزمة، الإصدار، المعمارية وكذلك الوصف. عرض حالة التنصيب للحِزم التي تم ترشيحها إذا قمت بإضافة معيار بحث بعد l- ، فإن dpkg سيعرض كل الحِزم التي تحوي معيار البحث، سواءً كانت منصّبة أم لا، على سبيل المثال، نستطيع البحث عن عمليات المكتبات YAML كما يلي: dpkg -l libyaml* Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-===============-============-============-=================================== ii libyaml-0-2:amd 0.1.4-2ubunt amd64 Fast YAML 1.1 parser and emitter li ii libyaml-dev:amd 0.1.4-2ubunt amd64 Fast YAML 1.1 parser and emitter li un libyaml-perl <none> (no description available) un libyaml-syck-pe <none> (no description available) ii libyaml-tiny-pe 1.51-2 all Perl module for reading and writing كما ترى في العمود الأول، النتيجة الثالثة والرابعة غير منصبة. هذا الأمر يعرض كل حزمة يتوفر فيها معيار البحث، بالإضافة إلى الحالة الحالية والمرغوب فيها. عرض الحِزم المنصبة بالأمر dpkg --get-selections يمكنك استعراض الحِزم المنصبة على نظامك بطريقة أخرى، باستخدام مؤشر get-selections-- مع الأداة dpkg، حيث يزودنا هذا الأمر بقائمة لكل الحِزم التي تم تنصيبها، والتي تمت إزالتها دون التنظيف بعد الإزالة: dpkg --get-selections للتفريق بين تلك الحالتين، يمكنك استخدام awk للتصفية حسب الحالة. لاستعراض الحِزم المنصّبة فقط، اطبع: dpkg --get-selections | awk '$2 ~ /^install/` لعرض قائمة الحِزم التي تمت إزالتها لكن لم يتم إزالة ملفات الإعدادات، يمكنك استخدام الأمر: dpkg --get-selections | awk '$2 !~ /^install/' البحث ضمن الحزم المنصبة للبحث في الحِزم المنصبة عن حزمة معينة، يمكنك إضافة نص التصفية/الترشيح بعد خيار get-selections--. هذا الأمر يستخدم البدائل لمحاولة العثور على تطابق ما. مرة أخرى، يقوم هذا الأمر بعرض أي حزمة منصبة، أو ملفات الإعدادات الخاصة بها ما زالت على النظام: dpkg --get-selections libz* مرة أخرى، يمكنك التصفية باستخدام تعبيرات awk من القسم أعلاه. استعراض الملفات المنصبة من خلال حزمة لاستعراض الملفات الخاصة بحزمة ما، يمكنك استخدام مؤشر L- مع الأمر dpkg: dpkg -L package سيقوم هذا الأمر بطباعة المسار الكامل لكل ملف خاص بتلك الحزمة. بالطبع لن تتضمن القائمة ملفات الإعدادات التي يتم إنشاؤها من خلال عمليات الحزمة البحث عن ما تنصبه الحزمة لموقع ما لاستكشاف أي حزمة مسؤولة عن ملف معين في نظام الملفات، يمكنك إضافة الملف الكامل إلى الأمر dpkg مع مؤشر S-. سيقوم الأمر بطباعة اسم الحزمة التي قامت بتنصيب الملف: dpkg -S /path/to/file تجدر الإشارة إلى أن أي ملفات تم نقلها إلى مكان ما من خلال عملية ما بعد التنصيب، لا يمكن ربطها بحزمتها من خلال هذا الأمر. معرفة أي حزمة تقوم بتوفير ملف ما دون تنصيبها قد تحتاج في بعض الأحيان لمعرفة أي حزمة تزودك بملف ما أو أمر ما، حتى بدون أن تقوم بتنصيب الحزمة على نظامك. من السهل معرفة إذا ما كانت حزمة معينة تحتوي ملفًا ما باستخدام dpkg بإضافة الخيار S-. لعمل ذلك يلزم تنصيب الأداة apt-file. تمتلك هذه الأداة قاعدة بيانات لمعلوماتٍ حول الحزم، من ضمن هذه المعلومات مسار التنصيب لكل ملف خاص بالحزمة من قاعدة البيانات تلك. قم بتنصيب الأداة بتنفيذ: sudo apt-get update sudo apt-get install apt-file الآن، قم بتحديث قاعدة البيانات الخاصة بالأداة وقم بالبحث عن ملف بتنفيذ: sudo apt-file update sudo apt-file search /path/to/file سيعمل هذا الأمر فقط للمواقع التي تم تنصيبها مباشرة بواسطة حزمة ما. لن يتم إيجاد الملفات التي تم إنشاؤها بواسطة برامج ما بعد التثبيت. تحويل قوائم الحزم بين الأنظمة في كثير من الأحيان قد تحتاج لعمل نسخة احتياطية لقائمة الحِزم التي تم تنصيبها على نظامك، واستخدامها لتنصيب نفس القائمة من الحِزم على نظام آخر. بالطبع تعتبر هذه الطريقة مفيدة لأغراض النسخ الاحتياطي. هذا القسم يوضح آلية تصدير واستيراد قوائم الحِزم. تصدير قائمة الحزم إذا كنت ترغب في تكرار مجموعة الحزم التي تم تنصيبها على نظامك إلى نظام آخر، يتوجب عليك في البداية تصدير قائمة الحزم. يمكنك تصدير قائمة الحزم المنصّبة إلى ملف بتلك الصيغة المطلوبة لاحقًا لعمية الاستيراد عن طريق مخرجات الأمر dpkg --get-selections : dpkg --get-selections > ~/packagelist.txt يمكنك نسخ هذه القائمة إلى نظام أو حاسوب آخر واستيرادها. قد تحتاج أيضًا لأخذ نسخة احتياطية لقوائم المصادر وقائمة المفاتيح الموثوقة. يمكنك أخذ نسخة احتياطية من المصادر بإنشاء مجلد بالملفات الضرورية ونسخها: mkdir ~/sources cp -R /etc/apt/sources.list* ~/sources المفاتيح الموثوقة يمكن نسخها احتياطيًا بتنفيذ: apt-key exportall > ~/trusted_keys.txt الآن يمكنك تحويل الملف packagelist.txt ومجلد sources والملف trusted_keys.txt إلى حاسوب آخر بغرض استيرادها. استيراد قائمة الحزم إذا قمت بإنشاء قائمة الحزم باستخدام dpkg --get-selections كما تم شرحه أعلاه، بإمكانك استيراد الحِزم على حاسوب آخر باستخدام الأمر dpkg . في البداية يجب أن تقوم بإضافة مفاتيح الثقة، وتحضير قوائم المصادر التي تم نسخها من الحاسوب الأول. على افتراض أنك قمت بنسخ جميع الملفات المنقولة إلى مجلد home في الحاسوب الثاني، يمكنك تنفيذ: sudo apt-key add ~/trusted_keys.txt sudo cp -R ~sources/* /etc/apt/ ثم قم بإزالة الحالة عن الحزِم غير الضرورية من الحاسوب الجديد. مما يعني أنك تقوم بتطبيق التغييرات من جديد. يجب تطبيق هذا من خلال حساب root أو صلاحيات sudo: sudo dpkg --clear-selections سيقوم الأمر باختيار كل الحِزم التي لا ترغب بتنصيبها. يجب أيضا تحديث قائمة الحِزم المحلية لضمان أن عمليات التنصيب سيكون لديها سجل أو معلومات لكل البرامج التي نحتاج تنصيبها. عملية التنصيب والتحدث الفعلية سيتم معالجتها من خلال أداة dselect. يجب التأكد أن الأداة dselect قد تم تنصيبها. تمتلك هذه الأداة قاعدة بيانات خاصة بها، لذا يجب تحديثها قبل الاستكمال: sudo apt-get update sudo apt-get install dselect sudo dselect update الآن نستطيع تطبيق قائمة الحزم فوق القائمة الحالية؛ لمعرفة أي حزم يجب الاحتفاظ بها أو تحميلها: sudo dpkg --set-selections < packagelist.txt يقوم هذا الأمر بضبط حالات الحزم التي نريدها. لتطبيق التغييرات يجب تطبيق الأمر الفرعي dselect-upgrade من الأمر apt-get: sudo apt-get dselect-upgrade سيقوم هذا الأمر بتحميل وتنصيب الحزم الضرورية. وسيقوم بإزالة الحزم التي لم يتم اختيارها. وفي النهاية فإن قائمة الحزم يجب أن تتطابق مع تلك التي تم استيرادها من الحاسوب الأول، بغض النظر عن أن ملفات الإعدادات ما زالت تحتاج لأن يتم نسخها أو تعديلها. إضافة المستودعات والأرشيف الشخصي للحزم PPA تحتوي المجموعة الافتراضية من المستودعات التي يتم توفيرها في أغلب التّوزيعات على حِزم كافية لأغلب المستخدمين. بالرغم من ذلك فإنك تحتاج في الكثير من الأحيان إلى مصادر إضافية. سنقوم في هذا القسم بمناقشة آلية أعداد أدوات الحزم لتتعامل مع مصادر إضافية. إضافة أرشيف شخصي للحزم يعتبر الأرشيف الشخصي للحزم (PPAs) بديلا عن المستودعات التقليدية. الأرشيف الشخصي للحزم متاح فقط لأنظمة أوبنتو، حتى كتابة هذا المقال. عادة ما يتميز الأرشيف الشخصي للحزم بمجال أصغر من المستودعات، ويحتوي مجموعة مركزة من التطبيقات التي يتم الاحتفاظ بها مِن خلال مَن يمتلك الأرشيف الشخصي للحِزم. إضافة أرشيف الشخصي للحزم لنظامك، يسمح لك بإدارة الحِزم التي يحتويها مع أدوات إدارة الحِزم التي اعتدت عليها. هذه الطريقة ستزودنا بحزمة أو مجموعة من الحِزم المحدّثة والتي لا يتضمنها مستودع الحِزم الخاص بالتوزيعة. يجب الانتباه إلى ضرورة إضافة الأرشيف الشخصي للحزم الذي تثق به، حيث أنك تسمح لحافظة غير رسمية ببناء الحِزم لنظامك. لإضافة أرشيف الشخصي للحزم، يمكنك استخدام الأمر add-apt-repository. يجب أن تضيف الاسم ppa:، متبوعًا باسم مالك الأرشيف الشخصي للحزم على Launchpad، شرطة مائلة ثم اسم الأرشيف الشخصي للحزم: sudo add-apt-repository ppa:owner_name/ppa_name قد يتطلب الأمر الموافقة على قبول مفتاح صاحب الحِزم. بعد ذلك سيتم إضافة الأرشيف الشخصي للحزم لنظامك، مما يسمح لك بتنصيب الحزم بأوامر apt الاعتيادية. قبل البحث عن الحزم أو تنصيبها، قمت بالتأكد من تحديث ذاكرة التخزين المؤقت المحلية بالمعلومات حول الأرشيف الشخصي الجديد للحِزم: sudo apt-get update إضافة مستودعات لإضافة المزيد من المستودعات لأنظمة أوبنتو أو دبيان، يمكنك اتباع أحدِ مسارين. المسار الأول يتم بتعديل قوائم المصادر لديك مباشرة. بإمكانك تعديل ملف etc/apt/sources.list/ أو استبدال ملف قائمة جديد في مجلد /etc/apt/sources.list.d/. إذا قمت باستخدام الطريقة الثانية فيجب الانتباه إلى أن اسم الملف الذي قمت بإنشائه يجب أن ينتهي بـ list.: sudo nano /etc/apt/sources.list.d/new_repo.list يمكنك إضافة موقع المستودع الجديد في الملف الجديد باستخدام الشكل التالي: deb_or_deb-src url_of_repo release_code_name_or_suite component_names الأجزاء المختلفة لمواصفات المستودع موضحة كما يلي: deb أو deb-src: لتحديد نوع المستودع. تستخدم deb لتحديد المستودع العادي، بينما مستودعات المصادر تبدأ بـ deb-src. url: العنوان الرئيسي للمستودع. يجب أن يكون الموقع الذي يتواجد فيه المستودع. release code name or suite: عادة ما يكون الاسم الرسمي لإصدار التوزيعة، لكن قد يكون أي اسم مستخدم لتعريف مجموعة محددة من الحِزم التي تم إنشاؤها للإصدار الذي تستخدمه من التوزيعة. component names: علامات لاختيار الحِزم التي ترغب بتوفرها. وتعتبر ميزة يتم توفيرها من القائمين على التوزيعة؛ لتقديم توضيح حول المصداقية أو قيود الترخيص للبرامج التي تحتويها. باستطاعتك إضافة هذه الأسطر بداخل الملف. معظم المستودعات تحتوي معلومات حول الهيئة التي يجب استخدامها. الطّريقة الأخرى لإضافة المزيد من المستودعات يتم باستخدام الأمر add-apt-repository. توزيعات أوبنتو تحتوي هذا الأمر بشكل افتراضي، بينما يمكن تنصيبها إلى توزيعات دبيان من خلال حزمة software-properties-common: sudo apt-get update sudo apt-get install software-properties-common بعد ذلك يمكن إضافة الأسطر التي تريد إضافتها إلى الأمر add-apt-repository. بالطبع يجب أن تكون الإضافات بنفس الهيئة أو الشكل الذي تستخدمه في حالة الإضافات اليدوية: sudo add-apt-repository 'deb url release component' يجب التأكد من تحديث الذاكرة المؤقتة المحلية للحِزم بعد تطبيق أي تحديث للمستودع؛ كي يكون النظام على وعي بالحِزم المتاحة حديثًا: sudo apt-get update خاتمة هناك العديد من عمليات إدارة الحِزم التي يمكنك إجراؤها، لكننا حاولنا تغطية أغلب الخطوات الشائعة هنا. إذا كان لديك أي تفضيلات أو اهتمامات أخرى، يرجى استخدام قسم التعليقات أدناه لنعرفها. ترجمة -وبتصرف- للمقال Ubuntu and Debian Package Management Essentials لصاحبه Justin Ellingwood.
  6. سنتناول في هذا المقال الأسئلة العشرة الأكثر تكرارًا حول Git والحلول المتعلقة بها. ستساعد هذه الحلول في تجاوز بعض العقبات عند استخدام Git. 1. كيف أقوم بتحرير رسالة إيداع commit خاطئة في Git؟ وهو سؤال يطرح بكثرة من طرف كل من بدأ لتوّه باستخدام Git. لتحرير رسالة إيداع commit خاطئة قمنا بكتابتها، نستخدم الأوامر التالية: git commit --amend سيقوم الأمر بفتح المحرّر الذي سيسمح بتغيير أحدث إيداع. إن أردنا تحرير رسالة الإيداع مباشرة عبر سطر الأوامر، يمكننا القيام بذلك باستخدام الأمر: git commit --amend -m "الرسالة الجديدة" 2. كيف أتراجع عن آخر إيداع commit كاملا؟ للتراجع عن إيداع بعض الملفّات نقوم في البداية بإلغاء آخر إيداع باستخدام الأمر: git reset --hard HEAD~1 سيقوم هذا الأمر بنقل مؤشر الرأس HEAD إلى الإيداع قبل الأخير. بعد ذلك، نقوم بعرض حالة جميع الملفات مرّة أخرى باستخدام الأمر: git status وأخيرًا، نضيف الملفات التي نودّ إضافتها إلى الإيداع باستخدام الأمر: git add ... 3. كيف أتراجع عن إضافة ملف git add؟ للتراجع عن إضافة بعض أو كل الملفّات إلى منطقة الإدراج بالأمر المذكور، نستخدم أحد الأوامر التالية: للتراجع عن ملف وحيد: git reset <FileName> للتراجع عن جميع الملفّات: git reset 4. كيف أحذف تفرع branch (محلي أو بعيد)؟ لحذف تفرّع محلّي، نستخدم الأمر: git branch -d yourLocalBranchName ولحذف تفرّع بعيد، نستخدم الأمر: git push origin --delete yourRemoteBranchName 5. كيف أحذف ملفات محلية من التفرع الحالي لحذف جميع الملفّات المحلّية التي لا نرغب بإيداعها أو الحفاظ عليها في مجلّد العمل، نستخدم الأمر: git clean -f أو: git reset --hard 6. كيف أعيد تسمية تفرع محلي؟ لإعادة تسمية تفرّع محلّي، لدينا حالتان: إعادة تسمية لتفرّع الحالي: git branch -m newBranchName إعادة تسمية تفرّع آخر ليس التفرّع النّشط الحالي: git branch -m oldBranchName newBranchName 7. كيف أنشئ تفرعا بعيدا remote Git branch؟ لإنشاء تفرّع بعيد، نقوم أوّلًا بإنشاء تفرّع محلّي: git checkout -b newBranchName ومن ثم نقوم بدفع التفرّع الذي أنشأناه إلى الخادم البعيد: git push origin newBranchName 8. كيف أغير الرابط الخاص بمستودع بعيد remote Git repository؟ لتغيير الرابط الخاص بمستودع بعيد (لنفترض أنه التفرّع origin) نستخدم الأمر التالي: git remote set-url origin newURL 9. كيف أغير اسم من قام بالإيداع؟ يمكن تغيير اسم من قام بالإيداع في سجلّ المُستودع باستخدام السكربت التالي المأخوذ من مقالات موقع github.com: git filter-branch --env-filter ' an="$GIT_AUTHOR_NAME" am="$GIT_AUTHOR_EMAIL" cn="$GIT_COMMITER_NAME" cm="$GIT_COMMITTER_EMAIL" if[ "$GIT_COMMITTER_EMAIL" = "your@email.to.match" ] then cn="New Committer Name" cm="New Committer Email" fi if[ "$GIT_AUTHOR_EMAIL" = "your@email.to.match" ] then an="New Author Name" am="New Author Email" fi export GIT_AUTHOR_NAME="$an" export GIT_AUTHOR_EMAIL="$am" export GIT_COMMITTER_NAME="$cn" export GIT_AUTHOR_EMAIL="$cm"' 10. كيف أستنسخ مستودعا في مجلد معين؟ لاستنساخ مستودع في مجلّد معيّن، نحتاج إمّا: الانتقال بمسار سطر الأوامر إلى داخل ذلك المجلّد وتنفيذ الأمر: git clone <URL> أو ننفّذ الأمر: git clone <URL> <yourFolderName> حيث أن yourFolderName هو اسم/مسار المُجلّد المعني بالأمر. ترجمة -وبتصرّف- للمقال Top 10 frequently asked questions on Git لصاحبه Hemant Joshi.
  7. المراجع البعيدة في Git هي مؤشرات Pointers إلى المستودعات البعيدة بما تتضمنه من تفريعات، وسوم وغيرها. يمكنك الحصول على لائحة كاملة بالمراجع البعيدة بتنفيذ الأمر: git ls-remote [remote] أو: git remote show [remote] لعرض التفريعات البعيدة إضافة لمعلومات متفرقة. إلا أنه توجد طريقة أخرى أكثر شيوعا وهي الاستفادة من تفريعات التتبع عن بعد Remote-tracking branches تفريعات التتبع عن بعد هي مراجع لحالة التفريعات البعيدة. توجد هذه التفريعات محليا إلا أنه ليس بالإمكان تحريكها فهي تتحرك تلقائيا عندما تتبادل بيانات مع الخادوم البعيد. تعمل تفريعات التتبع عن بعد كإشارة مرجعية لتذكيرك بموضع تفريعات المستودعات البعيدة في آخر مرة اتصلت فيها بالخادوم. تأخذ تفريعات التتبع عن بعد الهيئة (remote)/(branch). إن أردت مثلا رؤية الحالة التي كان عليها التفريع master على مستودع origin البعيد في آخر مرة اتصلت فيها به فستفحص Checkout التفريع origin/master. إن كنت تعمل عن بعد مع زملاء، ثم دفعوا Push تفريعا باسم iss53 فيمكن أن يكون لديك تفريع محلي بنفس الاسم بينما يشير تفريع التتبع عن بعد إلى الإيداع على origin/iss53. قد يبدو الأمر معقّدا قليلا لذا سنأخذ مثالا. فلنفترض أن لديك خادوم Git على العنوان git.ourcompany.com. إن نسخت Clone مستودعا من الخادوم فإن أمر git clone سيسميه تلقائيا origin، يجلب جميع البيانات إلى المستودع المحلي، ينشئ مؤشرا إلى آخر إيداع في التفريع master ويسميه محليا بـorigin/master. يوفر Git أيضا تفريع master محليا يبدأ من نفس مبدأ التفريع master الخاص بـorigin. ملحوظة: origin ليس مستودعا ذا خصوصية. لا يمثل origin مستودعا ذا خصوصية تماما كما أن master ليس تفريعا ذا دلالة خاصة في Git. يسمّي Git التفريع َالمبدئي بـmaster عند تنفيذ أمر git init كما يسمي أمر git clone المستودع البعيد مبدئيا بـ origin؛ لهذا السبب فقط يكثُر استخدام التسميتين. إن نفذت أمر git clone مع خيار o- مثل: git clone -o booyah فسيكون التفريع booyah/master هو التفريع المبدئي لديك. إن عملت على التفريع master المحلي وأثناء عملك دفع أحدهم تغييرات إلى git.ourcompany.com وحدّث التفريع master على الخادوم فسيختلف سجل الإيداعات المحلي عن البعيد. في تلك الأثناء لن يتغير تفريع origin/master ما لم تدخل في تبادل بيانات مع الخادوم. نفذ أمر: git fetch origin لمزامنة تفريع التتبع عن بعد. يبحث الأمر عن خادوم المستودع origin (أي git.ourcompany.com )، يفتش عن البيانات الموجودة على الخادوم التي لا توجد محليا ثم يحدّث البيانات المحلية محرّكا المؤشر origin/master إلى موضعه الجديد. نأخذ مثالا لتوضيح كيف تبدو تفريعات التتبع عن بعد لخواديم بعيدة متعدّدة. نفترض أن لديك خادوم Git داخلي تستخدمه إحدى فرق العمل لأغراض التطوير والاختبار. عنوان هذا الخادوم هو git.team1.ourcompany.com. تمكن إضافة مرجع للخادوم البعيد هذا إلى المشروع الذي تعمل عليه حاليا بتنفيذ الأمر: git remote add كما تطرقنا لذلك في درس العمل على المستودعات البعيدة في Git. نضيف المستودع مع استخدام الاختصار teamone. يمكنك الآن تنفيذ الأمر: git fetch teamone للبحث عن البيانات الموجودة على الخادوم الجديد دون أن تكون موجودة محليا. يوجد على الخادوم teamone جزء من العمل الموجود على الخادوم الأول؛ يعني هذا أن Git بعد تنفيذ الأمر السابق لن ينزل إيداعات جديدة لكنه سيكتفي بإنشاء تفريع تتبع عن بعد جديد ويسميه teamone/master يشير إلى آخر إيداع في التفريع master على الخادوم teamone. دفع البيانات عندما تريد مشاركة تفريع تعمل عليه مع آخرين فستحتاج لدفع البيانات إلى مستودع بعيد لديك صلاحية الكتابة عليه. لا يُزامن Git التفريعات المحلية تلقائيا مع التفريعات البعيدة، بل يجب أن تزامن التفريع الذي ترغب في مشاركته يدويا. تستخدم بهذه الطريقة تفريعات خاصة (محلية) للأعمال التي لا تريد مشاركتها بينما تدفع بيانات التفريعات التي تود مشاركتها إلى خادم مشترك. إن كان لديك تفريع باسم serverfix تريد التشارك بالعمل عليه مع آخرين فيمكنك دفع البيانات إليه بنفس طريقة دفع التفريع الأخرى بتنفيذ الأمر: git push <remote> <branch> كما في المثال التالي: git push origin serverfix Counting objects: 24, done. Delta compression using up to 8 threads. Compressing objects: 100% (15/15), done. Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done. Total 24 (delta 2), reused 0 (delta 0) To https://github.com/schacon/simplegit * [new branch] serverfix -> serverfix يأخذ Git تلقائيا محتوى التفريع serverfix إلى refs/heads/serverfix:refs/heads/serverfix وهو ما يعني أخذ بيانات التفريع المحلي serverfix ودفعها لتحديث بيانات التفريع البعيد serverfix. سنعرج للحديث عن refs/heads/ عندما نتحدث عن أمور داخلية في Git، يمكن الآن تجاوزها. يمكن أيضا تنفيذ الأمر: push origin serverfix:serverfix الذي يؤدي نفس المهمة. يُفسَّر الأمر بـ "خذ التفريع serverfix المحلي واجعله هو تفريع serverfix البعيد". يمكن استخدام هذه الطريقة لدفع بيانات تفريع محلي إلى تفريع بعيد يختلف عنه في الاسم. أما إن كنت ترغب في تغيير اسم التفريع في المستودع البعيد فيمكنك تنفيذ الأمر: git push origin serverfix:awesomebranch لدفع بيانات تفريع serverfix المحلي إلى التفريع awesomebranch على الخادوم البعيد. لا تعد كتابة كلمة السر في كل مرة! يطلُب Git عند دفع البيانات إلى مستودع يستخدم روابط مؤمنة بيانات الاستيثاق (اسم المستخدم وكلمة السر) ويظهر محثّ Prompt في سطر الأوامر لتلقي هذه البيانات. تستطيع استخدام تخبئة اعتمادات Credential cache لتفادي إدخال اسم المستخدم وكلمة السر في كل مرة تدفع فيها البيانات. الطريقة الأسهل لإعدادها هي تنفيذ أمر: git config --global credential.helper cache يحصُل مشاركوك في المستودع عند تنفيذ أمر git fetch مستقبلا على مرجع لمكان نسخة التفريع serverfix الموجودة على الخادوم: git fetch origin remote: Counting objects: 7, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 3 (delta 0) Unpacking objects: 100% (3/3), done. From https://github.com/schacon/simplegit * [new branch] serverfix -> origin/serverfix من المهم الانتباه إلى أن تفريعات التتبع عن بعد التي تنتج عن تنفيذ أمر git fetch لا تعطيك تلقائيا نسخة يمكن تحريرها من هذه التفريعات. يعني هذا بعبارة أخرى أن الأمر أعلاه لا ينشئ تفريع serverfix جديدا بل مؤشرا باسم origin/serverfix لا يمكن تعديله. نفذ أمر: git merge origin/serverfix لدمج التفريع البعيد في التفريع الحالي لديك. إن كنت تريد تفريع serverfix خاصا بك للعمل عليه فيمكنك تأسيسه على تفريع التتبع عن بعد: git checkout -b serverfix origin/serverfix Branch serverfix set up to track remote branch serverfix from origin. Switched to a new branch 'serverfix' يعطيك الأمر أعلاه تفريعا محليا يبدأ من حيث يوجد origin/serverfix ويمكنك العمل عليه. تفريعات التتبع Tracking branches يؤدي فحصُ تفريع محلي من تفريع تتبع عن بعد تلقائيا إلى إنشاء ما يُسمّى تفريع تتبع. تفريعات التتبع هي تفريعات محلية ذات علاقة مباشرة مع مستودع بعيد (يُسمَّى التفريع البعيد بالتفريع العلوي Upstream branch). إذا كنت تعمل على تفريع تتبع ثم نفذت أمر git pull فإن Git سيعرف تلقائيا من أين سيجلب البيانات والتفريعَ الذي يجب دمجها فيه. ينتج عادة عن نسخ مستودع إنشاء تفريع master لتتبع origin/master. يمكنك إن أردت إعداد تفريعات تتبع أخرى؛ أو التخلي عن تتبع التفريع master الذي أُعدّ تلقائيا أثناء نسخ المستودع. أقرب مثال لإنشاء تفريعات تتبع هو تنفيذ الأمر: git checkout -b [branch] [remotename]/[branch] هذا الأمر شائع لدرجة أن Git لديه اختصار للأمر: git checkout --track origin/serverfix Branch serverfix set up to track remote branch serverfix from origin. Switched to a new branch 'serverfix' في الواقع، الأمر شائع لدرجة وجود اختصار للاختصار أعلاه! ينشئ Git عند تنفيذ أمر git checkout تفريع تتبع إذا توفر الشرطان: (أ) التفريع غير موجود سلفا، (ب) يوافق اسم التفريع بالضبط تفريعا بعيدا وحيدا: git checkout serverfix Branch serverfix set up to track remote branch serverfix from origin. Switched to a new branch 'serverfix' تتاح أيضا إمكانية إعداد تفريع محلي باسم مختلف عن التفريع البعيد باستخدام النسخة الأولى من الأمر مع ذكر اسم التفريع المحلي على النحو التالي: git checkout -b sf origin/serverfix Branch sf set up to track remote branch serverfix from origin. Switched to a new branch 'sf' بهذا يجلب تنفيذ الأمر git pull على التفريع sf البيانات تلقائيا من origin/serverfix. استخدم خيار u- أو set-upstream-- إذا كنت تريد إعداد تفريع محلي تعمل عليه لتتبع تفريع بعيد أو تغيير التفريع العلوي الذي يتتبّعه التفريع المحلي: git branch -u origin/serverfix Branch serverfix set up to track remote branch serverfix from origin. ملحوظة: اختصار التفريع العلوي. تستطيع بعد إعداد تفريع تتبع استخدام الاختصار {upstream}@ أو {u}@ للإشارة إلى التفريع العلوي المرتبط به. نفترض أنك تعمل على التفريع master الذي يتتبع التفريع origin/master؛ يمكنك في هذه الحالة تنفيذ الأمر المختصر {git merge @{u بدلا من git merge origin/master. استخدم خيار vv- مع أمر git branch لعرض تفريعات التتبع التي أعددتها. يسرد الأمر قائمة بالتفريعات المحلية مع معلومات أكثر عن كل تفريع بما فيها التفريعات العلوية وهل تفريع التتبع يتقدم على التفريع العلوي أم يتأخر عنه أم الاثنان معا. git branch -vv iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets master 1ae2a45 [origin/master] deploying index fix * serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it testing 5ea463a trying something new تشير نتيجة الأمر في المثال أعلاه إلى أن التفريع iss53 يتتبّع التفريع origin/iss53، وأنه يتقدم عليه بإيداعين (ahead 2)؛ بمعنى أنه يوجد إيداعان محليان لم يدفعا بعدُ إلى الخادوم. يظهر أن التفريع master يتتبّع التفريع origin/master وأنه محدَّث. نرى في السطر الموالي أن serverfix يتتبّع server-fix-good على الخادوم teamone وأنه يتقدم عليه بـ3 ويتأخر عنه ب1؛ أي أنه يوجد إيداع واحد على الخادوم لم يُدمج محليَّا بعد، كما توجد ثلاثة إيداعات محلية لم تُدفع إلى الخادوم. يظهر في السطر الأخير أن التفريع testing لا يتتبّع أي تفريع بعيد. يجب الانتباه إلى أن هذه الأعداد تحيل إلى البيانات المخبَّأة منذ آخر مرة بُحث فيها عن بيانات على كل خادوم؛ فالأمر أعلاه لا يدخل في اتصال مع الخواديم بل يخبرك فقط بالمعلومات الموجودة محليا. إن كنت تريد بيانات حديثة كليًّا فيجب البحث عنها أولا على الخواديم جميعا بتنفيذ الأمر git fetch عليها. يمكن مثلا تحديث البيانات من جميع الخواديم كالتالي: git fetch --all; git branch -vv جلب البيانات ينزّل أمر git fetch كل التغييرات الموجودة على الخادوم التي لا توجد عندك محليا؛ ولكنها لا تغير مجلد العمل بتاتا، بل تكتفي بتنزيل البيانات وترك مهمة دمجها لك. إلا أنه يوجد أمر git pull الذي ينزّل البيانات ويدمجها في مجلد العمل لديك؛ ينتج عن تنفيذ أمر git pull غالبا تنفيذ أمر git fetch متبوعا مباشرةً بأمر git merge. يؤدي تنفيذ git pull على تفريع تتبع مُعدّ مثل ما وضحنا في الفقرة السابقة، إما بإعداده صراحة أو عن طريق أمر git clone أو git checkout، يؤدي إلى البحث في المستودع العلوي المناسب عن البيانات الجديدة ثم دمجها فورا في تفريع التتبع المحلي. من الأفضل عموما استخدام أمري: git fetch و git merge بالتتابع إذ أن أمر git pull قد يكون مربكا. حذف التفريعات البعيدة لنفترض أنك أنهيت العمل على تفريع بعيد (أكملت أنت ومشاركوك العمل على ميزة وأُدمجت في التفريع الرئيس الذي توجد به الشفرة المستقرة مهما كان اسمه، master أو أي شيء آخر)؛ يمكنك حذف هذا التفريع باستخدام الخيار delete-- في أمر git push. نفذ الأمر التالي إذا أردت حذف التفريع serverfix من الخادوم: git push origin --delete serverfix To https://github.com/schacon/simplegit - [deleted] serverfix يتلخص عمل الأمر في حذف مؤشر التفريع من الخادوم. يحتفظ خادوم Git عادة ببقية البيانات لمدة إلى أن تتم عملية جمع النفايات Garbage collection؛ تتيح هذه الطرقة إمكانية إرجاع التفريع إن كان حذفه عرضيا. ترجمة -بتصرف- للفصل Git Branching - Remote Branches من كتاب Pro Git لصاحبه Scott Chacon.
  8. سنقوم في هذا الدرس بتغطية الأوامر الأساسية التي لا يُمكنك الاستغناء عنها إن أردت أن تستخدم Git بكفاءة عالية والتي ستقضي وقتك على Git في استخدامها. يُفترض بك بعد إنهاء قراءة هذا الدرس أن تصبح قادرًا على إعداد وتهيئة مُستودع، الشروع في وإيقاف تتبع التغييرات في ملفات مُعينة، إرسال التغييرات إلى منطقة الإدراج وإيداعها. سنشرح لك أيضا كيف ستقوم بتجاهل ملفات مُعينة أو التي تتبع أنماطا خاصة، فيما ستتحدث الدروس التي تلي هذا عن كيفية التراجع عن أخطاء قُمت بها بشكل سريع وسهل، كيفية تصفح تاريخ المشروع والاطلاع على كافة التعديلات التي حدثت ما بين كل إيداعين، وكيف تقوم بدفع التغييرات إلى مُستودع في خادوم بعيد أو سحب البيانات منه. الحصول على مستودع وحفظ التغييرات فيه يُمكنك الحصول على مُستودع Git بطريقتين مُختلفتين. تتمثل الأولى في إنشاء مُستودع لمشروع أو مُجلد مُعد سلفا، وتتم الثانية عبر استنساخ مُستودع موجود على خادوم بعيد. إنشاء مستودع جديد لمشروع موجود مسبقا إن أردت الشروع في تتبع إصدارات وتغييرات مشروع راهن فكل ما عليك القيام به هو الذهاب إلى مُجلد المشروع (بسطر الأوامر طبعا) ومن ثم تنفيذ الأمر التالي: $ git init سيقوم هذا الأمر بإنشاء مُجلد فرعي داخل مُجلد المشروع يحمل الاسم git. والذي سيحتوي جميع ملفات المُستودع الأساسية التي ستشكل هيكل المستودع. بطبيعة الحال لن يتم تتبع أية تغييرات بُمجرد تنفيذ هذا الأمر لوحده. إن أردت الشروع في إدارة نسخ الملفات الراهنة (يعني لن تبدأ بمُجلد فارغ) فإنه يجب عليك أولا الشروع في تتبع هذه الملفات والقيام بأول عملية إيداع. يُمكن القيام بذلك عبر تنفيذ بضعة أوامر git add التي ستحدد فيها أسماء هذه الملفات ثم تُتبعها بأمر للإيداع: $ git add README $ git commit -m "initial project version" بعد تنفيذك لهذه الأوامر (سنشرح ما تقوم به هذه الأوامر بعد قليل) سيكون لديك مُستودع Git يقوم بتتبع التغييرات التي ستطرأ على مجموعة ملفات، إضافة إلى إيداع أولي. نسخ مستودع موجود مسبقا إن أردت استنساخ مُستودع Git راهن -وليكن مُستودعا لمشروع ترغب في المُساهمة فيه- فإن الأمر الذي ستحتاجه للقيام بذلك هو git clone. إن كانت لديك دراية مُسبقة بباقي أنظمة إدارة النُسخ مثل Subversion فإنك ستلحظ بأن الأمر الذي نستعمله هنا هو clone وليس checkout، حيث أن ما سيستقبله Git هو نُسخة من جميع البيانات (أو تقريبا) الموجودة على الخادوم. يعني بأنك ستحصل على كل نسخة من كل الملفات منذ أن تم الشروع في تتبع المُستودع الموجود على الخادوم لدى تنفيذ الأمر git clone. بعبارة أخرى، لو تعرض القرص الصلب لخادومك لعطب ما فإنه بإمكانك استعادة المشروع الذي كنت تعمل عليه كما كان (أو تقريبا) بفضل النُسخ الموجودة في جميع الأجهزة التي تعمل على نفس المشروع. يتم استنساخ المُستودعات باستخدام الأمر التالي: git clone [url] حيث يتم استبدال بعُنوان المُستودع المُراد استنساخه. فعلى سبيل المثال لو أردت استنساخ مُستودع "الدليل البسيط لاستخدام Git" فسيكون الأمر على النحو التالي: git clone git://github.com/arabicgit/simple-guide.git سيتم إنشاء مُجلد يحمل الاسم simple-guide يتم إنشاء مُجلد git. بداخله، ومن ثم سيتم استنساخ جميع البيانات الموجودة في مستودع الدليل على Github التي يُمكن الشروع في العمل عليها. إن أردت أن يتم استنساخ المُستودع داخل مُجلد باسم مُخالف فيُمكن تحديد اسم المُجلد الذي ترغب فيه (وليكن MyGuide) في أمر الاستنساخ على النحو التالي: git clone git://github.com/arabicgit/simple-guide.git MyGuide يدعم Git عدة بروتوكولات للتحويل. استعملنا في المثال السابق بروتوكول git://، لكنه بإمكانك أيضا استخدام (http(s:// أو user@server:/path.git والتي تعتمد على بروتوكول SSH. تسجيل التغييرات الحاصلة في المستودع الآن وبعد أن حصلت على مُستودع يقبل تتبع التغييرات الحاصلة على ملفاته، إضافة إلى جُملة من الملفات التي ستعمل عليها، ستحتاج بطبيعة الحال إلى إحداث تغييرات عليها ومن ثم إيداع تلك التغييرات في كل مرة يصل فيها مشروعك إلى مرحلة ترغب في تسجيلها. تذكر بأن الملفات تكون في إحدى الحالتين التاليتين: مُتتبع tracked أو غير مُتتبع untracked. الملفات المُتتبعة هي التي سبق وأن سجلت حضورا في اللقطة snapshot السابقة. يُمكن لهذه الملفات أن تكون في حالات ثلاثة: مُعدّلة غير مُعدلة مُدرجة staged (أي موجودة في منطقة الإدراج). أما الملفات غير المُتتبعة فهي ما سوى ذلك، ويشمل ذلك أي ملف في مجلد العمل لم يكن موجودًا في اللقطة السابقة وغير موجود في منطقة الإدراج. لدى قيامك باستنساخ مُستودع فإن جميع ملفاته ستكون مُتتبّعة وغير مُعدّلة لأنه -وبكل بساطة- قمت لتوك بسحبها ولم تحدث أية تغييرات عليها. بمُجرد أن تدخل تغييرات على أحد الملفات فسيعتبره Git ملفا مُعدّلا، حيث أن هذه الملفات قد طرأت عليها تغييرات منذ آخر إيداع. ما هي الخُطوة القادمة في هذه الحالة؟ ستقوم أولا بإدراج هذه التغييرات stage (أي وضع الملفات المعنية بالأمر في منطقة الإدراج) ومن ثم تقوم بإيداع تلك التغييرات commit، وستكرر هذه العملية طيلة استخدامك لـ Git، مثلما هو مُوضح في الصورة التالية: التحقق من حالة ملفاتك للتحقق من حالة ملفات مستودعك ستحتاج إلى استخدام الأمر git status. إن قمت بتنفيذ هذا الأمر مُباشرة بعد قيامك باستنساخ مُستودع فإنه يُفترض بهذه النتيجة أن تظهر: $ git status On branch master nothing to commit, working directory clean وهو ما يعني بأنك أمام مُجلد عمل نظيف. بعبارة أخرى لم يتم إحداث تغييرات على أي ملف مُتتبَّع. في هذه الحالة أيضا لا يُظهر Git أي ملفات غير مُتتبعة، لأنه لو كان الأمر غير كذلك فسيقوم حتما بإعلامك بالأمر. يُخبرك هذا الأمر بالفرع branch الذي تتواجد فيه، ومثلما هو ظاهر من النتيجة السابقة فإننا حاليا في الفرع الرئيسي master وهو الفرع الذي يتم استعماله بشكل قياسي. لنفرض الآن بأنك قُمت بإضافة ملف جديد إلى مشروعك، ولنفرض بأنه ملف README. إذا لم يكن هذه الملف موجودا من قبل داخل هذا المُستودع ولدى قيامك بتنفيذ الأمر git status فإن اسم هذا الملف سيظهر في قسم الملفات غير المُتتبعة على النحو التالي: $ vim README $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) README nothing added to commit but untracked files present (use "git add" to track) عدم التتبع عنها يعني بأن git قد عرف بأن الملف لم يكن في اللقطة السابقة لمشروعك (أي آخر إيداع)، لكنه لن يشرع في تتبعه بشكل آلي ما لم تقم أنت بطلب ذلك بشكل صريح، وستجد بأن ذلك مُفيد جدا خاصة لما تعمل على مشروع يُنتج الكثير من الملفات المُوقتة التي لا ترغب في تتبعها. تتبع الملف الجديد الآن وللشروع في تتبع الملف الجديد فإننا سنحتاج إلى الأمر git add على النحو التالي: $ git add README وإذا قمت بتنفيذ الأمر git status من جديد فإن الملف سيظهر مُتتبعا ومُدرجا: $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README بإمكانك معرفة أن الملف في منطقة الإدراج لأنه يظهر في قسم التغييرات التي سيتم إيداعها "Changes to be committed". إذا قمت بإدراج الملف في هذه المرحلة فإن حالة الملف لدى تنفيذك للأمر git add هي التي ستتم إضافتها إلى اللقطة. يُمكنك تحديد مسار ملف أو مُجلد لدى تنفيذك لأمر git add، ولدى تحديد مُجلد فإنه ستتم إضافة جميع مُحتوياته. إدراج الملفات المعدلة لنقم الآن بتعديل ملف سبق وأن شرعنا في تتبع التغيرات الطارئة عليه. لو أدخلنا تغييرات على الملف benchmark.rb ومن ثم نفذنا الأمر status من جديد لحصلنا على نتيجة مُماثلة لهذه النتيجة: $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb أين يظهر ملف benchmark.db تحت قسم "Changes not staged for commit" (تعديلات لم يتم إدراجها لإيداعها).ومثلما هو ظاهر من هذا الوصف فأننا قمنا بإدخال تعديلات على ملف مُتتبع ولم يتم إدراج تلك التغييرات بعد. ولإدراج هذه التغييرات يكفي أن نُنفذ الأمر git add الذي لديه عدة استخدامات كالشروع في تتبع الملفات مثلما سبق وأن شاهدناه، إدراج الملفات وللقيام بأمور أخرى كتعليم ملفات الدمج المُتضاربة merge-conflicted files كمحلولة. $ git add benchmarks.rb $ git status On branch master Changes to be committed: > (use "git reset HEAD <file>..." to unstage) new file: README modified: benchmarks.rb مثلما نلاحظه الآن، تم إدراج كلا الملفين وسيتم أخذ التغييرات الطارئة عليهما في الحسبان في عملية الإيداع القادمة. لكن لنفرض بأنك تذكرت تغييرا آخرًا تود إدخاله على ملف قبل أن تقوم بعملية الإيداع، وعليه فإنك ستقوم بالتعديل عليه قبل إيداعه. لكن لو قمت الآن بتنفيذ أمر git status من جديد فستظهر هذه النتيجة: $ vim benchmarks.rb $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: benchmarks.rb Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb غريب؟ أليس كذلك؟ يظهر ملف benchmark.rb على أساس أنه مُدرج وغير مُدرج في آن واحد. هل هذا ممكن؟ نعم هذا مُمكن حيث يقوم git بإدراج الملف في حالته التي كان عليها لدى تنفيذ الأمر git add وبالتالي لو قمت بالإيداع الآن فإنه سيتم إيداع ملف benchmark.rb في حالته التي كان عليها لدى تنفيذ الأمر git add وليس على الحال التي هو عليها الآن، وبالتالي فإنه يجب تنفيذ الأمر git add لإدراج تلك التغييرات في كل مرة تقوم بإدخال التعديلات على الملف: $ git add benchmarks.rb $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: benchmarks.rb تجاهل ملفات عادة ما تكون لديك في المشروع الذي تعمل عليه جُملة أو صنف من الملفات التي لا ترغب من Git أن يقوم بإضافتها بشكل آلي أو حتى في إظهارها كملفات غير مُتتبعة، وعادة ما تكون هذه الملفات تلك التي يتم توليدها بشكل آلي مثل ملفات log أو الملفات التي تنتج عن نظام البناء الخاص بك build system. في مثل هذه الحالات فإن الحل يكمن في استخدام ملف gitignore. الذي يحتوي أنماط أسماء هذه الملفات غير المرغوب فيها: $ cat .gitignore *.[oa] *~ يطلب السطر الأول في الملف (ولا أعني بذلك الأمر cat) من Git أن يتجاهل جميع الملفات التي تنتهي بـ o. أو a. (الكائنات وملفات الأرشيف التي يُحتمل أن تنتج عن عملية بناء شفرتك البرمجية). أما السطر الثاني فيطلب من Git أن يتجاهل جميع الملفات التي تنتهي أسماؤها بمحرف ~ والتي تستخدمها بعض محررات النصوص كمُحرر Emacs لحفظ الملفات المؤقتة. بإمكانك أيضا أن تُضمن في هذا الملف ملفات log ،tmp أو مجلد pid والتوثيق التي يتم توليده بشكل آلي وما إلى ذلك. الشروع في إعداد ملف gitignore. قبل الشروع في العمل على المشروع من شأنه أن يُجنبك إيداع ملفات لا ترغب أن تظهر في مستودعك لاحقا. القواعد العامة التي تتبعها أنماط أسماء الملفات المُضمنة في ملف gitignore. هي على النحو التالي: يتم تجاهل الأسطر الفارغة والأسطر التي تبدأ بمحرف # يتم أخذ الأنماط المبسطة للتعابير القياسية Standard glob patterns في الحسبان بإمكانك إنهاء النمط بـ / للدلالة على المجلدات. بإمكانك عكس النمط بتسبيقه بعلامة تعجب ! الأنماط المبسطة للتعابير القياسية Standard glob patterns هي نسخة مُبسطة من التعابير القياسية التي يُمكن استخدامها على سطر الأوامر. فـ * تدل على 0 أو أكثر من محرف، و [abc] تتوافق مع أي حرف ضمن هذه القائمة (في هذه الحالة: a ،b و c). أما علامة الاستفهام فتتوافق مع محرف واحد. أما لو استخدمنا هذه الصيغة [0-9] فإنها ستوافق أي محرف يقع ما بين 0 و 9. إليكم مثالا آخر عن ملف gitignore.: # a comment - this is ignored # no .a files *.a # but do track lib.a, even though you're ignoring .a files above !lib.a # only ignore the root TODO file, not subdir/TODO /TODO # ignore all files in the build/ directory build/ # ignore doc/notes.txt, but not doc/server/arch.txt doc/*.txt # ignore all .txt files in the doc/ directory doc/**/*.txt النمط **/ مُتوفر بداية من الإصدار 1.8.2 إظهار التغييرات المدرجة وغير المدرجة إذا كانت النتيجة التي يُظهرها الأمر git status غير دقيقة بالنسبة إليك أو إن كنت ترغب في معرفة التغييرات التي حصلت بدقة وليس مجرد معرفة قائمة الملفات التي تم تغييرها فإنه بإمكانك الاستعانة بالأمر git diff للقيام بذلك. لسنا هنا بصدد تفصيل الأمر git diff (سنقوم بذلك في مقال لاحق) لكنك ستحتاجه للإجابة على سؤالين مُحددين: ما الذي قُمت بالتعديل عليه ولم تقم بإدراجها بعد، وما الذي قمت بإدراجه لكن لم تقم بإيداعه بعد. بالرغم من أنه بإمكان git status الإجابة على هذين السؤالين، إلا أن الأمر git diff كفيل بالإجابة عنها بشكل أدق، حيث سيخبرك عن أي سطر تمت إضافته وعن أي سطر تمت إزالته. لنفرض بأنك قمت بتحرير وإيداع ملف README من جديد ومن ثم قمت بتحرير ملف benchmarks.rb من دون إدراجه. لو قمت بتنفيذ الأمر status فإنك ستحصل على نتيجة مُشابه للنتيجة التالية: $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb لكن لو رغبت في معرفة التغييرات التي حصلت والتي لم يتم إدراجها فاستعن بالأمر git diff: $ git diff diff --git a/benchmarks.rb b/benchmarks.rb index 3cb747f..da65585 100644 --- a/benchmarks.rb +++ b/benchmarks.rb @@ -36,6 +36,10 @@ def main @commit.parents[0].parents[0].parents[0] end + run_code(x, 'commits 1') do + git.commits.size + end + run_code(x, 'commits 2') do log = git.commits('master', 15) log.size يقوم هذا الأمر بمقارنة مُحتوى مُجلد العمل بما هو موجود في منطقة الإدراج، وبالتالي فإن النتيجة ستُخبرك بالتغييرات التي قُمت بها والتي لم تقم بإدراجها بعد. أما إذا أردت رؤية التغييرات المُدرجة التي سيتم أخذها في الحسبان في الإيداع القادم فما عليك سوى استخدام الأمر git diff --cached (في الإصدار 1.6.1 والإصدارات التي تليه أصبح بالإمكان استخدام الأمر git diff --staged بدلا عنه، حيث يُعتبر هذا الأمر أسهل للتذكر من سابقه). يقوم هذا الأمر بمقارنة مُحتوى منطقة الإدراج بآخر إيداع قمت به: $ git diff --cached diff --git a/README b/README new file mode 100644 index 0000000..03902a1 --- /dev/null +++ b/README2 @@ -0,0 +1,5 @@ +grit + by Tom Preston-Werner, Chris Wanstrath + http://github.com/mojombo/grit + +Grit is a Ruby library for extracting information from a Git repository تجدر الإشارة إلى أن الأمر git diff بدون أية معاملات إضافية لا يقوم بإظهار كل التغييرات التي قُمت بها منذ آخر إيداع، حيث تكتفي بإظهار التغييرات التي لم يتم إدراجها. هذا الأمر قد يكون مُربكا بعض الشيء بحكم أنه لو قمت بإدراج جميع التغييرات التي قمت بها فإن أمر git diff لن يُظهر أية نتيجة. مثال آخر، لو قمت بإدراج الملف benchmarks.rb ومن ثم قمت بالتعديل عليه فإنه بإمكانك استخدام الأمر git diff لرؤية التغييرات التي تمت على الملف والتي تم إدراجها وتلك التغييرات التي لم يتم إدراجها أيضا: $ git add benchmarks.rb $ echo '# test line' >> benchmarks.rb $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: benchmarks.rb Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb الآن يُمكنك استخدام الأمر git diff لمعرفة ما الذي لم يتم إدراجه بعد: $ git diff diff --git a/benchmarks.rb b/benchmarks.rb index e445e28..86b2f7c 100644 --- a/benchmarks.rb +++ b/benchmarks.rb @@ -127,3 +127,4 @@ end main() ##pp Grit::GitRuby.cache_client.stats +# test line والأمر git diff --cached لمعرفة ما الذي قُمت بإدراجه إلى حد الآن: $ git diff --cached diff --git a/benchmarks.rb b/benchmarks.rb index 3cb747f..e445e28 100644 --- a/benchmarks.rb +++ b/benchmarks.rb @@ -36,6 +36,10 @@ def main @commit.parents[0].parents[0].parents[0] end + run_code(x, 'commits 1') do + git.commits.size + end + run_code(x, 'commits 2') do log = git.commits('master', 15) log.size إيداع التغييرات الآن وبعد أن قمت بإعداد منطقة الإدراج على النحو الذي ترغب فيه، بإمكانك الآن القيام بإيداع التغييرات التي قمت بها. تذكر دائما بأن أي تغيير لم تم بإيداعه -أي ملف قمت بإنشائه أو التعديل عليه والذي لم تقم بإضافته بتنفيذ الأمر git add عليه منذ إنشائه أو منذ آخر تحديث عليه- لن يتم أخذه بالحسبان في الإيداع القادم، بل سينظر إليه النظام كملف تم تعديله. في هذه الحالة، ولدى تنفيذك للأمر git status آخر مرة فإنك لاحظت بأنه تم إدراج جميع التغييرات وبالتالي فإنك جاهز لإيداع التغييرات. يتم ذلك باستخدام الأمر git commit على النحو التالي: git commit تنفيذ هذا الأمر سيقوم بفتح مُحرر النصوص المُفضل لديك (والذي يتم تحديده عبر قيمة مُتغيرالنظام EDITOR$ والذي يُمكن أن يكون vim ،emacs أو أي مُحرر نصوص آخر، كما أنه يُمكنك تحديد عبر الأمر git config --global core.editor مثلما سبق وأن تطرقنا إليه في درس إعداد Git للمرة الأولى. سيقوم المُحرر بإظهار النص التالي (المثال التالي مأخوذ من مُحرر النصوص Vim): # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # new file: README # modified: benchmarks.rb # ~ ~ ~ ".git/COMMIT_EDITMSG" 10L, 283C مثلما نلاحظه هنا فإن نص/رسالة الإيداع القياسية تحتوي مُخرجة الأمر git status على هيئة تعليق، إضافة إلى سطر فارغ يسبق ذلك. بإمكانك حذف هذا التعليق وكتابة النص الذي ترغب فيه، كما أنه بإمكانك الإبقاء عنها لتتذكر الملفات التي تم تعديلها في هذا الإيداع ( للحصول على تذكير أدق يُمكنك تمرير المُعامل v- لأمر git commit سابق الذكر، وستحصل حينها على تفاصيل التغييرات التي حدثت على الملفات لما يتم فتح المُحرر المفضل لديك). لدى إغلاقك لمحرر النصوص سيقوم Git بإنشاء الإيداع ويقوم بإرفاق ذلك النص/الرسالة به مع التخلص من التعليقات وتفاصيل التغييرات التي أدخلت على الملفات. يُمكنك أيضا كتابة نص الإيداع مُباشرة مُرفقا بأمر الإيداع وذلك بتسبيقه بـ m- على النحو التالي: $ git commit -m "Story 182: Fix benchmarks for speed" [master 463dc4f] Story 182: Fix benchmarks for speed 2 files changed, 3 insertions(+) create mode 100644 README كما تلاحظ فإنك قمت بعمل أول عملية إيداع لك وحصلت على بعض المعلومات حوله، كاسم الفرع الذي ينتمي إليه (master) وما هي قيمة هاش SHA-1 الخاصة به 463dc4f، عدد الملفات التي تم تحديثها، إضافة إلى عدد الأسطر التي تمت إضافتها أو حذفها. يجب التذكير بأن عملية الإيداع تقوم بأخذ صورة عن البيانات التي تم إدراجها وأي تغييرات لم تقم بإرسالها إلى منطقة الإدراج لن يتم أخذها بالحسبان، وعليه يجب إضافتها من جديد إلى منطقة الإدراج قبل إيداعها من جديد. كل مرة تقوم فيها بعملية إيداع فإنك تأخذ صورة عن حالة مشروعك في اللحظة التي تم التقاط تلك الصورة فيها وبإمكانك الرجوع إلى تلك الحالة لاحقا إن رغبت في ذلك. تجاوز منطقة الإدراج بالرغم من أن منطقة الإدراج في غاية الأهمية لتمكيننا من "بناء" إيداعات على الشكل الذي نرغب فيه، إلا أنها تُعتبر في بعض المرات مرحلة إضافية لا نرغب فيها. إذا رغبت في تجاوز منطقة الإدراج ما بين الحين والآخر فإن Git يسمح لك بالقيام بذلك، حيث يكفي إضافة خيار a- إلى أمر git commit ليقوم git بإضافة جميع الملفات التي كنت تتبعها سابقا قبل عملية الإيداع الحالية (يعني يقوم باختصار أمر git add). $ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb no changes added to commit (use "git add" and/or "git commit -a") $ git commit -a -m 'added new benchmarks' [master 83e38c7] added new benchmarks 1 files changed, 5 insertions(+) لاحظ كيف أننا لم نحتج إلى تنفيذ الأمر git add على الملف benchmarks.rb في هذه الحالة قبل أن تقوم بعملية الإيداع. حذف الملفات لحذف ملف من مشروع Git فإنه يجب عليك أن تحذفه من قائمة الملفات المُتتبّعة (أو بالأحرى من قائمة الملفات المُدرجة) قبل أن تقوم بعملية إيداع. يُمكّن أمر git rm من القيام بذلك ويقوم بحذف الملف من مُجلد العمل مما سيُجنّب ظهوره في قائمة الملفات غير المُتتبعة. إذا قمت بحذف الملف يدويا من المشروع فإن اسمه سيظهر في قائمة التغييرات التي لم يتم إدراجها لما تقوم بتنفيذ الأمر git status: $ rm grit.gemspec $ git status On branch master Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: grit.gemspec no changes added to commit (use "git add" and/or "git commit -a") ومن ثم إذا قمت بتنفيذ الأمر git rm فإنه سيتم "إدراج" الملف للحذف: $ git rm grit.gemspec rm 'grit.gemspec' $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) deleted: grit.gemspec وفي المرة القادمة التي تقوم فيها بالإيداع فإن الملف سيختفي كُليّة. إذا كنت قد عدّلت على الملف وقمت بإضافته إلى الفهرس index فإنه يجب فرض إزالته عبر تمرير الخيار -f. يتم الاستعانة بذلك لتجنب حذف بيانات لم يتم تسجيلها في سجلات Git عن طريق الخطأ (وبالتالي لن يُصبح بالإمكان استرجاعها). أمر آخر قد ترغب في القيام به والمُتعلق بحذف الملف من git مع إبقائه في مُجلد العمل الخاص بك. بعبارة أخرى قد تكون أضفت ملفا مُعينا عن طريق الخطأ ولكن لا ترغب في مواصلة تتبعه عبر git، وعادة ما يكون الأمر مُتعلقا بملفات نسيت إضافتها إلى gitignore. كملفات log كبيرة الحجم أو ملفات a. الناتجة عن ترجمة المشروع. كل ما تحتاج إلى القيام به هو إضافة خيار cached-- على الشكل التالي: $ git rm --cached readme.txt بإمكان تمرير أسماء ملفات، مُجلدات أو حتى أنماطا مُبسطة للتعابير القياسية file-glob patterns لأمر git rm. يعني يُمكن القيام مثلا بالتالي: $ git rm log/\*.log لاحظ وجود محرف \ الذي يسبق * والذي يُعتبر ضروريا لأن git يستعمل نظامه الخاص لتفسير هذه الأنماط إضافة إلى النظام الخاص بسطر الأوامر الذي تستخدمه. أما على نظام Windows فإنك لن تحتاج إلى إضافته. يقوم هذا الأمر بحذف جميع الملفات التي تملك المُلحق log. في مُجلد log/. بإمكانك أيضا تنفيذ الأمر التالي لحذف جميع الملفات التي تنتهي أسماؤها بـ ~: $ git rm \*~ نقل الملفات على عكس ما هو مُتداول عليه باقي أنظمة إدارة الإصدارات فإن Git لا يقوم بتتبع حركة الملفات بشكل مُباشر. إن قمت بتغيير اسم ملف مثلا فإن git لا يحتفظ بأية بيانات حول الأمر، إلا أن النظام يملك مقدارا مُعينا من "الذكاء" يسمح له باستنتاج ذلك لاحقا. سنقوم بالتطرق لتتبع حركة الملفات في مقال لاحق. قد يبدو الأمر غامضا نوعا ما خاصة وأن git يملك الأمر mv. إن أردت إعادة تسمية ملف فإنه يُمكن القيام بذلك على النحو التالي: $ git mv file_from file_to وسيتم ذلك على النحو المُتوقّع، حيث يظهر ذلك واضحا على النحو التالي: $ git mv README README.txt $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README -> README.txt في المقابل، الأمر السابق مُماثل في نتيجته للتالي: $ mv README README.txt $ git rm README $ git add README.txt حيث سيعرف git بأنك تقوم بإعادة تسمية الملف، وبالتالي لا يهم الآلية التي تتبعها للقيام بذلك. الاختلاف الوحيد ما بين الطريقتين هو أن mv عبارة عن أمر واحد بدل الأوامر الثلاثة في الطريقة الثانية. الأهم من ذلك يُمكن الاستعانة بأية طريقة ترغب فيها لإعادة تسمية الملفات، كل ما يهمك أن تقوم بتنفيذ أوامر add/rm اللازمة قبل أن تقوم بعملية الإيداع. ترجمة -وبتصرف- للفصلين: Git Basics – Getting a Git Repository و Git Basics – Recording Changes to the Repository من كتاب Pro Git لصاحبه Scott Chacon.
  9. وأخيرا اقتنعت بفلسفة المصادر المفتوحة، أو أنك أخيرا وجدت الوقت للمُساهمة في أحدها، وربما ما دفعك إلى الأمر هو استفادتك من مشروع مفتوح المصدر وأردت أن ترد الجميل بالمساهمة فيه بدورك، لكن لا تدري كيف تقوم بذلك؟ الأمر بسيط، كل ما تحتاجه هو جهاز محلي يكون Git مُنصبا عليه، حساب على Github وبعض الوقت، ومن ثم اتباع بضع خُطوات بسيطة. تجدر الإشارة إلى أنك لا تحتاج إلى أن تكون مُبرمجا للمُساهمة في المشاريع مفتوحة المصدر، يُمكنك المُساهمة في إنشاء أو تحسين توثيق المشروع، حيث يُعتبر التوثيق الجيد أحد أهم عوامل نجاح المشاريع مفتوحة المصدر، وانتشار بعضها على حساب الآخر. لنفرض أن المشروع الذي تود المُساهمة فيه هو "git – الدّليل البسيط". 1. قم باستنساخ المشروع إلى حسابك الخاص وذلك بالنقر على زر Fork الموجود في الزاوية العُلوية اليُمنى لصفحة المشروع. هذه العملية من شأنها أن تُنشئ نُسخة مُطابقة للمُستودع الأصلي على حسابك على Github. 2. بعد ذلك قم باستنساخ هذا المُستودع الجديد على جهازك المحلي. ادخل إلى المجلد الذي تود استنساخ المُجلد فيه (cd path/to/folder) وقم بتنفيذ الأمر git clone متبوعا بعُنوان المُستودع. ستجد العُنوان أسفل القائمة اليُمنى في صفحة المُستودع. بالنسبة لي سيكون الأمر على النحو التالي: git clone https://github.com/djug/simple-guide.git 3. الآن وبعد أن حصلت على هذه النُسخة، قم بإدخال التعديلات التي ترغب فيها واحفظ تلك التعديلات. قبل أن نقوم بإيداع تلك التعديلات، يُنصح دائما التحقق من حالة المُستودع، وإن تم أخذ التعديلات بالحسبان أو إن قمنا بتعديل ملف لم نكن نرغب في تعديله، حيث يكفي أن ننفذ الأمر git status لمعرفة حالة المُستودع (النسخة المحلية): git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: index.ar.html4. تبدو الأمور طبيعية. لنقم بإرسال التغييرات إلى منطقة الإدراج: git add .ومن ثم إيداعها (commit): git commit -m "Your commit Message Here"بطبيعة الحال ستحتاج إلى وضع وصف مُناسب للتغييرات التي قمت بها. بإمكانك أيضا تنفيذ الأمر commit لوحده من دون أية رسائل: git commitوسيقوم git بفتح مُحرر النصوص المُفضل لديك لكتابة الوصف (في حال ما إذا قمت بتحديد اسم هذا المُحرر في إعدادات git). سنحتاج الآن إلى إرسال هذه التغييرات إلى المُستودع (الشخصي وليس مُستودع المشروع الذي نشارك فيه) على Github عبر تنفيذ الأمر: git push origin masterسيطلب منك Git اسم المُستخدم الخاص بك على Github وكلمة السر، وبمُجرد أن يتم التحقق منهما سيتم دفع التغييرات إلى مُستودعك الخاص. 5. الآن لو عدنا إلى صفحة المُستودع الشخصي على Github سيظهر لنا تاريخ آخر تحديث للملفات المُعدلة ووصف له. وكل ما نحتاج إلى فعله الآن هو إرسال "طلب سحب" pull request -أي سحب التغيرات- إلى المُستودع الأصلي للمشروع وذلك بالنقر على الزر الأخضر الذي يظهر في الزاوية العُلوية اليُمنى للمُستودع ستظهر لك صفحة لتلخص من جديد التغييرات التي تمت 6. الخطوة الأخيرة هي النقر على زر Create Pull request، إضافة أية تعليقات ترغب فيها على الطلب، ومن ثم النقر على زر Send pull request: ستصل صاحب المشروع رسالة بخصوص التغييرات التي قمت بها، وفي حال قبولها سيصلك إشعار بها وستظهر تغييراتك على المستودع الأصلي: هذا كل ما في الأمر. أنت الآن بطريق… أقصد مُشارك في مشاريع مفتوحة المصدر بشكل رسمي. نصائح عامة لدى المساهمة في المشاريعاجعل نص الإيداع commit معبّرا أو ملخصا للتغيرات التي قمت بها، لا تجعله مبهما أو عاما.افصل طلبات السحب "pull request" حسب الغرض، أو اجعل لكل تغير تقترحه في branch لوحده حسب الغرض، مثال: لا ترسل طلب سحب يحتوي تغيرات مرئية في الـ html و css وفيه أيضا ترجمة النصوص إلى العربية، هكذا تصعب المهمة على من يقوم بعملية الدمج في حال كان يريد قبول الترجمة لكنه غير راض عن التغيرات التي طرأت في الـ html/css. في هذه الحالة سيكون من الأنسب:طلب سحب للتغيرات المرئية لوحده.طلب سحب آخر للملفات الترجمة إلى العربية.في حال كانت مساهمتك تحل علة ما تم التبليغ عنها في قسم الـ issues في مستودع المشروع على github، أو لها علاقة به، فإنه يمكنك الإشارة إلى تلك العلة في نص الإيداع فقط بوضع رقم العلة مسبوقا بعلامة #، مثال:git commit -m "fix for some bug (issue #23)"ستلاحظ عند زيارة واجهة Github أن الرقم 23# في نص الإيداع، قد أصبح رابطا يحول مباشرة إلى صفحة تلك العلة. في حال قمت بعمل Fork وساهمت في المشروع، وأردت مرة أخرى أن تساهم في ذاك المشروع مجددا، فلا تنس أن تجعل مستودعك الشخصي من ذاك المشروع محدّثا، يعني اجلب التغيرات الجديدة التي طرأت على المستودع الأصلي، قبل المساهمة مجددا وعمل أي commit أو pull request، هذا يخفض من نسبة التعارض conflicts، ويسهّل عليك وعلى القائمين على المشروع عمليات الدمج، قد تكون التغيرات التي قمت بها، قد قام بها آخر، أو ربما تم حذف الملف الذي تعمل عليه أصلا من المشروع الأصلي، فيضيع جهدك هباءا منثورا.من المحبذ أيضا، استعمال صيغة الأمر في نص الإيداع، مثال، استعمل "add something to file x" عوض "adding|added something to file x".
  10. كما سبق ذكره فإن أحد أهم الاختلافات ما بين Git وأنظمة إدارة النُسخ هو الطريقة التي ينظر ويتعامل فيها Git مع البيانات. مبدئيا تقوم أغلب أنظمة إدارة النُسخ بحفظ البيانات على شكل قائمة من التغيرات التي طرأت على الملفات. هذه الأنظمة (CVS, Subversion، Perforce، Bazaar وهلم جرا) تنظر إلى البيانات التي تقوم بإدارتها على أساس أنها ملفات إضافة إلى التغييرات الحاصلة في كل ملف مع مرور الوقت، مثلما هو مُوضح في الشكل 1. الشكل 1: تقوم مُعظم الأنظمة بحفظ البيانات على هيئة الاختلافات الطارئة عليها مُقارنة بنسخة أولية من الملف. لا يتعامل Git مع البيانات على هذا النحو، بل يعتبر البيانات مجموعة من اللقطات Snapshots لنظام ملفات مُصغر. كل مرة تقوم فيها بإيداع البيانات (commit) أو تحفظ حالة مشروعك باستخدام Git، فإن ما يقوم به النظام هو التقاط صورة لما هي عليه الملفات في تلك اللحظة ويحفظها. ولجعل هذه الآلية فعالة فإنه لا يتم حفظ صور أخرى لنفس الملف ما إذا لم يتم إحداث تغييرات على الملف، بل يتم فقط الربط على الصورة السابقة. ينظر Git إلى البيانات التي يحفظها مثلما هو مُبين في الشكل 2. الشكل 2: يلتقط Git صورا على المشروع مع مرور الوقت. يُعتبر هذه الاختلاف في مبدأ العمل جوهريا، حيث يُعيد Git بشكل جذري تعريف كامل المفاهيم المُتعلقة بإدارة النُسخ التي توارثتها باقي الأنظمة عن الأنظمة التي سبقتها. هذا الاختلاف في مبدأ العمل يجعل Git أقرب ما يكون من نظام ملفات مُصغر منه إلى أنظمة إدارة النُسخ التقليدية، وهو ما مكّن من بناء أدوات في غاية القوة اعتمادا عليه. سنقوم باستعراض الفوائد المُترتبة عن مبدأ عمل Git المُختلف عن باقي الأنظمة لدى الحديث عن تفريعات Git في مقال لاحق. أغلب العمليات تتم بشكل محلي أغلب العمليات التي يقوم بها Git تحتاج فقط إلى ملفات محلية ليتم تنفيذها وذلك من دون الحاجة إلى أية بيانات من خواديم بعيدة. هذه الخاصية تُعطي الانطباع بأن Git سريع جدا مُقارنة بغيره من أنظمة إدارة النُسخ نظرا لكون كامل ملفات وتاريخ المشروع الذي تعمل عليه مُتوفرة بشكل محلي على خلاف باقي الأنظمة التي تحتاج إلى الاتصال بأجهزة وخواديم أخرى. فعلى سبيل المثال فحتى ولو احتجت إلى إلقاء نظرة على تاريخ المشروع أو مقارنة نسخ قديمة منه فإنك لن تحتاج إلى الاتصال بخادوم بعيد للقيام بذلك حيث يقوم Git بقراءته والاحتفاظ به كاملا على جهازك المحلي. هذه الخاصية تسمح لك أيضا بمواصلة العمل على مشروعك حتى ولو لم يكن لديك اتصال إنترنت، حيث يُمكنك إيداع البيانات commit في انتظار أن يكون بمقدورك الاتصال بالإنترنت من جديد. على باقي أنظمة إدارة الإصدارات، مواصلة العمل لدى انقطاع الإنترنت هو إما أمر مُستحيل أو معقد جدا. على نظام Perforce لا يُمكن القيام بأي شيء ما لم تكن مُتصلا بالخادوم الذي يدار عليه المشروع، أما Subversion وCVS فيسمحان لك بتحرير الملفات لكن لا يُمكن إدراج التغييرات ما لم تكن متصلا بخادوم مشروعك. قد لا تبدو هذه الخاصية مُهمة جدا لكن تأثيرها كبير. المحافظة على سلامة الملفات يتم التحقق من الملفات عبر عمليات checksum قبل حفظها، ومن ثم يتم استخدام ناتج هذه العملية (Cheksum) كمُعرف للملفات، يعني ذلك بأنه يستحيل تغيير مُحتوى ملف أو مُجلد من دون أن يعلم Git بذلك. هذه الخاصية هي إحدى الخواص التي بُني عليها Git والتي يعتمد عليها في الطبقات السُفلى من النظام، كما أنها جزء لا يتجزأ من المبادئ الأساسية التي بُني عليها. بفضل هذه الخاصية فإنه من غير المُمكن أن تفقد أية بيانات لدى نقلها أو أن يُصاب ملف ما بالعطب من دون أن يكون هناك لدى Git علم مُسبق بالأمر. يُطلق على الآلية التي يعتمد عليها Git للتحقق من الملفات اسم SHA-1 hash، تنتج عنها سلسلة نصية مُتكونة من 40 محرف ستعشري (الأرقام من 0 إلى 9 والحروف من a إلى f) ويتم حسابها اعتمادا على مُحتوى الملف أو المُجلد. سلاسل SHA-1 hash تكون مُشابهة للسلسلة التالية: 24b9da6552252987aa493b52f8696cd6d3b00373 لدى استخدامك لنظام Git ستشاهد هذه السلاسل في كل مكان بحكم أنه يتم استخدامها كثيرا حيث أن Git يحفظ كل شيء اعتمادًا على Hash مُحتوى كل ملف وليس اعتمادًا على أسمائها. لا يقوم Git عادة سوى بإضافة بيانات أغلب العمليات التي تقوم بها على Git تقوم بإضافة بيانات إضافية إلى قاعدة بيانات Git. من الصعب جدا أن تجعل النظام يقوم بعمليات لا يُمكن التراجع عنها أو حذف بيانات لا يُمكن استرجاعها. لدى استخدام باقي أنظمة إدارة النسخ فإنه من المُمكن أن تخسر أو تعبث ببيانات لم تقم بإدراجها بعد، لكن لدى القيام بعملية إدراج فإنه من الصعب جدا أن تفقدها خاصة ما إذا كنت تقوم بدفع البيانات إلى مستودع موجود على خادوم بعيد. هذا الأمر يجعل استخدام Git مُمتعا في حد ذاته، حيث أنه بإمكاننا القيام بأية تجارب نودها على مشاريعنا دون أن نعرض بياناتها للخطر. الحالات الثلاث للبيانات على Git إن قرأت ما سبق ذكره على عجل، فيجب أن تركز جيدا في هذه الفقرة حيث أننا سنناقش المبدأ الأساسي الذي لا يُمكن لك استخدام Git ما لم تستوعبه جيدا. لدى استخدام Git فإن الملفات تملك إحدى الحالات الثلاث التالية: مرحلة الإيداع (ملفات تم إيداعها committed)، مرحلة التعديل (ملفات أجريت تعديلات عليها modified) ومرحلة الإدراج (ملفات تم إدراجها staged). المقصود بالإيداع commit هو أن حفظ البيانات بشكل آمن في قاعدة البيانات المحلية. الملفات التي تم تعديلها هي التي تم إدخال تغييرات عليها لكنه لم يتم حفظ تلك التغييرات إلى قاعدة البيانات المحلية بعد. أما الإدراج فهو تعليم الملفات التي تم تعديلها في حالتها الحالية ليتم تضمينها في الإيداع القادم. هذه الحالات الثلاث تُقسم مشاريع Git إلى 3 أقسام: مُجلد Git، مُجلد العمل، ومنطقة الإدراج. الشكل 3: مُجلد Git، مُجلد العمل ومنطقة الإدراج مُجلد Git هو المكان الذي يقوم النظام فيه بتخزين البيانات الوصفية metadata وقاعدة بيانات مشروعك، ويُعتبر الجزء الأهم من Git حيث أنه المُجلد الذي يتم نسخه لدى القيام بعملية استنساخ المُستودعات الموجودة على أجهزة أخرى. مُجلد العمل عبارة عن إصدار المشروع الحالي والذي يحتوي ملفات تم استخراجها من قاعدة بيانات Git ووضعها تحت تصرفك لإدخال التعديلات التي ترغب فيها عليها. منطقة الإدراج عبارة عن ملف واحد يتم الاحتفاظ به عادة داخل مُجلد Git والذي يحفظ معلومات حول المُحتوى الذي سيتم إرساله في الإيداع القادم. عادة ما تتم الإشارة إليه باسم الفهرس index لكن استخدام مُصطلح “منطقة الإدراج” staging area أصبح الأكثر شيوعا. سير عمل مشاريع Git يتم عادة على النحو التالي: التعديل على الملفات الموجودة في مُجلد العمل. إضافة الملفات إلى منطقة الإدراج إيداع الملفات commit، أي التقاط صورة عن الملفات الموجودة في منطقة الإيداع وحفظها بشكل دائم داخل مُجلد Git. لتلخيص الأمر: إذا كان الملف موجودا داخل مُجلد Git فهذا يعني بأنه تم إيداعه committed. إذا تم إدخال تغييرات عليه وتم وضعه في منطقة الإيداع فهذا ملف مُدرج staged. أما إذا تم إحداث تغييرات عليه ولم يتم نقله إلى منطقة الإدراج فيُعتبر مُعدّلا. في مقال قادم سنتحدث بشكل أوسع حول الحالات الثلاث هذه وكيفية الاستفادة منها بشكل جيد، إضافة إلى كيفية تجاوز منطقة الإدراج بشكل كامل. ترجمة -وبتصرف- لـ Git Basics من كتاب Pro Git لصاحبه Scott Chacon.