أساسيات Git

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 35 دقيقة (بمعدل فنجانين من الشاي وراحة ما بينهما 😁)

المقدمة

اليوم لدينا مقالة دسمة إلى حد ما، سأتكلم في هذه المقالة عن الـ Git وكيف يعمل وكيف نستفيد منه ونستخدمه وما أهم الأوامر الخاصة به
وكيف يفيدنا في تنظيم مشاريعنا وتنظيم طريق العمل ضمن فريق

في هذه المقالة سأركز على الـ Git في بيئة عمل Local أي على جهازك الشخصي

ثم في مقالة أخرى سنتكلم عن مفهوم الـ Branch

ويمكنكم قراءتها من هنا git with branches

ثم في مقالة أخرى سنتطرق لجزء الـ Remote الخاص بالـ Git وكيف نتعامل مع GitHub وكل تلك الأمور إن شاء الله

يمكنكم قراءتها من هنا git with remote repository

ما هو الـ Git

الـ Git هو باختصار آلة السفر بين الزمن والأبعاد ... أقصد أنه أشهر Version Control System
بمعنى أنه نظام متكامل تم إنشاؤه لتنظيم وإدارة المشروعات بسلاسة ويسجل كل التغيرات والإضافات التي حدثت لمشروع على مر الزمن

بعض من أهم ما يقدمه:

سنتكلم عن كل هذه التفاصيل وأكثر

كيف يتعامل الـ Git مع مشروعك ؟

هناك عدة مراحل يتخذها الـ Git للتنقل الحالات المختلفة لمشروعك والتعديلات
وهذه المراحل مهمة جدًا في أن تستوعبها وتفهمها جيدًا لأنها ستكون أساس كل شيء سنشرحه في باقي هذه المقالة

ملحوظة: أول شيء عليك معرفته هو أن الـ Git ينشيء مجلد يدعى .git داخل المشروع الخاص بك
وهو المنزل الخاص بـ Git الذي يضم كل شيء متعلق به
بما في ذلك الـ Local Repository والـ Staging Area و Stash وكل شيء متعلق ويستخدمه الـ Git

Working Directory

الـ Working Directory وأيضًا يطلق عليه الـ Working Tree
هو المجلد الأساسي الذي يحتوي على ملفات المشروع على جهازك

Local Repository

الـ Local Repository هو مستودع يستخدمه الـ Git ليحتفظ ويتابع التعديلات الموجودة في الـ Working Directory الخاص بك على جهازك
ويطلق عليه عدة مسميات مثل Git Repository أو Git Directory أو Git History

Upstream Repository

الـ Upstream Repository أو الـ Remote Repository
هو كالـ Local Repository لكنه نسخة رئيسية من المشروع يتم استضافتها في مكان ما على الانترنت مثل GitHub أو GitLab أو غيرهم كثير
ويسهل العمل مع فريق من أماكن مختلفة ويقدم مميزات كثيرة تساعد على ها وتحقيق اقصى استفادة ممكنة في العمل ضمن فريق
بمعنى أن كل عضو لديه نسخته من المشروع ويعدل في الـ Local Repository الخاص به ثم في النهاية يرفع تلك التعديلات على الـ Remote Repository ليتشاركها مع الجميع وكل الفريق يتابع آخر التعديلات التي قام بها كل عضو

Staging Area

الـ Staging Area وتسمى أيضًا بالـ Index أو Cache هي مرحلة وسيطة ما بين الـ Working Directory والـ Local Repository
تستخدم لوضع التعديلات التي قمنا بها في الـ Working Directory قبل نقلها إلى الـ Local Repository
أو العكس لوضع التعديلات التي ترغب في التراجع عنها من الـ Local Repository قبل إزالتها من الـ Working Directory

هذه المنطقة الوسيطة وجدت لتجنب التعديل والتغير المباشر ما بين الـ Working Directory و الـ Local Repository
لكي يتم مراجعتها جيدًا والتأكد من كل شيء قبل تنفيذ العملية التي نريدها سواء إضافة أو تعديل أو حذف
ومن المهم دائمًا وجود مرحلة تأكيدية كتلك لتجنب الأخطاء الغير مقصودة أو الاستعجالية التي تحدث عندما نفذ التعديل بشكل مباشر

وأيضًا يحتفظ بالملفات الـ Tracked ليتابعها ويقارنها مع النسخة التي في الـ Working Directory ليتأكد هل تم تعديلها أم لا

Stash

الـ Stash هو مكان يستخدم لحفظ التعديلات التي قمت بها بشكل مؤقت، ثم يمكنك العمل على شيء آخر أو أن تنتقل
إلى branch آخر، ثم بعد انتهائك يمكنك أن تعود وتستحضر التعديلات المحفوظة التي قمت بها من الـ Stash في أي وقت

كيف يتعامل Git مع ملفات المشروع ؟

نستطيع أن نقول أن Git يصنف الملفات لعدة حالات ليسهل عليه التعامل معها ويتابعها ويراقب التغيرات

Untracked Files

الـ Untracked Files هي الملفات التي لا يتم متابعتها من قبل Git
بمعنى أنها ملفات موجودة في الـ Working Directory ولكن Git لا يعرف شيئًا عنها ولم يتخذ أي إجراء تجاهها من قبل
أي أنها لم تُنقل إلى الـ Staging من قبل أو تم حذفها من الـ Staging والـ Git لم يعد يتابعها

Tracked Files

الـ Tracked Files هي الملفات التي أصبح Git يدرك وجودها ويتابع أي تغير فيها
ويملك نسخة منها في الـ Staging ليتابعها

Modified Files

هي الملفات التي كان Git يتابعها أي أنها Tracked بالفعل لكن حصل لها تعديل ولم تنقل إلى مرحلة الـ Staging بعد
بمعنى أن نسختها التي في الـ Working Directory مختلفة عن نسختها التي يتم متابعتها في الـ Staging

Staged Files

هي الملفات التي كان Git يتابعها أي أنها Tracked بالفعل وحصل لها تعديل ثم نُقلت إلى مرحلة الـ Staging
أي أن نسختها التي في الـ Staging مختلفة عن نسختها التي في الـ Local Repository

أهم أوامر الـ Git

حسنًا سنتخذ مثال وهمي بسيط وسنشرح أهم الأوامر الخاصة بـ Git عن طريق المثال
حسنًا لنتخيل أننا لدينا مشروع لمدونة جميلة وهذا المشروع حاليا فيه ملف واحد يمثل مقالة واحدة

> ls
article_1.txt

التعريف بنفسك

