عندما يعدّل وكيل البرمجة مشروعكم، تحتاجون إلى رؤية ما تغيّر، وإلى الاحتفاظ بنسخة يمكنكم العودة إليها. يتيح لنا Git القيام بالأمرين معًا. سنشرح أولًا كيف يفصل Git بين الملفات التي تعدّلونها وبين النسخ التي سجّلتموها. ثم نستخدم لعبة صغيرة لنقرأ تغييرات الوكيل ونتحقّق منها، ونسجّلها في commit. وسنستعيد أيضًا ملفًا بعد تعديل غير مرغوب فيه.
لنفترض أن مشروعكم يعمل، وطلبتم من وكيل أن يضيف ميزة. يعدّل الوكيل عدة ملفات ويحفظها. أصبحت هذه الملفات تحتوي على تغييراته.
الحفظ في المحرّر يكتب المحتوى الحالي على القرص. لكن الحفظ وحده لا يُبقي سجلًّا للنسخ السابقة. إذا عطّلت الميزة الجديدة شيئًا، فنحن بحاجة إلى المحتوى السابق لنقارن به أو نعود إليه.
Git هو نظام للتحكم في الإصدارات. أي أنه يحتفظ بسجلّ لنسخ المشروع التي نختار أن نسجّلها.
بفضل هذا السجلّ، يمكننا مقارنة الملفات الحالية بنسخة سابقة، وقراءة الأسطر التي تغيّرت، واستعادة محتوى مسجَّل. وهذا مفيد خصوصًا عند العمل مع وكيل، لأنه قد يغيّر ملفات كثيرة قبل أن نقرأ أيًّا منها.
لنبدأ بالملفات الموجودة على حاسوبكم. ملفات المشروع التي يمكنكم فتحها وتعديلها وتشغيلها تُسمّى شجرة العمل (working tree). وهي تشمل الكود المصدري والملفات الأخرى التي يحتاجها برنامجكم.
عندما تحفظون تعديلًا، أو يكتب الوكيل ملفًا، تتغيّر شجرة العمل. هذا هو العمل الحالي، بما فيه تغييرات لم تراجعوها بعد.
الـ commit نسخة مسجَّلة من الملفات التي يتتبّعها Git. الملفات المتتبَّعة هي التي أدخلناها في نظام Git للتحكم في الإصدارات. لا يضمّ Git تلقائيًا كل ملف في المجلد.
الـ commit أشبه بلقطة لهذه الملفات معًا. بعد إنشاء الـ commit، يمكننا مواصلة تعديل شجرة العمل. وهذه التعديلات لا تغيّر الـ commit الذي أنشأناه من قبل.
يخزّن Git الـ commits في مستودع (repository)، واختصاره repo. وفيه يحتفظ Git بالنسخ المسجَّلة وسجلّها. في الحديث اليومي، يستخدم الناس كلمة repo أيضًا بمعنى المشروع مع ذلك السجلّ.
كل دائرة هنا تمثّل commit واحدًا. الـ commit الجديد يرتبط بالـ commit الذي بُني عليه، لذا يمكننا تتبّع تطوّر المشروع. المستودع وشجرة العمل موجودان على حاسوبنا. وتسجيل الـ commit لا يرفعه إلى أي مكان.
قبل تسجيل الـ commit، نختار التغييرات التي تدخل فيه. يسمّي Git هذا staging. منطقة الـ staging تضمّ نسخًا من الملفات مُعدّةً للـ commit التالي.
الأمر git add يختار المحتوى الحالي للملف للنسخة المُعدّة. ثم يسجّل الأمر git commit النسخة المُعدّة في المستودع. والملفات المتتبَّعة غير المتغيّرة تبقى في اللقطة أيضًا. فاختيار التغييرات لا يعني أن كل الملفات الأخرى تختفي.
هذا الفصل يتيح لنا حفظ وحدة عمل واحدة متماسكة، مثل ميزة واختبارها، مع إبقاء التعديلات غير المكتملة خارجها.
يستطيع الوكيل كتابة هذه الأوامر وتشغيلها نيابةً عنكم. لكن عليكم أن تفهموا ما يفعله كل أمر وأن تقرؤوا نتيجته، كي تعرفوا هل فعل الوكيل ما قصدتموه.
في المثال، يُضاء كل أمر حين يُنفَّذ، وسنشرحه عندما نصل إليه. والصيغة الكاملة موجودة في توثيق Git عندما تحتاجون إليها.
لنطبّق ذلك الآن على مشروع. هذه لعبة صغيرة تعمل في متصفح الويب. تطلق السفينة النار على الغزاة، وتزداد النقاط عندما تصيب أحدهم.
المجلد على اليمين يحتوي على الملفات التي تتكوّن منها اللعبة. الملف index.html هو صفحة الويب. الملف game.js يتحكّم في الحركة والإصابات والنقاط. الملف theme.css يتحكّم في الألوان والمظهر.
اللعبة تعمل بالفعل، وهذا المشروع يستخدم Git مسبقًا. نريد من الوكيل أن يضيف ميزة واحدة: الضغط على P يوقف الحركة والنقاط مؤقتًا، والضغط على P مرة أخرى يستأنفهما.
قبل إضافة الإيقاف المؤقت، نطلب من الوكيل أن يتحقّق من اللعبة الحالية ويسجّل ما أنجزناه حتى الآن. هكذا تكون لدينا نسخة بداية نقارن بها بعد أن يُجري الوكيل تغييراته.
هنا تحقّق الوكيل من اللعبة، وأضاف العمل الحالي إلى الـ staging. سنرى أمر الاختيار بالتفصيل عندما نسجّل ميزة الإيقاف المؤقت.
يشغّل الوكيل git commit ليسجّل تلك النسخة المُعدّة. صارت نسخة البداية من اللعبة commit في المستودع. وملفات العمل ما زالت موجودة ليعدّلها الوكيل.
العبارة «Working game» هي رسالة الـ commit. الخيار -m يمرّر هذه الرسالة إلى git commit. الرسالة المفيدة تصف العمل الذي يُسجَّل، كي يتعرّف عليه من يقرأ السجلّ.
ويعطي Git الـ commit أيضًا مُعرِّفًا. الشكل المختصر المعروض هنا يشير إلى هذه النسخة المسجَّلة بالضبط. سنستخدمه في أمر لنقارن تغييرات الوكيل بنقطة البداية. الرسالة وصف نختاره نحن. أما المُعرِّف فبه نخبر Git بالضبط أيّ commit نقصد.
الأمر git status يعرض حالة العمل الحالي: أيّ التغييرات في الـ staging، وأيّها ليس فيه، وأيّ الملفات الجديدة لا يتتبّعها Git بعد. هنا يقول «working tree clean». فكل الملفات المتتبَّعة تطابق آخر commit، لذا يمكننا أن نبدأ العمل على الإيقاف المؤقت من نسخة معروفة.
نطلب من الوكيل أن يجعل P يوقف اللعبة مؤقتًا ويستأنفها، مع بقاء النقاط كما هي أثناء الإيقاف. قبل أن نسجّل أي شيء في commit، سننظر في التغييرات بأنفسنا.
على اليسار اللعبة تعمل، والغزاة يتقدّمون والنقاط ترتفع. وعلى اليمين اللعبة متوقفة مؤقتًا، والنقاط ثابتة.
يشغّل الوكيل الآن git status بشكله المختصر، الذي يعرض الملفات المتغيّرة والجديدة، ملفًا في كل سطر. ملف منطق اللعبة معلَّم بـ «M»، أي modified، معدَّل. وكتب الوكيل أيضًا ملف اختبار، وهو كود يفحص سلوك اللعبة تلقائيًا. علامتا الاستفهام بجانبه تعنيان أن Git لا يتتبّعه بعد. Git يسمّيه untracked، أي غير متتبَّع.
سنحتاج إلى فحص الملف الجديد، وكذلك التغييرات في الكود الموجود. أما الـ commit المحفوظ نفسه فلم يتغيّر.
الأمر git status يخبرنا أيّ الملفات تغيّرت. ولرؤية ما تغيّر داخل ملف، نقرأ الـ diff. الـ diff مقارنة سطرًا بسطر بين نسختين.
في هذه المراجعة، النسختان هما commit البداية والملفات بعد تعديل الوكيل. نريد أن نحكم هل تنفّذ التغييرات سلوك الإيقاف المؤقت الذي طلبناه.
نُجري هذه المقارنة بأنفسنا. نكتب git diff مع مُعرِّف نقطة الحفظ التي أنشأناها، فيطبع كل سطر يختلف بين الملفات الآن وذلك الـ commit. يمكنكم طلب الـ diff من الوكيل، لكن اقرؤوه بأعينكم، في طرفيتكم أنتم.
شغّلوا هذا من مجلد مشروعكم، مستخدمين مُعرِّف commit البداية لديكم. فالمُعرِّف الظاهر على الشريحة خاص بمثالنا.
هذا مقتطف من منطق اللعبة. علامة الطرح تدلّ على سطر محذوف، وعلامة الجمع على سطر مضاف.
اقرؤوا السطرين المتغيّرين معًا: سطر استُبدل بآخر. السطر القديم كان يُحرّك اللعبة في كل تحديث. والجديد يُحرّكها فقط حين لا يكون الإيقاف المؤقت مفعَّلًا.
لاحظوا السطر الذي تحتهما، وهو يرسم الشاشة. لم يتغيّر، وهذا صحيح. فيجب أن تُرسَم اللعبة حتى أثناء الإيقاف المؤقت.
ملف الاختبار الجديد ليس في هذا الـ diff، لأن Git يقارن فقط الملفات التي يتتبّعها. افتحوه على حدة.
وهو يتحقّق من أن النقاط تزداد أثناء تشغيل اللعبة، وأنها لا تزداد أثناء الإيقاف المؤقت. وهذا يطابق الطلب.
تحقّقوا من أن اللعبة تعمل وأن الميزة تعجبكم قبل قبولها. اضغطوا P، وتحقّقوا من توقّف اللعبة والنقاط معًا. ثم اضغطوا P مرة أخرى، وتحقّقوا من أن اللعبة تُستأنف.
يشغّل الوكيل الآن node --test، الذي يشغّل اختبارات المشروع، بما فيها الاختبار الجديد.
تنجح الأربعة كلها. لو كان هناك خطأ، لطلبنا من الوكيل تصحيحه قبل تسجيل الميزة على أنها مكتملة.
الميزة تعمل، وقد راجعنا كودها واختبارها. نريد تسجيل هذين التغييرين معًا، لذا نطلب من الوكيل تسجيلهما في commit.
يشغّل الوكيل git add، مع اسم ملف اللعبة واسم ملف الاختبار الجديد. هذا يضيف محتواهما الحالي إلى الـ staging للـ commit التالي. وإضافة الاختبار الجديد تجعل Git يتتبّعه أيضًا. لكن لم يُسجَّل أي commit بعد.
الـ staging يختار محتوى الملفات لحظة تنفيذ git add. لنفترض أننا عدّلنا ملف اللعبة مجددًا بعد إضافته إلى الـ staging. صار في شجرة العمل ذلك التعديل اللاحق، لكن نسخة الـ staging ما زالت تحمل المحتوى السابق.
ولإدخال التعديل اللاحق، علينا تشغيل git add مرة أخرى. في مثالنا، نحن جاهزون لتسجيل النسخ التي راجعناها للتوّ.
يشغّل الوكيل الآن git commit مع الرسالة «Add pause control». يسجّل Git التغييرات التي في الـ staging في لقطة جديدة، ويُبقي فيها الملفات المتتبَّعة غير المتغيّرة.
يأتي الـ commit الجديد بعد «Working game» في السجلّ. والنسختان تبقيان متاحتين. وإبقاء الميزة واختبارها معًا يوضّح ما أضافه هذا الـ commit.
بعض الملفات في مجلد المشروع يجب أن تبقى خارج سجلّ نسخه. هنا، في المجلد dist مُخرَجات بناء مولَّدة، يمكن توليدها مجددًا من الكود المصدري. والملف .env يحمل إعدادات محلية قد تتضمّن معلومات سرّية.
الملف .gitignore يسرد المسارات التي يجب أن يتركها Git دون تتبّع. ومحتواه معروض على اليسار. وعمليات git add العادية تستثني تلك الملفات المتجاهَلة.
التجاهل ينطبق فقط على الملفات التي لا يتتبّعها Git بعد. إضافة اسم ملف متتبَّع إلى .gitignore لا تُزيله من أي commit، ولا توقف Git عن تتبّعه.
لنفترض أن التعديل التالي عطّل الإيقاف المؤقت. هذا التعديل لم يُسجَّل في commit، ولا نريد الاحتفاظ به.
يُظهر الـ diff أن الشرط حُذف، ويفشل اختبار الإيقاف المؤقت من جديد.
نطلب من الوكيل استعادة ملف اللعبة من commit الإيقاف المؤقت. فيؤكّد أن هذا سيستبدل التعديلات الحالية في ذلك الملف، وهذا بالضبط ما نريده.
يشغّل الوكيل الآن git restore، ويعطيه مُعرِّف commit الإيقاف المؤقت مصدرًا، وملفَّ اللعبة هدفًا. فيعيد نسخة ذلك الـ commit من هذا الملف وحده إلى شجرة العمل.
ينجح اختبار الإيقاف المؤقت من جديد، وشجرة العمل نظيفة. والـ commits نفسها لم تتغيّر.
هذا يستعيد ملفًا متتبَّعًا واحدًا من commit أنشأناه. في حالات الاستعادة الأخرى، أخبروا الوكيل بما يجب الحفاظ عليه، وافهموا الأمر المقترح قبل تنفيذه. لا يستطيع Git استعادة عمل لم يحفظه قط.
يمكننا الآن أن ندع الوكيل يعمل انطلاقًا من نقطة بداية مسجَّلة، ونفحص تغييراته، ثم نسجّل الميزة بعد مراجعتها في commit خاص بها. وإذا أفسد تعديل لاحق ملفًا، فلدينا نسخة محفوظة نستعيدها.
يستطيع الوكيل المساعدة في صيغة الأوامر. أما مهمتنا فهي أن نفهم أيّ نسخة تُقارَن أو تُسجَّل، وأن نقرّر هل نُبقي التغييرات في المشروع.
نراكم في الفيديو التالي، وفيه سنتعلّم المزيد عن Git.