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

تكملة أوامر الـ Git والتحكم بالـ HEAD

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

يمكنك متابعة السلسلة بالترتيب أو الانتقال مباشرة إلى أي مقال:


المقدمة

في المقالة السابقة تعلمنا أساسيات الـ Git وأهم الأوامر الأساسية مثل git init و git add و git commit و git log وغيرها

ويمكنكم قراءتها من هنا أساسيات الـ Git لتتبع التغييرات في المشاريع

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

سنتعلم كيف نتراجع عن commit معين، وكيف نستخدم الـ HEAD للسفر عبر الزمن بين نسخ المشروع المختلفة
وكيف نحفظ التعديلات مؤقتًا في الـ Stash لنستخدمها لاحقًا

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

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

  • جميعهم يحركوا الـ branch والـ HEAD في آن واحد وليس فقط الـ HEAD إلى الـ commit الذي تم تحديده
  • عندما لا يتم تحديد commit يتم تحديد الـ HEAD كقيمة افتراضية
  • يمكن التراجع عن الأمر بالاستعانة بـ reflog و reset للرجوع للـ commit الأصلي الذي كان عليه الـ HEAD من قبل

git reset --soft commit-hash

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

  • لا يقوم بتعديل الـ Working Directory على الاطلاق بل يظل كما هو قبل الـ git reset
  • لا يفعل أي شيء مع الملفات التي في مرحلة الـ Staging بل تظل كما هي في الـ Staging
  • التعديلات التي حدثت ما بين الـ HEAD و commit يتم نقلها إلى الـ Staging
  • إذا كان هناك تعديلات كنا نعمل عليها ولم تنقل إلى الـ Staging فسيتم نقلها تلقائيًا (كأنك قمت بعمل git add .)

لنتخيل أننا قمنا بالعديد من الاضافات والتعديل وهذا هو شكل الـ 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

  • لا يقوم بتعديل الـ Working Directory على الاطلاق بل يظل كما هو قبل الـ reset
  • كل الملفات التي في مرحلة الـ Staging تخرج من مرحلة الـ Staging وتنتقل إلى الـ Working Directory (كأنك قمت بعمل git restore --staged .)
  • التعديلات التي حدثت ما بين الـ HEAD و commit لا يتم نقلها إلى الـ Staging بل تظل كما هي في الـ Working Directory
  • الـ --mixed هو القيمة الافتراضية للـ git reset

يمكننا أن نقول أن --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:

  • الـ Working Directory لم يحدث له أي شيء
  • الـ HEAD أصبح يشاور بالفعل على الـ commit الذي حددناه
  • وأن كل الـ commit التي كانت ما بين موقع الـ HEAD السابق والموقع الجديد في الـ commit الذي حددناه اختفت
  • يمكننا استرجاعها عن طريق الـ reflog بالنفس الطريق التي فعلناها مع --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 خطير ويجب الحذر منه لأنه:

  • يحرك الـ HEAD إلى الـ commit وينقل التعديلات إلى الـ Working Directory مباشرةً
  • يتخلص من أي تعديلات كانت في الـ Working Directory دون رجعة (أي أنه يتخلص من الـ Modified Files)
  • يتخلص من أي تعديلات كانت في مرحلة الـ Staging دون رجعة

ملحوظة: أحذر من استخدام --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 معين إلى HEAD ووضعه في الـ Staging
  • لتغير مكان الـ HEAD إلى commit محدد كما كان يفعل git reset مع بعض الاختلافات
  • التنقل بين الـ branch, لكن يفضل استخدام git switch لهذا الغرض

استعادة ملف من 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 فالأمور مختلفة قليلًا

  • يتم نقل الـ HEAD فقط إلى الـ commit المحدد ولا يتم تغير موقع الـ Branch
  • التغيرات يتم تطبيقها فورًا في الـ Working Directory
  • إذا كنت تملك ملفات تقوم بالتعديل عليها في الـ Working Directoryأو في الـ Staging
    فلن تستطيع تنفيذ الـ git checkout والـ Git سينبهك بأنك لا يمكنك تحريك الـ HEAD
    وهناك ملفات يتم التعديل عليها
    طالما أن الـ commit الحالي ما بين مكان الـ HEAD مختلفين

لنرى مثالًا عمليًا، لدينا الـ 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

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 وكيفية استخدامها
يجب أن تكون قادرًا الآن على التحكم الكامل في تاريخ مشروعك والتنقل بين الـ commit المختلفة بكل سهولة

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

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

ويمكنكم قراءتها من هنا Git Branches آلة السفر عبر الأبعاد

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