أول شيء نفعله وهو أن تعرف نفسك لـ Git
لذا نستخدم git config --global user.email "[email protected]" و git config --global user.name "your name"
لتسجيل اسمك وبريدك الإلكتروني في Git على مستوى جهازك الشخصي

أما اذا اردت على مستوى المشروع الحالي فيمكنك ازالة --global

> git config --global user.email "[email protected]"
> git config --global user.name "AhmedEl-Tabarani"

git init

نستخدم git init لمرة واحدة فقط داخل المجلد الذي فيه المشروع أي الـ Working Directory لنخبر الـ Git
أن يبدأ بإنشاء مجلد الـ .git الذي يضم كل شيء متعلق به
بما في ذلك الـ Local Repository والـ Staging Area و Stash
يبدأ في مراقبة ومتابعة أي تعديلات في هذا الـ Working Directory

> git init
Initialized empty Git repository in blog/.git/

لاحظ أنه يخبرنا أنه أنشيء Git Repository وهو مجلد يدعى .git وهو المنزل الخاص بـ Git الذي الذي يضم كل شيء متعلق به كما قلنا
وهو مجلد خفي لكن يمكنك ان تراه وتتأكد من وجوده عن طريق أمر بسيط

> ls --all
./  ../  .git/  article_1.txt

ملحوظة: وعندما نقوم بعمل git init لاول مرة لمشروعنا يبدأ Git بعمل branch فارغ يدعى main لا يحتوي على أي commit بعد

git status

نستخدم git status لمعرفة حالة المشروع والملفات التي لدينا
هل تم تعديل الملفات أم لا ؟ هل يوجد ملفات جديدةUntracked أم لا
أو هل هناك ملفات نُقلت للـ Staging أم لا ... إلخ

وأيضا دائمًا ما يساعدك على اقتراح لك بعض الأوامر التي يمكنك أن تقوم بها في الحالة التي أنت فيها

> git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        article_1.txt

nothing added to commit but untracked files present (use "git add" to track)

لاحظ أن git status أخبرنا بالكثير من المعلومات، بما في ذلك:

git add file-name

نستخدم git add عندما نريد نقل التعديلات من الـ Working Directory إلى مرحلة الـ Staging
ويحول الملفات من حالة Untracked إلى Tracked

يمكنك git add ثم اسم الملف أو اسم المجلد الذي تريد نقله إلى مرحلة الـ Staging
أو يمكنك القيام بكتابة git add ثم . أو * لنقل كل التعديلات

> git add article_1.txt

الآن، بعد إضافة الملف article_1.txt إلى مرحلة الـ Staging، يمكننا أن نستخدم git status مجددًا إذا اردنا لكي نرى حالة الملفات الآن

> git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   article_1.txt

لاحظ أنه لم يعد يقول لنا أن الملف article_1.txt انه Untracked
بل يخبرنا أنه الآن في مرحلة الـ Staging وأنه اصبح جاهزًا لكي نقوم بعمل commit لننقله إلى الـ Local Repository
ويقول استخدم git rm --cached <file> لك إن أردنا حذف الملف من مرحلة الـ Staging وإرجاعه مجددا لحالة الـ Untracked

git commit -m nice-message

نستخدم git commit عندما نريد نقل التعديلات من مرحلة الـ Staging إلى الـ Local Repository
وعندما نقوم بعمل commit فهكذا كأننا نقوم بحفظ نسخة جديدة من المشروع والتعديلات التي حصلت فيه

وقد تسمع أن commit ينشيء snapshot لمشروع أي نسخة من المشروع
ويمكننا القول أن الـ Local Repository هو مجموعة من الـ snapshot التي تم حفظها من المشروع

ولاحظ أن git commit تقوم بنقل كل الملفات والتغيرات المتواجدة في مرحلة الـ Staging كحزمة واحدة إلى الـ Local Repository في commit واحد فقط

بمعنى أن commit واحد قد يضم أكثر من ملف وتعديل

ونستخدم -m لكتابة رسالة توضح التغييرات التي تمت في هذا الـ commit
لتساعدك أنت والفريق الذي معك بتبع تاريخ كل التغيرات التي حصلت في المشروع فيما بعد

ويفضل أن تكون الرسالة مختصرة ومعبرة وغالبًا ما تتفق أنت وباقي الفريق على صيغة معينة لكتابة تلك الرسائل التوضحية

> git commit -m "add new article to the blog"
[main (root-commit) 4032898] add new article to the blog
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 article_1.txt

لاحظ أن Git يخبرنا ببعض المعلومات الجميلة منها

وأمر لم يخبرنا به وهو أنه أصبح لدينا مؤشر يدعى HEAD ويؤشر على آخر commit تم اضافته إلى الـ main
وسنتحدث عن هذا بالتفصيل لاحقًا في هذه المقالة

ملحوظة: الـ HEAD يتواجد داخل الـ Local Repository بالطبع، والـ commit الذي يشاور عليه هو ما نراه في الـ Working Directory وسنفهم معنى هذا لاحقًا

حسنًا لنقم بعمل git status مجددًا لكي نرى حالة الملفات الآن

> git status
On branch main
nothing to commit, working tree clean

الآن، يخبرنا Git أنه لا توجد تغييرات جديدة
وبالتالي لا توجد أي ملفات تحتاج إلى الانتقال إلى مرحلة الـ Staging أو عمل لها commit ونقلها إلى الـ Local Repository
ولذا يقول لك أن الـ Working Tree أي الـ Working Directory الخاص بالمشروع نظيف ولا يحتاج إلى أي شيء

git restore file-name

نستخدم git restore ثم اسم الملف عندما يكون الملف حالته Tracked بالنسبة للـ Git لكن تم التعديل عليه
أي أن نسخته التي في الـ Working Directory مختلفة عن نسخته التي في الـ Staging
ونريد التراجع عن هذا التعديل وإرجاع الملف لحالته الأصلية التي كان عليها

ما يقوم به git restore حقًا هو أنه يأخذ نسخة الملف التي في الـ Staging ويستبدلها مع الملف الذي في الـ Working Directory

الـ Staging هو أيضًا يحتفظ بنسخة بالملفات التي يتابعها وعن طريقها يستطيع معرفة إن حدث تعديل أم
بالتالي إن كان هناك ملف تم تعديله في الـ Working Directory فنستطيع عن طريق استخدام git restore استبدالها لترجع وتصبح طبق الأصل لنسختها التي في الـ Staging كما كانت
ونكون قد تخلصنا من التعديلات التي حدثت للملف في الـ Working Directory

لنرى مثال توضيحي، أولًا لنعدل في ملف المقالة article_1.txt ونضيف أي شيء

