Git ما وراء الأساسيات: سير عمل يوفّر ساعات كل أسبوع
إعادة التأسيس التفاعلية، وcherry-pick، وbisect، وworktrees، وإنقاذ reflog، واستراتيجيات التفرع التي تعمل فعلاً. أوامر Git التي أستخدمها يومياً والتي لا يعرف معظم المطورين بوجودها.
معظم المطورين يتعلمون خمسة أوامر Git ويتوقفون. add، commit، push، pull، merge. وربما checkout وbranch إن كانوا يشعرون بالمغامرة. هذا يكفيك للسنة الأولى. ثم تجد أن فرعك يحتوي على 47 عملية commit برسائل مثل "fix" و"wip" و"please work"، وتعيد تعيين شيء لم يكن عليك إعادة تعيينه بالخطأ، وتقضي 40 دقيقة على Stack Overflow تحاول التراجع عن دمج فاشل.
أستخدم Git منذ سنوات. ليس بشكل عابر — بل بشكل مكثف. فروع متعددة، مستودعات متعددة، متعاونون متعددون، طوال اليوم، كل يوم. ما يلي هو الأوامر وسير العمل التي أستخدمها فعلاً. ليست تلك التي تبدو جيدة في شرح تعليمي. بل التي توفر لي وقتاً حقيقياً، كل أسبوع.
إعادة التأسيس التفاعلية: نظّف فوضاك قبل أن يراها أحد#
فرعك يحتوي على اثنتي عشرة عملية commit. نصفها "fix typo". واحدة تقول "undo previous commit". وأخرى تقول "actually fix it this time". أنت على وشك فتح طلب سحب (PR). لا أحد بحاجة لرؤية هذا التاريخ.
إعادة التأسيس التفاعلية هي الطريقة التي تعيد بها كتابة التاريخ على فرعك قبل مشاركته. تتيح لك دمج عمليات الـ commit معاً، وإعادة صياغة الرسائل، وإعادة ترتيبها، أو حذفها بالكامل.
الأمر الأساسي#
git rebase -i HEAD~5هذا يفتح محرراً يعرض آخر 5 عمليات commit، من الأقدم للأحدث:
pick a1b2c3d Add user authentication endpoint
pick d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
pick l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth testsكل سطر يبدأ بأمر. غيّر pick إلى أحد هذه:
squash(أوs) — دمج هذا الـ commit مع الذي فوقه، وجمع الرسائلfixup(أوf) — نفس squash، لكن مع تجاهل رسالة هذا الـ commitreword(أوr) — الإبقاء على الـ commit مع تغيير رسالتهdrop(أوd) — حذف هذا الـ commit بالكاملedit(أوe) — إيقاف إعادة التأسيس عند هذا الـ commit لتعديله
جلسة تنظيف حقيقية#
إليك ما أفعله فعلاً. ذلك التاريخ الفوضوي أعلاه يصبح:
pick a1b2c3d Add user authentication endpoint
fixup d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
fixup l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth testsاحفظ وأغلق. الآن لديك ثلاث عمليات commit نظيفة بدلاً من خمس. إصلاح الخطأ المطبعي يُدمج في الـ commit الخاص بالمصادقة. إصلاح خطأ تحديد المعدل يُدمج في الـ commit الخاص بتحديد المعدل. مراجع طلب السحب يرى تسلسلاً نظيفاً ومنطقياً.
إعادة ترتيب عمليات الـ Commit#
يمكنك حرفياً إعادة ترتيب الأسطر. إذا كان يجب أن يأتي commit الاختبار قبل commit تحديد المعدل، فقط انقل السطر:
pick a1b2c3d Add user authentication endpoint
pick p3q4r5s Update auth tests
pick h7i8j9k Add rate limitingسيقوم Git بإعادة تشغيل عمليات الـ commit بهذا الترتيب الجديد. إذا كانت هناك تعارضات، سيتوقف ويتيح لك حلها.
اختصار Autosquash#
إذا كنت تعلم أن commit هو إصلاح لواحد سابق، حدده وقت عمل الـ commit:
git commit --fixup=a1b2c3dهذا ينشئ commit برسالة fixup! Add user authentication endpoint. ثم عند إعادة التأسيس:
git rebase -i --autosquash HEAD~5يقوم Git تلقائياً بإعادة ترتيب عمليات الإصلاح أسفل أهدافها مباشرة ويحددها كـ fixup. فقط احفظ وأغلق. لا تحرير يدوي.
أستخدم هذا باستمرار. إنها أسرع طريقة للتكرار على فرع مع الحفاظ على نظافة التاريخ النهائي.
القاعدة الذهبية#
لا تعد تأسيس عمليات commit التي تم دفعها إلى فرع مشترك أبداً. إذا كان أشخاص آخرون قد بنوا عملهم على تلك العمليات، فإن إعادة كتابة التاريخ ستسبب مشاكل حقيقية. أعد تأسيس فروع الميزات الخاصة بك قبل الدمج. لا تعد تأسيس main أبداً.
إذا كنت قد دفعت فرع الميزة بالفعل وتحتاج لإعادة التأسيس:
git push --force-with-leaseعلامة --force-with-lease أكثر أماناً من --force. ترفض الدفع إذا دفع شخص آخر إلى نفس الفرع منذ آخر جلب. لن تمنع جميع المشاكل، لكنها تلتقط الأكثر شيوعاً.
Cherry-Pick: نقل جراحي للـ Commits#
يأخذ Cherry-pick عملية commit محددة من فرع ويطبقها على فرع آخر. ليس دمجاً. ليس إعادة تأسيس. فقط commit واحد، يُطبق بنظافة.
متى أستخدم هذا فعلاً#
السيناريو الأكثر شيوعاً: أصلحت خطأ على فرع الميزة، لكن الإصلاح يحتاج أيضاً للذهاب إلى main أو فرع إصدار الآن. لا أريد دمج فرع الميزة بالكامل. أريد فقط ذلك الإصلاح الواحد.
# ابحث عن هاش الـ commit للإصلاح
git log --oneline feature/user-auth
# a1b2c3d Fix null pointer in session validation
# انتقل إلى main واعمل cherry-pick
git checkout main
git cherry-pick a1b2c3dتم. الإصلاح على main كعملية commit جديدة بنفس التغييرات.
Cherry-Pick بدون عمل Commit#
أحياناً تريد تطبيق التغييرات لكن بدون عمل commit بعد. ربما تريد دمج عدة cherry-picks في commit واحد، أو تعديل التغييرات قليلاً:
git cherry-pick --no-commit a1b2c3dالتغييرات في منطقة التجهيز (staged) لكن لم يتم عمل commit لها. يمكنك تعديلها، إضافة المزيد من التغييرات، ثم عمل commit عندما تكون جاهزاً.
Cherry-Pick بالنطاق#
تحتاج عدة عمليات commit متتالية؟ استخدم صيغة النطاق:
git cherry-pick a1b2c3d..f6g7h8iهذا يعمل cherry-pick لكل شيء بعد a1b2c3d حتى f6g7h8i وشاملاً إياه. لاحظ أن a1b2c3d نفسه مستبعد. إذا أردت تضمينه:
git cherry-pick a1b2c3d^..f6g7h8iالتعامل مع التعارضات#
تعارضات cherry-pick تعمل مثل تعارضات الدمج. عند حدوث واحدة:
# سيخبرك Git أن هناك تعارضاً
# أصلح الملفات المتعارضة، ثم:
git add .
git cherry-pick --continueأو إذا غيرت رأيك:
git cherry-pick --abortشيء يجب الانتباه إليه: عمليات الـ commit التي تم عمل cherry-pick لها تنشئ هاشات commit جديدة. إذا دمجت الفرع الأصلي لاحقاً، فإن Git عادة ذكي كفاية للتعامل مع التكرار. لكن إذا استخدمت cherry-pick بكثرة بين فروع ستُدمج في النهاية، قد ترى تعارضات غير متوقعة. استخدمه بدقة جراحية، وليس كاستراتيجية دمج.
Git Bisect: بحث ثنائي عن الأخطاء#
شيء ما تعطل. تعلم أنه كان يعمل قبل أسبوعين. كان هناك 200 عملية commit منذ ذلك الحين. أيها سبّب العطل؟
يمكنك فحص كل commit يدوياً. أو يمكنك استخدام git bisect، الذي يستخدم البحث الثنائي لإيجاد الـ commit المسبب بالضبط في log2(n) خطوة. لـ 200 عملية commit، هذا حوالي 7-8 فحوصات بدلاً من 200.
الطريقة اليدوية#
# ابدأ البحث الثنائي
git bisect start
# حدد الـ commit الحالي كسيئ (الخطأ موجود هنا)
git bisect bad
# حدد commit معروف أنه جيد (الخطأ لم يكن موجوداً هنا)
git bisect good v2.1.0يعمل Git checkout لـ commit في منتصف المسافة بين الجيد والسيئ. اختبره. ثم:
# إذا كان الخطأ موجوداً عند هذا الـ commit:
git bisect bad
# إذا لم يكن الخطأ موجوداً عند هذا الـ commit:
git bisect goodيضيّق Git النطاق بمقدار النصف كل مرة. بعد 7-8 خطوات، يخبرك:
a1b2c3d4e5f6g7h is the first bad commit
commit a1b2c3d4e5f6g7h
Author: Some Developer <dev@example.com>
Date: Tue Feb 18 14:23:01 2026 +0300
Refactor session handling to use async middlewareالآن تعرف بالضبط أي commit أدخل الخطأ. عندما تنتهي:
git bisect resetهذا يعيدك إلى حيث بدأت.
الطريقة الآلية (هذه هي القوة الحقيقية)#
إذا كان لديك اختبار يمكنه اكتشاف الخطأ، يمكنك أتمتة العملية بالكامل:
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run npm test -- --grep "session validation"سيقوم Git تلقائياً بعمل checkout لعمليات الـ commit، وتشغيل الاختبار، وتحديدها كجيدة أو سيئة بناءً على كود الخروج. صفر يعني جيد، غير صفر يعني سيئ. اذهب وعُد، وسيخبرك بالـ commit بالضبط.
يمكنك استخدام أي سكريبت:
git bisect run ./test-regression.shحيث test-regression.sh هو:
#!/bin/bash
npm run build 2>/dev/null || exit 125 # 125 means "skip this commit"
npm test -- --grep "session" || exit 1 # 1 means "bad"
exit 0 # 0 means "good"كود الخروج 125 خاص — يخبر bisect بتخطي ذلك الـ commit (مفيد إذا لم يتم تجميع commit). هذه واحدة من تلك الميزات التي تبدو متخصصة حتى تحتاجها، وعندها توفر لك فترة ما بعد ظهر كاملة.
جلسة Bisect حقيقية#
إليك كيف تبدو عملياً:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good abc1234
Bisecting: 97 revisions left to test after this (roughly 7 steps)
[def5678...] Commit message here
$ npm test -- --grep "login flow"
# Tests fail
$ git bisect bad
Bisecting: 48 revisions left to test after this (roughly 6 steps)
[ghi9012...] Another commit message
$ npm test -- --grep "login flow"
# Tests pass
$ git bisect good
Bisecting: 24 revisions left to test after this (roughly 5 steps)
...
# After ~7 iterations:
abc1234def5678ghi is the first bad commitسبع خطوات لإيجاد إبرة في كومة قش من 200 عملية commit.
Git Worktrees: فروع متعددة، بدون أي Stashing#
هذه أكثر ميزة غير مُستغلة في Git. أنا مقتنع أن معظم المطورين لا يعرفون بوجودها.
المشكلة: أنت غارق في فرع ميزة. الملفات متغيرة في كل مكان. ثم يقول لك أحدهم "هل يمكنك إلقاء نظرة على خطأ الإنتاج هذا بسرعة؟" لديك ثلاثة خيارات:
- عمل stash لكل شيء، تبديل الفروع، الإصلاح، العودة، عمل pop للـ stash. أتمنى ألا يحدث خطأ.
- عمل commit لعملك غير المكتمل برسالة "wip". قبيح لكن عملي.
- استنساخ المستودع مرة أخرى في مجلد مختلف.
أو الخيار 4: worktrees.
ما هو الـ Worktree#
الـ worktree هو مجلد عمل ثانٍ (أو ثالث، أو رابع) مرتبط بنفس المستودع. كل worktree له فرعه الخاص، وملفات العمل الخاصة به، والفهرس الخاص به. لكنها تشترك في نفس بيانات .git، لذا فأنت لا تنسخ المستودع بالكامل.
إضافة Worktree#
# أنت على feature/user-auth، غارق في العمل
# تحتاج لإصلاح خطأ على main:
git worktree add ../hotfix-session mainهذا ينشئ مجلداً جديداً ../hotfix-session مع main محمّلاً. مجلدك الحالي يبقى كما هو تماماً. لا stash، لا commit، لا أي تعطيل.
cd ../hotfix-session
# أصلح الخطأ
git add .
git commit -m "Fix null pointer in session validation"
git push origin main
cd ../my-project
# تابع العمل على ميزتك كأن شيئاً لم يحدثإنشاء فرع جديد في Worktree#
git worktree add ../hotfix-nav -b hotfix/nav-crash mainهذا ينشئ الـ worktree وينشئ فرعاً جديداً hotfix/nav-crash بناءً على main.
إدارة الـ Worktrees#
# عرض جميع الـ worktrees
git worktree list
# /home/dev/my-project abc1234 [feature/user-auth]
# /home/dev/hotfix-session def5678 [main]
# إزالة worktree عند الانتهاء
git worktree remove ../hotfix-session
# إذا تم حذف المجلد مسبقاً:
git worktree pruneلماذا هذا أفضل من Stashing#
عمل Stash جيد للتبديل السريع في السياق. لكن الـ worktrees أفضل لأي شيء يستغرق أكثر من خمس دقائق:
- بيئة التطوير (IDE) تبقى مفتوحة على فرع الميزة. لا إعادة فهرسة، ولا فقدان موضع التمرير.
- يمكنك تشغيل الاختبارات في كلا المجلدين في نفس الوقت.
- لا خطر تعارضات stash أو نسيان ما عملت له stash.
- يمكنك تشغيل خادم تطوير طويل الأمد في worktree وبناء نظيف في آخر.
عادةً أبقي worktreein أو ثلاثة نشطة: فرع الميزة الرئيسي، وworktree لـ main للفحوصات السريعة، وأحياناً worktree للمراجعة حيث أحمّل طلب سحب شخص آخر.
القيد الوحيد#
لا يمكنك تحميل نفس الفرع في worktreein. هذا بالتصميم — يمنعك من إجراء تغييرات متعارضة على نفس الفرع في مكانين. إذا حاولت، سيرفض Git.
Reflog: زر التراجع لكل شيء#
قمت بإعادة تعيين صعبة (hard reset) وفقدت عمليات commit. حذفت فرعاً. عملت rebase وحدث خطأ فادح. تعتقد أن عملك ضاع.
لم يضع. Git لا يحذف أي شيء فعلياً تقريباً أبداً. الـ reflog هو شبكة أمانك.
ما هو الـ Reflog#
في كل مرة يتحرك HEAD — كل commit، checkout، rebase، reset، merge — يسجله Git في الـ reflog. إنه سجل لكل مكان كان HEAD فيه، بالترتيب.
git reflogالمخرجات:
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
f4e5d6c HEAD@{1}: commit: Add payment processing
b7a8c9d HEAD@{2}: commit: Update user dashboard
e0f1g2h HEAD@{3}: commit: Fix auth token refresh
i3j4k5l HEAD@{4}: checkout: moving from feature/payments to mainكل إدخال له فهرس (HEAD@{0}، HEAD@{1}، إلخ.) ووصف لما حدث.
الاستعادة بعد Hard Reset#
قمت بالخطأ بتشغيل git reset --hard HEAD~3 وفقدت ثلاث عمليات commit. إنها موجودة هناك في الـ reflog:
# انظر ما فقدته
git reflog
# الـ commit قبل إعادة التعيين هو HEAD@{1}
git reset --hard f4e5d6cالثلاث عمليات commit عادت. تم تجنب الأزمة.
استعادة فرع محذوف#
حذفت فرعاً به عمل غير مدمج:
git branch -D feature/experimental
# أوه لا، كان به أسبوعين من العملعمليات الـ commit لا تزال موجودة. ابحث عنها:
git reflog | grep "feature/experimental"
# أو ابحث في الـ reflog عن آخر commit على ذلك الفرع
# وجدته. أعد إنشاء الفرع عند ذلك الـ commit:
git branch feature/experimental a1b2c3dالفرع عاد، بكل عمليات الـ commit الخاصة به.
الاستعادة بعد Rebase سيئ#
عملت rebase وكل شيء سار بشكل خاطئ. تعارضات في كل مكان، عمليات commit خاطئة، فوضى:
# الـ reflog يُظهر أين كنت قبل إعادة التأسيس
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): ...
# ...
# f4e5d6c HEAD@{5}: rebase (start): checkout main
# b7a8c9d HEAD@{6}: commit: Your last good commit
# ارجع إلى ما قبل إعادة التأسيس
git reset --hard b7a8c9dأنت عدت بالضبط إلى حيث كنت قبل بدء إعادة التأسيس. كأنها لم تحدث أبداً.
شبكة الأمان لمدة 30 يوماً#
بشكل افتراضي، يحتفظ Git بإدخالات الـ reflog لمدة 30 يوماً (90 يوماً لعمليات الـ commit القابلة للوصول). بعد ذلك، يمكن تنظيفها بواسطة جامع القمامة. لذا لديك شهر لتدرك أنك ارتكبت خطأ. عملياً، هذا أكثر من كافٍ.
يمكنك فحص تاريخ الانتهاء:
git config gc.reflogExpire
# default: 90.days.ago (for reachable)
git config gc.reflogExpireUnreachable
# default: 30.days.ago (for unreachable)إذا كنت حذراً، زِد المدة:
git config --global gc.reflogExpireUnreachable "180.days.ago"قاعدة شخصية#
قبل أي عملية تدميرية — hard reset، دفع قسري، حذف فرع — أشغّل git log --oneline -10 أولاً. أسجل ذهنياً الـ HEAD الحالي. يستغرق ثانيتين وقد أنقذني أكثر من مرة من حالة ذعر لم أكن بحاجة لها.
عمل Stash بشكل صحيح: ليس مجرد git stash#
معظم الناس يستخدمون stash هكذا:
git stash
# افعل شيئاً
git stash popهذا يعمل، لكنه يعادل رمي كل شيء في صندوق مكتوب عليه "أشياء". عندما يكون لديك ثلاث عمليات stash، لا فكرة لديك أيها أي.
سمِّ عمليات الـ Stash#
git stash push -m "WIP: user auth form validation"الآن عندما تعرض قائمة الـ stash:
git stash list
# stash@{0}: On feature/auth: WIP: user auth form validation
# stash@{1}: On main: Quick fix attempt for nav bug
# stash@{2}: On feature/payments: Experiment with Stripe webhooksيمكنك أن ترى بالضبط ما يحتويه كل stash.
تضمين الملفات غير المتتبعة#
بشكل افتراضي، git stash يعمل stash فقط للملفات المتتبعة. الملفات الجديدة التي لم تضفها بعد تُترك:
# عمل stash لكل شيء، بما في ذلك الملفات الجديدة
git stash push --include-untracked -m "WIP: new auth components"
# أو حتى تضمين الملفات المتجاهلة (نادراً ما يُحتاج)
git stash push --all -m "Full workspace snapshot"أستخدم --include-untracked تقريباً في كل مرة. ترك الملفات الجديدة خلفك عند تبديل الفروع يسبب ارتباكاً.
Stash جزئي#
هذا الذي لا يعرفه معظم الناس. يمكنك عمل stash لملفات محددة:
# عمل stash لملفات محددة فقط
git stash push -m "Just the auth changes" src/auth/ src/middleware.tsأو استخدم وضع الترقيع (patch mode) لعمل stash لأجزاء محددة داخل الملفات:
git stash push --patch -m "Partial: only the validation logic"سيمر Git على كل تغيير بشكل تفاعلي ويسألك إن كنت تريد عمل stash له. y لنعم، n للا، s لتقسيم الجزء إلى قطع أصغر.
Apply مقابل Pop#
# Pop: تطبيق وإزالة من قائمة الـ stash
git stash pop stash@{2}
# Apply: تطبيق مع الإبقاء في قائمة الـ stash
git stash apply stash@{2}أستخدم apply عندما لا أكون متأكداً إن كان الـ stash سيُطبق بنظافة. إذا كان هناك تعارض، يتم الحفاظ على الـ stash. مع pop، إذا كان هناك تعارض، يبقى الـ stash في القائمة على أي حال (كثير من الناس لا يعرفون هذا)، لكنني أفضل النية الصريحة لـ apply.
عرض محتويات الـ Stash#
# رؤية الملفات التي تغيرت في stash
git stash show stash@{0}
# رؤية الفرق الكامل
git stash show -p stash@{0}إنشاء فرع من Stash#
إذا كبر الـ stash وأصبح شيئاً أكبر:
git stash branch feature/auth-validation stash@{0}هذا ينشئ فرعاً جديداً من الـ commit حيث عملت stash أصلاً، يطبق الـ stash، ويحذفه. نظيف.
استراتيجيات التفرع: أيها يعمل فعلاً#
هناك ثلاث استراتيجيات تفرع رئيسية. لكل واحدة سياق تتألق فيه وسياقات تسبب فيها ألماً.
Gitflow#
الكلاسيكي. main، develop، feature/*، release/*، hotfix/*. أنشأه Vincent Driessen في 2010.
# فرع الميزة
git checkout -b feature/user-auth develop
# ... العمل ...
git checkout develop
git merge --no-ff feature/user-auth
# فرع الإصدار
git checkout -b release/2.1.0 develop
# ... الإصلاحات النهائية ...
git checkout main
git merge --no-ff release/2.1.0
git tag -a v2.1.0 -m "Release 2.1.0"
git checkout develop
git merge --no-ff release/2.1.0
# إصلاح عاجل
git checkout -b hotfix/session-fix main
# ... الإصلاح ...
git checkout main
git merge --no-ff hotfix/session-fix
git checkout develop
git merge --no-ff hotfix/session-fixمتى يعمل: تطبيقات الهاتف، برامج سطح المكتب، أي شيء بإصدارات مسماة ومعددة الإصدارات مدعومة في نفس الوقت. إذا كنت تشحن v2.1 وv3.0 وتحتاج لترقيع كليهما، يتعامل Gitflow مع ذلك.
متى لا يعمل: تطبيقات الويب ذات النشر المستمر. إذا كنت تنشر إلى الإنتاج 5 مرات يومياً، فإن طقوس فروع الإصدار وفروع التطوير عبء محض. معظم فرق الويب التي تتبنى Gitflow ينتهي بها الأمر بفرع develop معطل دائماً وفروع إصدار لا يفهمها أحد.
GitHub Flow#
بسيط. لديك main. تنشئ فروع ميزات. تفتح طلبات سحب. تدمج في main. تنشر main.
git checkout -b feature/user-auth main
# ... العمل ...
git push origin feature/user-auth
# افتح PR، احصل على مراجعة، ادمج
# main دائماً قابل للنشرمتى يعمل: فرق صغيرة إلى متوسطة تشحن تطبيقات ويب. نشر مستمر. إذا كان main يُنشر دائماً، هذا كل ما تحتاجه. هذا ما يستخدمه هذا الموقع.
متى لا يعمل: عندما تحتاج لصيانة إصدارات متعددة، أو عندما يكون لديك دورة اختبار جودة طويلة قبل النشر. يفترض GitHub Flow أن main يذهب للإنتاج بسرعة.
التطوير القائم على الجذع (Trunk-Based Development)#
الجميع يعمل commit إلى main ("الجذع") مباشرة أو عبر فروع قصيرة العمر جداً (أقل من يوم). لا فروع ميزات طويلة الأمد.
# فرع قصير العمر (يُدمج في نفس اليوم)
git checkout -b fix/auth-token main
# ... تغيير صغير ومركّز ...
git push origin fix/auth-token
# PR تتم مراجعته ودمجه في ساعات، وليس أياممتى يعمل: الفرق عالية الأداء مع CI/CD جيد، ومجموعات اختبار شاملة، وأعلام الميزات (feature flags). Google وMeta ومعظم شركات التكنولوجيا الكبيرة تستخدم التطوير القائم على الجذع. يفرض تغييرات صغيرة ومتدرجة ويقضي على جحيم الدمج.
متى لا يعمل: الفرق التي ليس لديها تغطية اختبار جيدة أو CI. إذا كان الدمج إلى الجذع يعني نشر كود غير مُختبر، ستكسر الإنتاج باستمرار. تحتاج أيضاً أعلام ميزات لأي شيء يستغرق أكثر من يوم لبنائه:
# علم ميزة في الكود
if (featureFlags.isEnabled('new-checkout-flow')) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}توصيتي#
لمعظم فرق تطوير الويب: ابدأ بـ GitHub Flow. بسيط، يعمل، ولا يتطلب أدوات أبعد مما يوفره GitHub/GitLab بالفعل.
إذا نما فريقك إلى أكثر من 15-20 مهندساً وتنشرون عدة مرات يومياً، انظر في التطوير القائم على الجذع مع أعلام الميزات. الاستثمار في بنية أعلام الميزات يدفع ثمنه بتقليل تعارضات الدمج وتسريع التكرار.
إذا كنت تشحن برمجيات ذات إصدارات (تطبيقات هاتف، أدوات سطر أوامر، مكتبات): Gitflow أو نسخة مبسطة منه. أنت فعلاً تحتاج فروع الإصدار تلك.
لا تختر استراتيجية لأن مقالة قالت إنها الأفضل. اختر التي تتطابق مع كيفية شحنك فعلاً.
Git Hooks: أتمتة الأشياء التي تنسى فعلها باستمرار#
Git hooks هي سكريبتات تعمل تلقائياً عند نقاط محددة في سير عمل Git. هي محلية لجهازك (لا تُدفع إلى الخادم البعيد)، مما يعني أنك تحتاج طريقة لمشاركتها مع فريقك.
الـ Hooks التي تهم فعلاً#
pre-commit — يعمل قبل كل commit. استخدمه للتدقيق اللغوي والتنسيق:
#!/bin/bash
# .git/hooks/pre-commit
# تشغيل ESLint على الملفات المُجهزة فقط
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
if [ -n "$STAGED_FILES" ]; then
echo "Running ESLint on staged files..."
npx eslint $STAGED_FILES --quiet
if [ $? -ne 0 ]; then
echo "ESLint failed. Fix errors before committing."
exit 1
fi
fi
# تشغيل Prettier على الملفات المُجهزة
if [ -n "$STAGED_FILES" ]; then
echo "Running Prettier..."
npx prettier --check $STAGED_FILES
if [ $? -ne 0 ]; then
echo "Prettier check failed. Run 'npx prettier --write' first."
exit 1
fi
ficommit-msg — يتحقق من صيغة رسالة الـ commit. مثالي لفرض الـ conventional commits:
#!/bin/bash
# .git/hooks/commit-msg
COMMIT_MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}"
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "Invalid commit message format."
echo "Expected: type(scope): description"
echo "Example: feat(auth): add session refresh endpoint"
echo ""
echo "Valid types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
exit 1
fipre-push — يعمل قبل الدفع. استخدمه للاختبارات:
#!/bin/bash
# .git/hooks/pre-push
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fiHusky مقابل الـ Hooks الأصلية#
الـ hooks الأصلية تعيش في .git/hooks/. المشكلة: مجلد .git لا يتتبعه Git، لذا لا يمكنك مشاركة الـ hooks عبر المستودع. على الجميع إعدادها يدوياً.
Husky يحل هذا. يخزن إعدادات الـ hooks في المستودع ويُعد .git/hooks تلقائياً عند npm install:
npx husky initهذا ينشئ مجلد .husky/. أضف الـ hooks كملفات:
# .husky/pre-commit
npx lint-stagedمع lint-staged، تحصل على فحوصات pre-commit سريعة وموجهة:
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.css": ["prettier --write"],
"*.json": ["prettier --write"]
}
}هذا يشغل ESLint وPrettier فقط على الملفات التي تعمل لها commit فعلاً. ليس قاعدة الكود بالكامل. سريع.
متى تتخطى الـ Hooks#
أحياناً تحتاج لعمل commit بدون تشغيل الـ hooks. إصلاحات عاجلة، عمليات commit عمل قيد التنفيذ على فرعك الخاص:
git commit --no-verify -m "WIP: debugging production issue"استخدم هذا باعتدال. إذا وجدت نفسك تتخطى الـ hooks بانتظام، فإن hooks الخاصة بك ربما بطيئة جداً أو صارمة جداً.
اختصارات (Aliases) توفر الوقت#
ملف .gitconfig الخاص بي تطور على مر السنين. هذه الاختصارات هي التي نجت — تلك التي أستخدمها يومياً فعلاً، وليست التي أضفتها لأنها بدت ذكية.
سجل جميل#
git log الافتراضي مطوّل. هذا يعطيك عرضاً نظيفاً وملوناً وقائماً على الرسم البياني:
git config --global alias.lg "log --oneline --graph --all --decorate"الاستخدام:
git lg
# * a1b2c3d (HEAD -> main) Fix session validation
# | * d4e5f6g (feature/payments) Add Stripe integration
# | * h7i8j9k Update payment models
# |/
# * l0m1n2o Merge PR #42
# * p3q4r5s Add user dashboardأشغّل هذا 20 مرة يومياً. إنها أسرع طريقة لفهم حالة مستودعك.
التراجع عن آخر Commit#
الإبقاء على التغييرات، فقط التراجع عن الـ commit:
git config --global alias.undo "reset HEAD~1 --mixed"الاستخدام:
git undo
# الـ commit اختفى، لكن جميع التغييرات لا تزال في مجلد العملأستخدم هذا عندما أعمل commit مبكراً جداً، أو أنسى ملفاً، أو أريد إعادة هيكلة التغييرات.
إلغاء تجهيز كل شيء#
git config --global alias.unstage "reset HEAD --"الاستخدام:
git unstage src/auth/session.ts
# الملف أُلغي تجهيزه لكن التغييرات محفوظةعرض جميع الاختصارات#
لأنك ستنسى ما أعددته:
git config --global alias.aliases "config --get-regexp ^alias\\."المزيد من الاختصارات التي أستخدمها#
# عرض ما أنت على وشك عمل commit له
git config --global alias.staged "diff --staged"
# حالة مختصرة
git config --global alias.st "status -sb"
# تعديل بدون تغيير الرسالة
git config --global alias.amend "commit --amend --no-edit"
# عرض آخر commit
git config --global alias.last "log -1 HEAD --stat"
# سحب مع rebase بدلاً من merge
git config --global alias.up "pull --rebase --autostash"
# حذف الفروع التي تم دمجها في main
git config --global alias.cleanup "!git branch --merged main | grep -v '^[ *]*main$' | xargs git branch -d"اختصار up جيد بشكل خاص. pull --rebase يبقي تاريخك خطياً بدلاً من إنشاء merge commits لكل pull. --autostash يعمل stash تلقائياً ويستعيد تغييراتك إذا كان لديك ملفات معدّلة. هذا ما كان يجب أن يكون عليه pull بشكل افتراضي.
اختصار cleanup يحذف الفروع المحلية التي تم دمجها في main. مع الوقت، تتراكم عشرات الفروع القديمة. شغّل هذا أسبوعياً.
الأوامر التي أستخدمها يومياً#
هذه ليست اختصارات أو ميزات متقدمة. إنها فقط أوامر أشغلها باستمرار ولا يبدو أن كثيراً من المطورين يعرفونها.
أمر السجل الأمثل#
git log --oneline --graph --allهذا يعرض كل فرع، كل دمج، الطوبولوجيا الكاملة لمستودعك في عرض مضغوط. إنه أول شيء أشغله عندما أسحب التغييرات. يجيب على "ما الذي يحدث في هذا المستودع الآن؟"
فرق التغييرات المُجهزة#
git diff --stagedهذا يعرض ما هو على وشك أن يُعمل له commit. ليس ما تغير في مجلد العمل — ما هو مُجهز فعلاً. أشغّل هذا دائماً قبل عمل commit. دائماً. يلتقط التضمينات العرضية، عبارات التصحيح، console.logs التي لا يجب أن تكون موجودة.
# رؤية التغييرات غير المُجهزة
git diff
# رؤية التغييرات المُجهزة
git diff --staged
# رؤية كليهما
git diff HEADعرض Commit محدد#
git show a1b2c3dيعرض الفرق الكامل لـ commit واحد. مفيد عند مراجعة التاريخ، وفهم ما غيّره commit فعلاً.
# عرض الملفات التي تغيرت فقط
git show --stat a1b2c3d
# عرض ملف محدد عند commit محدد
git show a1b2c3d:src/auth/session.tsذلك الأخير مفيد بشكل لا يصدق. يمكنك عرض أي ملف في أي نقطة من التاريخ دون عمل checkout لذلك الـ commit.
Blame بنطاقات الأسطر#
git blame -L 42,60 src/auth/session.tsيعرض من عدّل آخر مرة الأسطر 42-60. أكثر فائدة من blame للملف بالكامل، والذي عادة يكون مربكاً.
# عرض رسالة الـ commit أيضاً، وليس فقط الهاش
git blame -L 42,60 --show-name src/auth/session.ts
# تجاهل تغييرات المسافات البيضاء (مفيد جداً)
git blame -w -L 42,60 src/auth/session.ts
# عرض الـ commit قبل المُلام (حفر أعمق)
git log --follow -p -- src/auth/session.tsعلامة -w مهمة. بدونها، سينسب blame الأسطر لمن أعاد تنسيق الملف آخر مرة، وهو نادراً الشخص الذي تبحث عنه.
البحث عن نص عبر كل التاريخ#
# ابحث متى تم إضافة أو إزالة دالة
git log -S "validateSession" --oneline
# ابحث متى ظهر نمط regex
git log -G "session.*timeout" --oneline-S ("الفأس" pickaxe) تجد عمليات الـ commit حيث تغير عدد مرات ظهور نص. -G تجد عمليات الـ commit حيث يطابق الفرق نمط regex. كلاهما قوي للتنقيب — معرفة متى تم إدخال شيء أو إزالته.
عرض ما تغير بين نقطتين#
# ما تغير بين فرعين
git diff main..feature/auth
# ما تغير على هذا الفرع منذ انفصاله عن main
git diff main...feature/auth
# عرض أسماء الملفات المتغيرة فقط
git diff main...feature/auth --name-only
# عرض إحصائي (ملفات + إدراجات/حذف)
git diff main...feature/auth --statنقطتان مقابل ثلاث نقاط مهم. نقطتان تُظهر الفرق بين رأس كلا الفرعين. ثلاث نقاط تُظهر ما تغير على الجانب الأيمن منذ انفصاله عن الجانب الأيسر. ثلاث نقاط عادة ما تريده عند مراجعة فرع ميزة.
تنظيف الملفات غير المتتبعة#
# رؤية ما سيُحذف (تشغيل تجريبي)
git clean -n
# حذف الملفات غير المتتبعة
git clean -f
# حذف الملفات والمجلدات غير المتتبعة
git clean -fd
# حذف الملفات غير المتتبعة والمتجاهلة (الخيار النووي)
git clean -fdxدائماً شغّل مع -n أولاً. git clean -fdx سيحذف node_modules، .env، مخرجات البناء — كل شيء لا يتتبعه Git. مفيد لبداية نظيفة حقاً، لكنه تدميري.
استعادة ملف واحد من فرع آخر#
# الحصول على نسخة فرع main من ملف بدون تبديل الفروع
git restore --source main -- src/config/database.tsأو من commit محدد:
git restore --source a1b2c3d -- src/config/database.tsهذا أنظف من صيغة git checkout main -- path/to/file القديمة، ولا يؤثر على HEAD.
تجميع كل شيء معاً: سير عمل حقيقي#
إليك كيف يبدو يوم نموذجي مع هذه الأدوات:
# الصباح: تحقق مما يحدث
git lg
git fetch --all
# ابدأ ميزة
git checkout -b feature/session-refresh main
# اعمل، وعمل commit تدريجياً
git add -p # جهّز أجزاء محددة، وليس ملفات بالكامل
git commit -m "Add token refresh endpoint"
git commit -m "Add refresh token rotation"
git commit -m "Fix: handle expired refresh tokens"
git commit -m "Add integration tests"
# مقاطعة: تحتاج لإصلاح خطأ إنتاج
git worktree add ../hotfix main
cd ../hotfix
# ... إصلاح، commit، دفع، PR مُدمج ...
cd ../my-project
git worktree remove ../hotfix
# العودة لعمل الميزة
git commit -m "Fix edge case in token validation"
# جاهز لطلب السحب: تنظيف التاريخ
git rebase -i main
# دمج عمليات الإصلاح، إعادة الصياغة للوضوح
# دفع وفتح PR
git push -u origin feature/session-refresh
# شيء تعطل في staging؟ اكتشف أي commit:
git bisect start
git bisect bad HEAD
git bisect good main
git bisect run npm test
# عفواً، عملت hard-reset للشيء الخاطئ
git reflog
git reset --hard HEAD@{2}كل من هذه الأوامر يستغرق ثوانٍ. معاً، توفر ساعات. ليست ساعات افتراضية — ساعات حقيقية، كل أسبوع، كنت سأقضيها في فك تشابك فوضى Git، أو البحث يدوياً عن الأخطاء، أو فقدان العمل بسبب تبديل السياقات.
Git أداة تكافئ العمق. الأساسيات تمرّرك خلال اليوم. لكن الأوامر في هذا المقال هي ما يفصل "أستخدم Git" عن "Git فعلاً يجعلني أسرع." تعلمها تدريجياً. اختر تقنية جديدة هذا الأسبوع واستخدمها حتى تصبح ذاكرة عضلية. ثم اختر أخرى.
ذاتك المستقبلية، تحدّق في خطأ إنتاجي في الساعة 11 مساءً، ستشكرك على معرفة git bisect.