> echo "In this article we will prove that git is a time machine!" > article_1.txt

بعد ما اضفنا سطر وعدلنا في article_1.txt، لنقم بعمل git status لكي نرى حالة الملف

> git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   article_1.txt

no changes added to commit (use "git add" and/or "git commit -a")

أنظر كيف يقوم الأمر git status بتوضيح كل شيء لك ويساعدك على اتخاذ القرارات
يقول لك هنا:

لذا لكي نخبره أننا نريد التراجع عن التعديل فسنقول له git restore article_1.txt

> git restore article_1.txt

هكذا سيقوم Git بالذهاب إلى الـ Staging واحضار نسخة الملف article_1.txt المتواجده فيه ويستبدلها
بالملف الذي في الـ Working Directory

git restore --staged file-name

هذا هو نفس الأمر السابق لكن مع زيادة --staged
والفرق هنا ان الملف الذي تم التعديل عليه دخل إلى مرحلة الـ Staging بالفعل عن طريق git add
والآن نريد أن نخرجه فقط من مرحلة الـ Staging دون أن نتراجع عن التعديلات التي في الملف في الـ Working Directory

لذا مع زيادة --staged يقوم Git بإحضار نسخة الملف من الـ HEAD المتواجد في الـ Local Repository
ثم يستبدله بالملف الذي في الـ Staging
كهذا سيصبح الملف الذي في الـ Staging مطابق لنسخة الملف الذي في الـ HEAD المتواجد في الـ Local Repository
وهكذا تراجعنا عن التعديل واخرجناه من مرحلة الـ Staging

لنفقم برؤية هذا بشكل عملي

> git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   article_1.txt

no changes added to commit (use "git add" and/or "git commit -a")

لنفترض أننا وصلنا إلى هذه النقطة ثم قمنا بعمل git add بهذا الشكل

> git add article_1.txt

الآن سنقوم بعمل git status لنرى ما الذي حدث

> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   article_1.txt

لاحظ أن الملف تم نقله إلى مرحلة الـ Staging
ونستطيع أن نقوم بعمل commit جديد في الـ Local Repository يضم التعديلات التي قمنا بها
عن طريق git commit

لكن نحن نريد ان نتعلم كيف نتراجع عن هذا التعديل لذا Git يساعدنا ويخبرنا كيف نفعل هذا
يقول لنا إن أردنا إخراج الملف فقط من مرحلة الـ Staging دون ان نتراجع عن التعديلات التي في الملف نستعمل git restore --staged article_1.txt

> git restore --staged article_1.txt

هكذا سيقوم Git بالذهاب إلى الـ HEAD المتواجد في الـ Local Repository واحضار نسخة الملف article_1.txt ويستبدلها بالملف الذي في الـ Staging
بالتالي نسخة الملف التي في الـ Staging ستكون مطابقة لنسخة الملف التي في الـ HEAD
بالتالي كأننا أخرجناه من حالة الـ Staging

لكن التعديلات التي في الملف مازالت موجودة
بمعنى أن نسخة الملف في الـ Working Directory مختلفة عن نسختها التي في الـ HEAD

وتذكر أن الـ HEAD يشير إلى آخر commit قمنا بعمله والذي يضم آخر التعديلات التي قمنا بتسجيلها داخل الـ Local Repository

يمكننا عمل git status ونتأكد

> git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   article_1.txt

no changes added to commit (use "git add" and/or "git commit -a")

أنظر رجعنا لحالتنا الأولى قبل قيامنا بعمل git add article_1.txt
بمعنى أن التعديلات التي في الملف مازالت موجودة في الـ Working Directory لكن نحن فقط أخرجنا الملف من مرحلة الـ Staging

لذا نستطيع أن نقول أن git restore --staged هو الأمر الذي يعكس ما يقوم به الأمر git add

ملحوظة: تذكر git restore يقوم باسترجاع الملف من الـ Staging إلى Working Directory
أما git restore --staged يقوم باسترجاع الملف من الـ HEAD المتواجد في Local Repository إلى Staging

قبل أن نكمل لنقم بعمل git add و commit للملف article_1.txt

> git add article_1.txt

> git commit -m "edit article 1"
[main 6dc050b] edit article 1
 1 file changed, 1 insertion(+)

git rm file-name

نستخدم git rm عندما نريد حذف ملف من المشروع كليًا أي من الـ Working Directory ومن الـ Staging

وما يفعله أنه يحذف الملف من الـ Working Directory ثم يقوم بعمل git add تلقائيًا للملف المحذوف دون أن تشعر
والـ git add غرضها هنا وضع الملف المحذوف في مرحلة الـ Staging لتأكيد على حذفه في الـ commit جديد
بالتالي سيتم إنشاء نسخة جديدة من المشروع يكون الملف محذوفًا فيه

ملحوظة: يمكننا القول أن git rm هو في الحقيقة مزيج بين rm ثم git add

وبما أنها تحذف الملف من الـ Staging هكذا تم تحويل الملف إلى حالة Untracked بالتالي Git لن يتعرف عليه
لانه لم يعد Tracked File بالنسبة له
لكن في هذه الحالة الأمر لن يكون مهمًا لأن الملف لم يعد موجودًا في الـ Working Directory من الأساس

لنأخذ مثال عملي على ما قلناه
لنقم بإنشاء ملف جديد ثم نقوم بعمل git add و commit له
ثم نحاول حذفه من المشروع

> touch article_xyz.txt

> git add article_xyz.txt

> git commit -m "add xyz article"
[main c2d77c1] add xyz article
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 article_xyz.txt

> ls
article_1.txt  article_xyz.txt

الآن أصبح لدينا مقالة جديدة تدعى article_xyz.txt
لكن للأسف الشديد هذه المقالة تم إنشاءها عن طريق الخطأ ونريد أن نحذفها
لذا سنستعمل الأمر الجميل git rm

> git rm article_xyz.txt
rm 'article_xyz.txt'

الآن هنا حصل أمرين:

لنقوم بعمل git status للتحقق من ما قلناه

> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    article_xyz.txt

لاحظ أنه يقول لك Changes to be committed أي أن هذه التعديلات التي في الـ Staging جاهزة لعمل commit
وأهم شيء ستلاحظ أن الملف article_xyz.txt يتم الإشارة إليه بأنه محذوف بمعنى انه سيتم حذفه في النسخة التي سينشئها الـ commit

لاحظ أنه أيضًا يقول لك بكيفية التراجع عن الحذف عن طريق git restore --staged article_xyz.txt
وهذا الأمر كما أوضحنا سيتم إرجاع نسخة الملف من الـ HEAD إلى الـ Staging
هكذا سنستعيد الملف في الـ Staging وسيصبح Tracked كما كان
لكن الملف محذوف أيضًا من الـ Working Directory لذا لكي تسترجعه بشكل نهائي ستحتاج لتنفيذ git restore article_xyz.txt
وهكذا سيتبدل الملف المحذوف من الـ Staging ألى الـ Working Directory
وهكذا سنستعيد الملف ويعود المشروع إلى حالته الاصلية
يمكنك أختبار طريقة ارجاع الملف المحذوف بنفسك

نحن لا نريد أن نسترجع الملف المحذوف لذا سنقوم بعمل الـ commit ليتم إنشاء نسخة من المشروع يكون الملف محذوفًا فيه

> git commit -m "delete xyz article"
[main 937637d] delete xyz article
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 article_xyz.txt

git rm --cached file-name

هذا هو نفس الأمر السابق لكن مع زيادة --cached
وهو في الحقيقة يشبه الأمر السابق لكنه لا يقوم بحذف الملف من الـ Working Directory بل يكتفي بأن يحذفه فقط من الـ Staging

وطالما أن الملف حذف من الـ Staging فقط فسيتم تحويل الملف إلى Untracked بالتالي Git لن يتعرف عليه لانه لم يعد Tracked File بالنسبة للـ Git

وتذكر أن الملف لم يحذف من الـ Working Directory بل حذف فقط من الـ Staging

سنعطي نفس المثال السابق بأننا سننشيء ملف ما ثم نحاول حذف

> touch article_xyz_again.txt

> git add article_xyz_again.txt

> git commit -m "add xyz article again"
[main cbbc411] add xyz article again
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 article_xyz_again.txt

الآن أصبح لدينا مقالة جديدة تدعى article_xyz_again.txt
ونريد أن نحذفها عن طريق الأمر git rm --cached

> git rm --cached article_xyz_again.txt
rm 'article_xyz_again.txt'

الآن ما حصل هنا هو مثل ما حصل الأمر السابق لكن مع اختلاف:

أن الملف حذف فقط من الـ Staging وليس من الـ Working Directory
وبالتطبع تم وضع الملف المحذوف في مرحلة الـ Staging للتجهيزه لعمل commit جديد

لنقوم بعمل git status للتحقق من ما قلناه مجددًا

> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    article_xyz_again.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        article_xyz_again.txt

حسنًا أريدك أن تركز جيدًا في ما سأشرحه الآن
الأمر git status يخبرنا بمعلومتين مهمتين

الأولى أن الملف الذي حذفناه تم وضعه في مرحلة الـ Staging مع الإشارة إليه بأنه محذوف بمعنى انه سيتم حذفه في النسخة التي سينشئها الـ commit
الثانية أن نفس الملف موجود بالفعل في الـ Working Directory لكنه Untracked أي أن Git ينظر اليه كملف جديد لا يعرف عنه شيء
وهذا بسبب أن الأمر git rm --cached لا يحذف الملف من الـ Working Directory بل يحذفه فقط الـ Staging وطالما أن الملف حذف من الـ Staging فبالتبعية سيتم تحويل حالة الملف من Tracking إلى Untracking

الآن لدينا شيء مميز هنا وأريدك أن تركز معي، إذا أردنا التراجع عن الحذف ما الذي سنفعله ؟
ستقول لي هذا سهل، فقط سنستخدم git restore --staged article_xyz_again.txt لنخرجه من حالة الـ Staging كما تعلمنا

صحيح، لكن ماذا سيحدث عندما نقوم بعمل git add article_xyz_again.txt ؟
سيتم وضع الملف article_xyz_again.txt في مرحلة الـ Staging وجعله Tracked
حسنًا وثم ؟ فكر وركز هنا جيدًا، ستتفاجيء ان الملف لم يعد مطالب حذفه
والمشروع عاد كمان كان إلي حالته الأصلية

بمعنى أن تنفيذ الأمر git add article_xyz_again.txt كان مساويًا تماما للأمر it restore --staged article_xyz_again.txt في هذه الحالة بالتحديد

هل تستطيع التفكير بالسبب ؟

السبب بسيط وهو أن الأمر git rm --cached، حذف الملف من الـ Staging فقط لا غير وأبقى الملف كما هو في الـ Working Directory
وهذا الملف الآن الموجود في الـ Working Directory مازال مساويًا للنسخة الموجودة في الـ HEAD داخل الـ Local Repository

كل ما في الأمر أنه ليس في الـ Staging!

لذالك عندما نستعمل git add سيتم إضافة الملف من الـ Working Directory إلى الـ Staging
وعندما نستعمل git restore --staged يتم نقل الملف من الـ HEAD إلى الـ Staging

إذًا في كلتا الحالتين ستكون نسخة الملف في الـ Staging متساوية مع النسخة التي في الـ Working Directory ومتساوية مع النسخة الموجودة في الـ HEAD المتواجد داخل الـ Local Repository

هكذا طالما أن كل النسخ متساوية، فكل شيء عاد كما كان قبل حذف الملف
والملف سيرجع Tracked كمان كان لأنه في الـ Staging
لذا النتيجة ستكون ذاتها بأن اصبحت كل النسخ متساوية

أرجوا أن تكون الفكرة وصلت الأمر يحتاج فقط لفهم طبيعة عمل كلا الأمرين git add وgit restore --staged

لنعود للأمر git status

> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    article_xyz_again.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        article_xyz_again.txt

ونقول أننا نريد عمل commit لإنشاء نسخة من المشروع يكون الملف محذوفً فيه

> git add article_xyz_again.txt

> git commit -m "delete xyz article again"
[main be7a504] delete xyz article again
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 article_xyz_again.txt

الآن تم إنشاء نسخة جديدة لا يتواجد فيها الملف article_xyz_again.txt
لكن تذكر أن ملف لازال موجودًا في الـ Working Directory وحالته Untracked

لنتأكد من هذا عن طريق git status

> git status
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        article_xyz_again.txt

nothing added to commit but untracked files present (use "git add" to track)

يمكنك أن تمسحه يدويًا الآن إن أردت أو أن تحتفظ به أو تضيف مجددًا، فلا يهم افعل ما تريده

أنا سأقوم بحذفه يدويًا لأنني لن احتاجه مجددًا

> rm article_xyz_again.txt

> ls
article_1.txt

git log

نستخدم git log عندما نرغب في مشاهدة تاريخ المشروع بالكامل وكل التعديلات التي تمت عليه من بداية المشروع حتى اللحظة الحالية
وكذلك لمعرفة من قام بإجراء التعديل ومتى قام بها

لذا عندما تريد رؤية جميع التغييرات والـ commit التي قمت بها في المشروع، فقط بضغطة زر تنفذ هذا الأمر البسيط git log

> git log
commit be7a50440aa044d331741b5af4758e23a190760d (HEAD -> main)
Author: AhmedEl-Tabarani <[email protected]>
Date:   Fri Apr 12 03:17:02 2024 +0200

    delete xyz article again

commit cbbc4119e47737ea5c96cd441b7bc69f51b16f1b
Author: AhmedEl-Tabarani <[email protected]>
Date:   Fri Apr 12 01:51:45 2024 +0200

    add xyz article again

commit 937637d020fbc80114f291c841d2f17f88f6178a
Author: AhmedEl-Tabarani <[email protected]>
Date:   Fri Apr 12 01:02:54 2024 +0200

    delete xyz article

commit c2d77c1cd3b94208816aefa916c8feb7f933f8ec
Author: AhmedEl-Tabarani <[email protected]>
Date:   Thu Apr 11 23:55:25 2024 +0200

    add xyz article

commit 6dc050b8d781d0247277fda29e8c330cd8b7f689
Author: AhmedEl-Tabarani <[email protected]>
Date:   Thu Apr 11 05:50:45 2024 +0200

    edit article 1

commit 4032898bcaf1dd2b5fbbaac2f15840e93097955f
Author: AhmedEl-Tabarani <[email protected]>
Date:   Thu Apr 11 03:25:33 2024 +0200

    add new article to the blog

وسيتم عرض لك جميع الـ commit التي كانت تخزن في الـ Local Repository
بالإضافة إلى عدة أمور منها الـ hash الخاص بالـ commit واسم الشخص الذي قام بالتعديل والوقت الذي تم فيه التعديل والرسالة التي كتبها مع الـ commit
فكما ترى لدينا العديد من الـ commit التي أنشأناها أثناء الشرح

لاختصار كمية المعلومات يمكننا ان ننفذ الأمر هكذا git log --oneline

> git log --oneline
be7a504 (HEAD -> main) delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article
6dc050b edit article 1
4032898 add new article to the blog

وكما ترى فأنه مفيد جدًا لرؤية آخر الـ commit التي اجريناها في المشروع
قد تجدني استخدم بعض الخيارات الاضافية معه كهذا git log --all --decorate --oneline --graph

git revert commit-hash

نستخدم git revert عندما نريد التراجع عن commit معين

وما يقوم به أنه ينشيء commit جديد يعكس ويتراجع عن التعديلات التي كانت في الـ commit المعين الذي حددناه
بمعنى أنه يقوم بعكس التغيرات التي كانت في الـ commit الذي نريد التراجع عنه
بالتالي ما أُضيف سيُحذف وما حُذف سيعاد اضافته مجددًا

ثم سيتم تطبيق التغيرات فورًا في commit جديد دون وضعها في الـ Staging
بل سيتم اضافة هذا الـ commit الجديد إلى الـ Local Repository مباشرةً

لنتخيل أننا نريد أن نتراجع عن آخر commit

> git log --oneline
be7a504 (HEAD -> main) delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article
6dc050b edit article 1
4032898 add new article to the blog

وآخر commit كان عندما حذفنا الملف article_xyz_again.txt لذا عندما نقوم بعمل الأمر git revert على آخر commit
سيتم عكس الـ commit وسيتم ارجاع الملف article_xyz_again.txt مجددًا

> git revert be7a504
[main cd9c14a] Revert "delete xyz article again"
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 article_xyz_again.txt

الآن إذا نظرنا إلى الملفات ستجد أن الملف article_xyz_again.txt الذي حذفناه عاد إلينا

> ls
article_1.txt article_xyz_again.txt

ودعونا ننفذ الأمر git log --oneline لرؤية الـ commit التي قمنا بعملها

> git log --oneline
cd9c14a (HEAD -> main) Revert "delete xyz article again"
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article
6dc050b edit article 1
4032898 add new article to the blog

لاحظ أنه تم إنشاء commit جديد ليعكس ويتراجع عن التعديلات التي كانت في الـ commit السابق

git reflog

الـ git reflog يشبه شقيقه git log لكنه متخصص في تتبع كل حركات الـ HEAD وكيف تحرك
ويستطيع تتبع عدة امور منها commit، revert، reset و checkout وغيرها من الأمور وحتى وان حذفت commit يتم تسجيلها في الـ reflog

> git reflog
2d4a2f8 (HEAD -> main) HEAD@{0}: revert: Revert "delete xyz article again"
be7a504 HEAD@{1}: commit: delete xyz article again
cbbc411 HEAD@{2}: commit: add xyz article again
937637d HEAD@{3}: commit: delete xyz article
c2d77c1 HEAD@{4}: commit: add xyz article
6dc050b HEAD@{5}: commit: edit article 1
4032898 HEAD@{6}: commit (initial): add new article to the blog

سترى أنه يتم تتبع كل حركات الـ HEAD هنا وسترى يسجل كل الـ commit حتى الـ revert سجله
وفيما سنتعرف على الـ reset وسنرى انه يسجله أيضًا هنا
وسترى كيف سيساعدنا reflog عندما نريد تحريك الـ HEAD وتتبع خطواته

لكن لحظة واحدة ما هو الـ HEAD ؟

الـ HEAD الكبير

لنتكلم الآن عن الـ HEAD لأننا اصبحنا نراه ونتكلم عليه كثيرًا ولا نعرف ما هو بالتحديد
الـ HEAD هو مؤشر يشاور على commit ما وغالبا ما يؤشر على آخر commit حصل في الـ branch
لكن يمكننا تغير مكانه وجعله يؤشر على commit قديم وهكذا وسنرى ذلك لاحقًا

مكان الـ HEAD يعكس محتوى الملفات التي في الـ Working Directory بمعنى إذا غيرنا مكان الـ HEAD وجعلناه يشاور على commit قديم
فإن محتوى الـ Working Directory ستكون نفس حالة المشروع التي كان عليها في هذا الـ commit القديم

بمعنى أن الـ HEAD هو جهاز السفر عبر الزمن الذي تحدثنا عنه ويستطيع السفر إلى أي commit ليرى كيف كان شكل المشروع في هذا الـ commit
فيستطيع جلب التعديلات التي كانت في commit معين ويطبقها مباشرةً في الـ Working Directory أويجعلها في الـ Staging لرؤيتها ونراجعها قبل أن ننقلها للـ Working Directory إن اردنا

كيف نغير الـ commit الذي يشاور عليها الـ HEAD ؟ يوجد العديد من الأوامر التي نستطيع أن نستخدمها لتحريك الـ HEAD ومن ضمنها git reset, git checkout, git switch

git reset commit-hash

نستطيع أن نغير مكان الـ HEAD عن طريق أمر الـ git reset
وهو يأخذ الـ commit الذي تريد أن تنقل الـ HEAD إليه

هناك ثلاث خيارات مهمة في الـ git reset وهم --soft, --mixed, --hard

ويتشاركوا في:

git reset --soft commit-hash

الأمر git reset --soft يحرك الـ HEAD إلى commit المعين

لنتخيل أننا قمنا بالعديد من الاضافات والتعديل وهذا هو شكل الـ git log الخاص بنا الآن

> git log --all --oneline
290f0eb (HEAD -> main) add article 3 # new commit
9ebb273 add article 2 # new commit
0035199 edit article 1 # new commit
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article # <--- we will reset soft the HEAD to this commit here
6dc050b edit article 1
4032898 add new article to the blog

هنا قمنا بالتعديل على المقالة الاولى وقمنا باضافة مقالات جديد المقالة الثانية والثالثة

> ls
article_1.txt article_2.txt article_3.txt

ثم قمنا بعمل git reset --soft c2d77c1
الآن ما حصل أن الـ Working Directory لم يحدث له أي شيء

> ls
article_1.txt article_2.txt article_3.txt

نحن فقط ذهبنا إلى الـ commit الـ c2d77c1 ونستطيع أن نتأكد من موقع الـ HEAD عن طريق git log --oneline

> git log --all --oneline
c2d77c1 (HEAD -> main) add xyz article # <--- now the HEAD is here
6dc050b edit article 1
4032898 add new article to the blog

لترى أن HEAD أصبح يشاور بالفعل على الـ commit الذي حددناه
لكن ستلاحظ أن كل الـ commit التي كانت بعده اختفت، لكنها لم تختفي تمامًا لا تقلق نستطيع الرجوع لها عن طريق الـ reflog وسنرى ذلك لاحقًا

وعلى أي حال كل التعديلات التي كانت ما بين موقع الـ HEAD السابق والموقع الجديد في الـ commit تم نقلها إلى الـ Staging
ويمكننا التأكد من هذا عن طريق الأمر git status

> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   article_1.txt
        new file:   article_2.txt
        new file:   article_3.txt
        deleted:    article_xyz.txt

كما ترى برغم من أننا انتقلنا إلى commit قديم إلا أنه احتفظ بالملفات والتعديلات التي كانت موجودة لدينا في الـ Working Directory قبل ان نتنقل للـ commit القديم

وقام بوضعهم في مرحلة الـ Stagingكما لو أنه قام بتنفيذ git add . بعد أن قام بعمل git reset --soft

نحن كأننا رجعنا بالماضي باستخدام git reset
حسنًا لنقل أننا نريد ان نعود للـ commit الأساسي الذي كنا فيه قبل قيامنا بتنفيذ الـ reset
بمعنى أننا نريد أن نرجع مجددًا إلى المستقبل حيث كنا ما الذي سنفعله ؟

نستطيع أن نستخدم git reflog لنتتبع أين كان الـ HEAD وكيف تحرك

> git reflog
c2d77c1 (HEAD -> main) HEAD@{0}: reset: moving to c2d77c1
290f0eb HEAD@{1}: commit: add article 3 # <--- we was here
9ebb273 HEAD@{2}: commit: add article 2
0035199 HEAD@{3}: commit: edit article 1
be7a504 HEAD@{4}: commit: delete xyz article again
cbbc411 HEAD@{5}: commit: add xyz article again
937637d HEAD@{6}: commit: delete xyz article
c2d77c1 (HEAD -> main) HEAD@{7}: commit: add xyz article
6dc050b HEAD@{8}: commit: edit article 1
4032898 HEAD@{9}: commit (initial): add new article to the blog

سترى أنه يتم تتبع كل حركات الـ HEAD نحن فقط الـ commit الذي كما فيه سابقًا قبل الـ reset
وهو كما نلاحظ الخطوة المشار إليها بـ HEAD@{1} لذا يمكننا استخدام reset مجددًا وتحريك الـ HEAD إلى الأمام إلى المستقبل حيث كنا تحديدا إلى HEAD@{1}

> git reset HEAD@{1}

ثم سنرى أننا عدنا الى ما كنا عليه

> git log --all --oneline
290f0eb (HEAD -> main) add article 3
9ebb273 add article 2
0035199 edit article 1
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article
6dc050b edit article 1
4032898 add new article to the blog

وأيضًا تم تسجيل كل تحركات الـ HEAD هنا في reflog تم تسجيل ذهابه إلى commit قديم ثم رجوعه مجددًا

> git reflog
290f0eb (HEAD -> main) HEAD@{0}: reset: moving to HEAD@{1}
c2d77c1 HEAD@{1}: reset: moving to c2d77c1
290f0eb (HEAD -> main) HEAD@{2}: commit: add article 3
9ebb273 HEAD@{3}: commit: add article 2
0035199 HEAD@{4}: commit: edit article 1
be7a504 HEAD@{5}: commit: delete xyz article again
cbbc411 HEAD@{6}: commit: add xyz article again
937637d HEAD@{7}: commit: delete xyz article
c2d77c1 HEAD@{8}: commit: add xyz article
6dc050b HEAD@{9}: commit: edit article 1
4032898 HEAD@{10}: commit (initial): add new article to the blog

git reset --mixed commit-hash

الأمر git reset --mixed يقوم بكل الأشياء التي تحدثنا عنا سابقًا مع --soft
الفرق أنه لا يضع التعديلات في الـ Staged

يمكننا أن نقول أن --mixed تساوي --soft لكن بدون عمل git add، ونقل كل التعديلات من الـ Staging إلى الـ Working Directory

لنرى مثالًا عمليًا على هذا وسنرجع لشكل الـ git log السابق

> git log --all --oneline
290f0eb (HEAD -> main) add article 3
9ebb273 add article 2
0035199 edit article 1
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article # <--- we will reset mixed the HEAD to this commit here
6dc050b edit article 1
4032898 add new article to the blog

لنرى الملفات

> ls
article_1.txt article_2.txt article_3.txt

الآن نقوم بعمل بعمل git reset --mixed c2d77c1 أو git reset c2d77c1 لأن --mixed هي القيمة الافتراضية كما قلنا

> git reset --mixed c2d77c1
Unstaged changes after reset:
M       article_1.txt
D       article_xyz.txt

الآن ما حصل هو تمامًا ما حصل مع --soft:

لكن الفرق ما بين --soft و--mixed هو أن في --mixed كل التعديلات التي كانت ما بين موقع الـ HEAD السابق والموقع الجديد في الـ commit لا تنقل إلى الـ Staging كما كان يحصل مع --soft
بل ان التعديلات تظل في الـ Working Directory وأي شيء داخل مرحلة الـ Staging يتم سحبه منها واخراجه إلى الـ Working Directory

ويمكننا التأكد من هذا عن طريق الأمر git status

> git status
On branch main
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   article_1.txt
        deleted:    article_xyz.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        article_2.txt
        article_3.txt

كما ترى الملفات article_2.txt وarticle_3.txt لم يتم حذفهما بل بقيا كما هما في الـ Working Directory
لكن أصبحا Untracked ولم يتم إضافتها للـ Staging كما كان الأمر مع --soft

والتعديلات الخاصة بالملف article_1.txt واجراء الحذف الخاص بالملف article_xyz.txt بقيا كما هما في الـ Working Directory
ولكن لم يتم إضافتها للـ Staging كما كان الأمر مع --soft

ملحوظة: ما تراه في الـ git status بعد قيامنا بتنفيذ --mixed إذا قمنا بعمل git add . فسنحصل على نفس git status إذا نفذنا --soft

لنتأكد من هذا نحن فقط سنقوم بعمل git add. ونرى أن الأمر git status تغير واصبح مطابق مع ما نراه مع --soft

> git add .

> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   article_1.txt
        new file:   article_2.txt
        new file:   article_3.txt
        deleted:    article_xyz.txt

git reset --hard commit-hash

الـ --hard خطير ويجب الحذر منه لأنه:

ملحوظة: أحذر من استخدام --hard فهذا سيعدل الملفات المتواجدة في الـ Working Directory وسيجعلها مطابقة للـ HEAD أو للـ commit الذي نحدده بالتالي سيتخلص من أي Modified Files وكل التعديلات التي تقوم بها حاليًا في الـ Working Directory وفي الـ Staging ستختفي دون رجعة

لذا قبل أن تستخدم git reset --hard ويوجد ملفات يتم التعديل عليها حاليًا في الـ Working Directory فيجب أن تنقل هذه التعديلات في مكان آخر قبل أن تقوم بعمل git reset --hard وهنا يأتي دور الأمر git stash، والذي نتكلم عنه بالتفصيل لاحقًا في هذه المقالة

لنرى مثالًا عمليًا على هذا وسنرجع لشكل الـ git log السابق

> git log --all --oneline
290f0eb (HEAD -> main) add article 3
9ebb273 add article 2
0035199 edit article 1
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article # <--- we will reset hard the HEAD to this commit here
6dc050b edit article 1
4032898 add new article to the blog

لنرى الملفات

> ls
article_1.txt article_2.txt article_3.txt

الآن نقوم بعمل بعمل git reset --hard c2d77c1

> git reset --hard c2d77c1
HEAD is now at c2d77c1 add xyz article

لنرى الملفات

> ls
article_1.txt article_xyz.txt

لنرى git status

>git status
On branch main
nothing to commit, working tree clean

ما الذي حصل ؟
المشروع عاد كما كان في هذا الـ commit وهو c2d77c1
وكل التعديلات التي قمنا بها بعد هذا الـ commit تم التخلص منها
ولم يتم نقلها إلى الـ Staging كما كان يحصل

واذا كنت تملك أي ملفات يتم التعديل عليها في الـ Working Directory أو كانت في مرحلة الـ Staging
ولم يتم إنشاء أي commit أو عمل لها stash فهذه التعديلات تم التخلص منها نهائيًا دون رجعة

بالطبع يمكننا استرجاع أي commit باستخدام الأمر git reflog

جدول لتلخيص الفروق بين soft و mixed و hard في git reset

الاختيار الملفات التي في الـ Working Directory التعديلات التي في مرحلة الـ Staging التعديلات التي في الـ Working Directory ولم تُنقل إلى مرحلة الـ Staging بعد التعديلات التي حدثت ما بين الـ HEAD والـ commit المحدد
soft تظل كما هي تظل كما هي تُنقل إلى مرحلة الـ Staging تُنقل إلى مرحلة الـ Staging
mixed تظل كما هي تُنقل إلى الـ Working Directory تظل كما هي في الـ Working Directory تظل كما هي في الـ Working Directory
hard يتم استبدالها اجباريًا لجعل الـ Working Directory يطابق النسخة التي كان عليها في الـ commit المحدد يتم التخلص منها يتم التخلص منها يتم التخلص منها

git checkout commit-hash

الـ git checkout لها عدة استخدامات منها:

استعادة ملف من commit معين

لنرى بعض الأمثلة عليه أولًا لنرى الـ git log المعتاد

> git log --all --oneline
290f0eb (HEAD -> main) add article 3
9ebb273 add article 2
0035199 edit article 1
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article
6dc050b edit article 1
4032898 add new article to the blog

والملفات كما هي

> ls
article_1.txt article_2.txt article_3.txt

الآن سنحاول احضار الملف article_xyz.txt الذي كانت في الـ commit الـ c2d77c1

> git checkout c2d77c1 article_xyz.txt
Updated 1 path from 7847fb1

الملف article_xyz.txt الذي كان في الـ commit الـ c2d77c1 أصبح في مرحلة الـ Staging
ويمكننا التأكد من هذا عن طريق git status

> git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   article_xyz.txt

نقل الـ HEAD إلى commit محدد (detached HEAD)

يختلف الـ git checkout عن الأمر git reset في طريقة تغير مكان الـ HEAD إلى الـ commit المحدد

الـ git reset كان يغير مكان الـ HEAD والـ Branch في آن واحد إلى الـ commit المحدد
وكان يخفي ويتخلص من كل الـ commit التي كانت ما بين مكان الـ HEAD والـ commit المحدد
وكنا نستخدم طريقة ملتوية وهي الاستعانة بالأمر reflog لكي نستطيع أن نرجع الـ commit التي فقدت

أما مع git checkout فالأمور مختلفة قليلًا

لنرى مثالًا عمليًا، لدينا الـ git log المعتاد

> git log --all --oneline
290f0eb (HEAD -> main) add article 3
9ebb273 add article 2
0035199 edit article 1
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 add xyz article
6dc050b edit article 1
4032898 add new article to the blog

والملفات كما هي

> ls
article_1.txt article_2.txt article_3.txt

الآن سنجعل الـ HEAD يذهب إلى الـ commit الـ c2d77c1
الأمر أشبه بقيامنا بعمل git reset --hard لكن إليك الفروقات هنا

> git checkout c2d77c1
Note: switching to 'c2d77c1'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at c2d77c1 add xyz article

حسنًا سترى أنه كما تعودنا من الـ Git أنه دائمًا ما يزودنا بالمعلومات وينصحنا ويشرح لنا ما الذي يجري بالتحديد
هنا هو يقول لك انتبه أن الـ HEAD اصبح يشاور على commit مجهول أي ليس هناك اي branch عليه
وهذه الحالة تسمى detached HEAD لكن لا تقلق ليس هناك شيء تخافه
هو فقط يقول لك أنك يمكنك فعل أي شيء في هذا الـ commit

لكن انتبه اذا كنت تريد عمل تعديلات ثم تريد عمل commit جديد يضم هذه التعديلات فيُنصح أن تنشيء branch في مكان الـ HEAD ليضم التغيرات والـ commit الجديد الذي ستنشئها
لانك ببساطة جعلت الـ HEAD يذهب إلى commit مجهول بالتالي اذا تريد عملت commit جديد ثم حاولت ان تغير مكان الـ HEAD
هكذا لن يكون هناك أي شيء يشير إلى الـ commit الذي أنشأته للتو بالتالي ستفقده

لذا عندما تدخل في حالة الـ detached HEAD وتريد تعديل بعض الأمور وعمل commit جديد
فيفضل أن تنشيء branch جديد سواء عن طريق git switch -c branch-name أو git checkout -b branch-name

لنرى الـ git log

> git log --all --oneline
290f0eb (main) add article 3
9ebb273 add article 2
0035199 edit article 1
be7a504 delete xyz article again
cbbc411 add xyz article again
937637d delete xyz article
c2d77c1 (HEAD) add xyz article
6dc050b edit article 1
4032898 add new article to the blog

لاحظ كيف أن الـ HEAD يشاور على commit الـ c2d77c1 والـ branch المسمى main كما هو لم يتغير مكانه
لذا يمكننا جعل الـ HEAD يرجع لاصله بسهوله عن طريق git checkout main أو git switch main

> git checkout main
Previous HEAD position was c2d77c1 add xyz article
Switched to branch 'main'

هكذا عاد الـ HEAD لمكانه دون مشاكل ودون الاستعانة بالـ reflog

ما هو الـ Stash ؟

كما ذكرنا سابقًا فإن الـ Stash هو مكان أشبه بمخزن يستخدم لحفظ التعديلات التي تقوم بها حاليًا بشكل مؤقت ثم يمكنك العمل على شيء آخر أو أن تنتقل إلى commit أو branch آخر كما كنا نفعل سابقًا
ثم بعد انتهائك يمكنك أن تعود وتستحضر التعديلات المحفوظة التي قمت بها من الـ Stash

تذكر عندما قلنا أن git reset --hard يقوم بتعديل الملفات المتواجدة في الـ Working Directory وسيجعلها مطابقة للـ HEAD أو للـ commit الذي نحدده

ويتخلص من أي Modified Files وكل التعديلات التي تقوم بها حاليًا في الـ Working Directory ستختفي دون رجعة

وقلنا إذا كان يوجد ملفات يتم التعديل عليها حاليًا في الـ Working Directory
ولا تريدها أن تختفي ولا تريد أن تضعها في الـ Staging أو تقوم بعمل commit لها حاليًا
فيمكنك هنا أن تخزن هذه التعديلات في الـ Stash ثم تقوم بعمل git reset --hard كما يحلوا لك
ثم يمكنك في أي لحظة سحب التعديلات التي وضعتها في الـ Stash

أهم أوامر الـ Stash

git stash

يستخدم لتخزين التعديلات التي لم تنقل بعد إلى الـ Staging وتخزينها في الـ Stash يمكنك استخدام -m لكتابة رسالة توضيحية لتذكرك على ماذا كنت تعمل أو لماذا وضعتهم في الـ Stash وهكذا

> git stash -m "test something related to feature xyz"

git stash list

يستخدم لعرض قائمة بكل شيء خزناه في الـ Stash

> git stash list
stash@{0}: On main: test something related to feature xyz
stash@{1}: On main: working on new article, should finish until weekend

git stash pop

يستخدم لاستعادة آخر stash مُخزن ووضعه في الـ Working Directory

> git stash pop
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   feature_xyz.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (2cc2b989e15e8bab916fb20665eb5cc25216c9cf)

git stash apply stash-id

يستخدم لاستعادة stash محدد ووضعه في الـ Working Directory.

> git stash apply stash@{1}
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   new_article.txt

no changes added to commit (use "git add" and/or "git commit -a")

git stash drop stash-id

يستخدم لحذف stash محدد

> git stash drop stash@{0}
Dropped stash@{0} (b34271adfb27cb2e3ffc40546b570b4b9f37bfc4)

خاتمة

كما تلاحظ، الآن أصبح لديك معرفة جيدة عن الأوامر الأساسية للـ Git وكيفية استخدامها
يجب أن تكون قادراً الآن على إدارة مشروعاتك باستخدام Git وتنظيمها والقيام بالتعديلات والتحديثات بكل سهولة

بالطبع أنا لم اشرح جميع وظائف كل أمر، أنا فقط شرحت لك الفكرة والاستخدام العام لكل أمر
لكن اذا تعمقت ستجد ان كل أمر يحتوي على تفاصيل اعمق ودهاليز كثيرة
وأظن أنني اديت وظيفيتي في توصيل الهدف والاستخدام العام وهذا يكفي كبداية لتبدأ انت بجمع واستكشاف هذه الدهاليز والتفاصيل اثناء تقدمك وتعاملك

في مقالة التالية سنتكلم عن الـ Branch في الـ Git كيف
وأنها تعد من أهم المزايا التي يقدمها الـ Git
حيث أنها تساعدنا عن إنشاء عدة نسخ من المشروع، وكل نسخة تستطيع أن تطور فيها وتعدل وتفعل ما تريده دون أن تأثر على نسخة المشروع الأساسية
أو تقسم العمل ضمن الفريق حيث يمكن لكل عضو العمل على نسخته من الكود في فرع مختلف بشكل مستقل عن باقي الفريق

ويمكنكم قراءتها من هنا git with branches

ثم في المقالة التالية سنتكلم الـ Remote Repository وسنشرح مفاهيم وأوامر جديدة تتعلق بهذا العالم الآخر ويمكنكم قراءتها من هنا git with remote repository