{"checkpoints":{"s-git-1":[{"q":"<p>تشغّل <code dir=\"ltr\">git add index.html</code>، ثم تعدّل <code dir=\"ltr\">index.html</code> مرة أخرى، ثم تشغّل <code dir=\"ltr\">git commit -m &quot;Update intro&quot;</code>. أيّ نسخة من الملف تدخل في الـ commit؟</p>","options":[{"text":"النسخة التي حفظتها آخر مرة.","why":"<code dir=\"ltr\">git commit</code> يسجّل الـ index، لا ملفاتك على القرص. والتعديل الثاني لم يُجهَّز قط."},{"text":"النسختان، في commitين منفصلين.","why":"الأمر <code dir=\"ltr\">git commit</code> الواحد ينشئ دائمًا commit واحدًا. ولا يقسم Git التعديلات بحسب وقت حفظها."},{"text":"النسخة كما كانت لحظة تشغيل <code dir=\"ltr\">git add</code>.","why":"صحيح. نسخ <code dir=\"ltr\">git add</code> تلك النسخة إلى الـ index، والـ commit يسجّل الـ index. أما التعديل اللاحق فما زال غير مجهّز."},{"text":"لا هذه ولا تلك؛ يرفض Git تسجيل ملف تغيّر بعد تجهيزه.","why":"يسمح Git بذلك ويعرض الملف على أنه مجهّز ومعدّل في آن واحد. وهذا طبيعي أثناء بناء الـ commit."}],"answer":2},{"q":"<p>يعرض <code dir=\"ltr\">git status -s</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>A  contact.html\n?? notes.txt</code></pre></div>\n<p>ماذا يسجّل <code dir=\"ltr\">git commit -m &quot;Add contact page&quot;</code>؟</p>\n","options":[{"text":"<code dir=\"ltr\">contact.html</code> فقط.","why":"صحيح. الحرف <code dir=\"ltr\">A</code> في العمود الأيسر يعني أنه مجهّز كملف جديد؛ و <code dir=\"ltr\">??</code> تعني أن <code dir=\"ltr\">notes.txt</code> غير متتبَّع ويبقى خارج الـ commit."},{"text":"الملفين كليهما، لأنهما جديدان.","why":"كون الملف جديدًا لا يكفي. لا يدخل الـ commit إلا المحتوى المجهّز، و <code dir=\"ltr\">notes.txt</code> لم يُضف قط."},{"text":"لا شيء، لأن الملفات غير المتتبَّعة تمنع الـ commit.","why":"الملفات غير المتتبَّعة لا تمنع الـ commits أبدًا. يتجاهلها Git ما لم تجهّزها."},{"text":"<code dir=\"ltr\">notes.txt</code> فقط، لأن <code dir=\"ltr\">??</code> تعني أنه جاهز.","why":"<code dir=\"ltr\">??</code> تعني أن Git لم يتتبّع الملف قط. ولا شيء منه مجهّز."}],"answer":0},{"q":"<p>أيّ عبارة عن معرّفات الـ commits صحيحة؟</p>","options":[{"text":"المعرّفات مرقّمة بالترتيب، فالمعرّف الأعلى يعني دائمًا commit أحدث.","why":"المعرّفات hashes، لا عدّادات. وترتيبها لا يقول شيئًا عن الزمن."},{"text":"تستطيع تعديل رسالة الـ commit والاحتفاظ بمعرّفه.","why":"الرسالة جزء مما يُحسب منه الـ hash. وتغييرها ينشئ commit مختلفًا بمعرّف جديد."},{"text":"شخصان يسجّلان ملفات متطابقة يحصلان دائمًا على المعرّف نفسه.","why":"المؤلف والوقت والأب تدخل في الـ hash أيضًا، فتختلف الـ commits."},{"text":"معرّف الـ commit يعتمد على محتواه ومؤلفه وتاريخه ورسالته وأبيه، فالتغيير نفسه إن سُجّل مرتين حصل على معرّفين.","why":"صحيح. ولهذا أيضًا ينتج الـ amend والـ rebase والـ revert معرّفات جديدة دائمًا."}],"answer":3},{"q":"<p>هذا ناتج <code dir=\"ltr\">git log --oneline --graph</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>*   9d1f0a2 (HEAD -&gt; main) Merge branch 'contact'\n|\\\n| * 6b2c8e1 (contact) Add contact form\n* | 3e7a4d9 Fix header spacing\n|/\n* 1f0c5b8 Add home page</code></pre></div>\n<p>أيّ commit هو <code dir=\"ltr\">HEAD~1</code>؟</p>\n","options":[{"text":"<code dir=\"ltr\">6b2c8e1</code>، لأنه أحدث commit قبل الدمج.","why":"<code dir=\"ltr\">~</code> تتبع الأب الأول، وهو في الـ merge الفرع الذي كنت عليه (<code dir=\"ltr\">main</code>)، لا الفرع المدموج."},{"text":"<code dir=\"ltr\">3e7a4d9</code>، الأب الأول للـ merge.","why":"صحيح. كنت على <code dir=\"ltr\">main</code> حين دمجت، لذا فطرف <code dir=\"ltr\">main</code> السابق هو الأب الأول. أما <code dir=\"ltr\">HEAD^2</code> فسيكون <code dir=\"ltr\">6b2c8e1</code>."},{"text":"<code dir=\"ltr\">1f0c5b8</code>، لأنه حيث يلتقي الخطّان.","why":"هذا هو الـ merge base، على بعد خطوتين. و <code dir=\"ltr\">HEAD~1</code> خطوة واحدة."},{"text":"لا شيء؛ <code dir=\"ltr\">HEAD~1</code> خطأ على merge commit.","why":"إنه يعمل جيدًا. يتبع Git دائمًا الأب الأول مع <code dir=\"ltr\">~</code>."}],"answer":1},{"q":"<p>تريد مراجعة ما سيحتويه الـ commit التالي بالضبط قبل تشغيل <code dir=\"ltr\">git commit</code>. أيّ أمر يعرض ذلك؟</p>","options":[{"text":"<code dir=\"ltr\">git diff --staged</code>","why":"صحيح. يقارن الـ index بآخر commit، وهذا بالضبط التغيير الذي سيسجّله الـ commit التالي."},{"text":"<code dir=\"ltr\">git diff</code>","why":"هذا يعرض التغييرات التي <em>لم</em> تجهّزها بعد، ولن تكون في الـ commit."},{"text":"<code dir=\"ltr\">git log -p</code>","why":"هذا يعرض الـ commits السابقة وفروقاتها، لا ما هو على وشك أن يُسجَّل."},{"text":"<code dir=\"ltr\">git show HEAD</code>","why":"هذا يعرض آخر commit أنشأته بالفعل."}],"answer":0}],"s-git-2":[{"q":"<p>على <code dir=\"ltr\">main</code>، تشغّل <code dir=\"ltr\">git switch -c hero-image</code>، وتعدّل <code dir=\"ltr\">index.html</code> دون commit، ثم تشغّل <code dir=\"ltr\">git switch main</code>. الفرعان يشيران إلى الـ commit نفسه. ماذا يحدث لتعديلك؟</p>","options":[{"text":"يضيع، لأن الانتقال يعيد ضبط الـ working tree.","why":"الانتقال يحتفظ بالتغييرات غير المسجّلة كلما استطاع. ويرفض بدل أن يضيّعها."},{"text":"ينتقل معك إلى <code dir=\"ltr\">main</code> كتغيير غير مسجّل.","why":"صحيح. في الفرعين <code dir=\"ltr\">index.html</code> نفسه، فلا شيء يحتاج إلى الكتابة فوقه وينتقل التعديل معك."},{"text":"يرفض Git دائمًا الانتقال ما دامت لديك تغييرات غير مسجّلة.","why":"لا يرفض إلا حين يكون الانتقال سيكتب فوق تغييراتك. وهنا لن يفعل."},{"text":"يُسجَّل تلقائيًا في commit على <code dir=\"ltr\">hero-image</code> قبل الانتقال.","why":"لا ينشئ Git commits نيابةً عنك أبدًا. العمل غير المسجّل يبقى كذلك حتى تسجّله."}],"answer":1},{"q":"<p>يفشل <code dir=\"ltr\">git branch -d old-layout</code> بالرسالة <code dir=\"ltr\">error: the branch 'old-layout' is not fully merged</code>. ماذا يعني ذلك؟</p>","options":[{"text":"أنت حاليًا على <code dir=\"ltr\">old-layout</code>، فلا يمكن حذفه.","why":"هذا خطأ مختلف (لا يمكنك حذف الفرع الذي أنت عليه). أما هذا فيتعلّق بـ commits غير مدموجة."},{"text":"الفرع غير موجود محليًا.","why":"الفرع المفقود يعطي خطأ «غير موجود»، لا «not fully merged»."},{"text":"لا يمكن الوصول إلى GitHub، فلا يستطيع Git فحص الفرع.","why":"<code dir=\"ltr\">git branch -d</code> يعمل محليًا بالكامل ولا يتواصل مع أي remote."},{"text":"بعض الـ commits الخاصة به لم تُدمج في فرعك الحالي؛ ادمجها، أو استخدم <code dir=\"ltr\">-D</code> إن كنت متأكّدًا أنها غير مرغوبة.","why":"صحيح. <code dir=\"ltr\">-d</code> يحمي العمل غير المدموج. و <code dir=\"ltr\">-D</code> يحذف رغم ذلك، ويظل الـ reflog قادرًا على إنقاذه لفترة."}],"answer":3},{"q":"<p>أنت عند الـ commit الثالث من <code dir=\"ltr\">git rebase main</code> على فرعك حين يجعلك تعارض تدرك أنك تفضّل الدمج. كيف تعود تمامًا إلى حيث كنت قبل الـ rebase؟</p>","options":[{"text":"<code dir=\"ltr\">git merge --abort</code>","why":"لا يوجد merge قيد التنفيذ. لكل عملية أمر الإلغاء الخاص بها."},{"text":"<code dir=\"ltr\">git reset --hard main</code>","why":"هذا يجعل فرعك يشير إلى <code dir=\"ltr\">main</code>، فيُسقط منه commits الفرع. يمكن استعادتها عبر الـ reflog، لكنه عكس ما تريده."},{"text":"<code dir=\"ltr\">git rebase --abort</code>","why":"صحيح. يوقف الـ rebase ويعيد الفرع إلى الـ commits الأصلية."},{"text":"<code dir=\"ltr\">git rebase --skip</code>","why":"هذا يُسقط الـ commit المتعارض ويواصل الـ rebase، فتخسر عملًا وتبقى في منتصف الـ rebase."}],"answer":2},{"q":"<p>أنت وزميلك ترفعان كلاكما إلى الفرع المشترك <code dir=\"ltr\">redesign</code> على GitHub. وقد تأخّر عن <code dir=\"ltr\">main</code>. كيف تحدّثه؟</p>","options":[{"text":"ادمج <code dir=\"ltr\">main</code> في <code dir=\"ltr\">redesign</code> وارفع.","why":"صحيح. الـ merge يضيف commit دون إعادة كتابة أي شيء يملكه زميلك بالفعل."},{"text":"نفّذ rebase لـ <code dir=\"ltr\">redesign</code> على <code dir=\"ltr\">main</code> ثم <code dir=\"ltr\">git push --force</code>.","why":"هذا يعيد كتابة commits بنى عليها زميلك، وهذا بالضبط ما تمنعه القاعدة الذهبية."},{"text":"احذف <code dir=\"ltr\">redesign</code> وأعد إنشاءه من <code dir=\"ltr\">main</code>.","why":"هذا يرمي عمل الفرع بدل أن يحدّثه."},{"text":"شغّل <code dir=\"ltr\">git reset --hard main</code> على <code dir=\"ltr\">redesign</code>.","why":"هذا يُسقط commits الفرع من الفرع. ولا يُدمج شيء."}],"answer":0},{"q":"<p>أثناء دمج، يتعارض <code dir=\"ltr\">styles.css</code>، وأنت تعرف أن نسخة الفرع القادم هي الصحيحة للملف كله. ما أسرع حلّ صحيح؟</p>","options":[{"text":"<code dir=\"ltr\">git restore --ours styles.css</code>، ثم <code dir=\"ltr\">git add styles.css</code>","why":"<code dir=\"ltr\">--ours</code> يحتفظ بنسخة فرعك الحالي، وهذا عكس ما تريده."},{"text":"<code dir=\"ltr\">git merge --abort</code>","why":"هذا يلغي الدمج كله بدل أن يحلّه."},{"text":"احذف <code dir=\"ltr\">styles.css</code> وشغّل <code dir=\"ltr\">git commit</code>.","why":"هذا يسجّل حذف الملف، فيتعطّل الموقع."},{"text":"<code dir=\"ltr\">git restore --theirs styles.css</code>، ثم <code dir=\"ltr\">git add styles.css</code> و <code dir=\"ltr\">git commit</code>","why":"صحيح. <code dir=\"ltr\">--theirs</code> يأخذ نسخة الفرع القادم من الملف المتعارض؛ والتجهيز يعلّمه بأنه حُلّ."}],"answer":3}],"s-git-3":[{"q":"<p>يقول <code dir=\"ltr\">git status</code> إن <code dir=\"ltr\">Your branch is up to date with 'origin/main'</code>، لكن GitHub يعرض commit على <code dir=\"ltr\">main</code> ليس لديك. لماذا؟</p>","options":[{"text":"صفحة GitHub مخزّنة مؤقتًا والـ commit ليس موجودًا فعلًا.","why":"صفحة GitHub دقيقة. معلومات الـ repository الخاص بك هي القديمة."},{"text":"<code dir=\"ltr\">git status</code> يتجاهل الـ commits الأقدم من يوم.","why":"لا يوجد حدّ كهذا. الحالة تقارن بما يسجّله <code dir=\"ltr\">origin/main</code> حاليًا أيًّا كان."},{"text":"رُفع الـ commit إلى remote مختلف.","why":"إنه على <code dir=\"ltr\">main</code> في <code dir=\"ltr\">origin</code>، كما تراه على GitHub. المشكلة في آخر مرة سألت فيها."},{"text":"الحالة تقارن بآخر <code dir=\"ltr\">origin/main</code> معروف لديك، ولا يتحدّث إلا حين تنفّذ fetch؛ شغّل <code dir=\"ltr\">git fetch</code> وتحقّق مجددًا.","why":"صحيح. لا يتواصل Git مع GitHub عند <code dir=\"ltr\">git status</code>. وبعد الـ fetch سيبلّغك أنك متأخّر."}],"answer":3},{"q":"<p>ترفع فرعًا جديدًا بـ <code dir=\"ltr\">git push</code> فتحصل على <code dir=\"ltr\">fatal: The current branch contact-form has no upstream branch.</code> ما الذي يجري؟</p>","options":[{"text":"لم يُضف الـ remote المسمّى <code dir=\"ltr\">origin</code> بعد.","why":"لو لم يوجد remote أصلًا، لاشتكى Git من الـ repository المفقود، لا من الـ upstream المفقود."},{"text":"لم يُرفع الفرع قط، فليس له upstream؛ شغّل <code dir=\"ltr\">git push -u origin contact-form</code>.","why":"صحيح. <code dir=\"ltr\">-u</code> يرفع ويسجّل الـ upstream، فيعمل <code dir=\"ltr\">git push</code> و <code dir=\"ltr\">git pull</code> المجرّدان لاحقًا."},{"text":"انتهت صلاحية بيانات الاعتماد.","why":"أخطاء المصادقة تذكر الوصول أو المصادقة. أما هذا فيتعلّق بالتتبّع."},{"text":"عليك تنفيذ <code dir=\"ltr\">git pull</code> قبل رفع فرع جديد.","why":"لا شيء يُسحب؛ فالفرع غير موجود على GitHub بعد."}],"answer":1},{"q":"<p>يريد فريقك أن يحصل <code dir=\"ltr\">main</code> على commit واحد بالضبط لكل pull request، يحمل عنوان الـ pull request. أيّ طريقة دمج تناسب؟</p>","options":[{"text":"Squash and merge","why":"صحيح. كل commits الفرع تصبح commit واحدًا جديدًا على <code dir=\"ltr\">main</code>."},{"text":"Create a merge commit","why":"هذا يُبقي كل commit في الفرع مع merge commit، فيحصل <code dir=\"ltr\">main</code> على عدة commits لكل pull request."},{"text":"Rebase and merge","why":"هذا يعيد تشغيل كل commit منفردًا على <code dir=\"ltr\">main</code>، دون commit ملخّص واحد."},{"text":"أغلق الـ pull request وارفع التغييرات مباشرة","why":"هذا يتخطّى المراجعة، ومع قواعد الحماية يُحجب على أي حال."}],"answer":0},{"q":"<p>استنسخت الـ fork الخاص بك من مشروع ما. يعرض <code dir=\"ltr\">git remote -v</code> الاسم <code dir=\"ltr\">origin</code> فقط، ويشير إلى الـ fork. كيف تحصل على أحدث commits من المشروع الأصلي؟</p>","options":[{"text":"<code dir=\"ltr\">git pull origin main</code>","why":"الـ fork الخاص بك لا يحتوي أحدث commits الأصل حتى تزامنه، فهذا يسحب سجلًّا متأخّرًا."},{"text":"استنسخ الأصل بـ <code dir=\"ltr\">git clone</code> في المجلد نفسه.","why":"الـ clone ينشئ repository جديدًا؛ ولا يستطيع الدمج في repository موجود."},{"text":"<code dir=\"ltr\">git remote add upstream &lt;original-url&gt;</code>، ثم <code dir=\"ltr\">git fetch upstream</code> ودمج <code dir=\"ltr\">upstream/main</code>.","why":"صحيح. remote ثانٍ للأصل يتيح لك الجلب منه مباشرة مع مواصلة الرفع إلى الـ fork الخاص بك."},{"text":"احذف الـ fork وأنشئ fork جديدًا.","why":"هذا ينجح مرة واحدة، على حساب فروعك وأي pull requests مفتوحة. أما الـ remote فيفعلها في كل مرة."}],"answer":2},{"q":"<p>يقول الـ pull request الخاص بك <code dir=\"ltr\">Fixes #31</code> ودُمج في فرع <code dir=\"ltr\">develop</code>، لكن الـ issue رقم 31 ما زالت مفتوحة. الفرع الافتراضي للـ repository هو <code dir=\"ltr\">main</code>. لماذا؟</p>","options":[{"text":"<code dir=\"ltr\">Fixes</code> ليست كلمة مفتاحية معترفًا بها؛ وحدها <code dir=\"ltr\">Closes</code> تعمل.","why":"<code dir=\"ltr\">fixes</code> و <code dir=\"ltr\">closes</code> و <code dir=\"ltr\">resolves</code> كلها تعمل، مع صيغها المختلفة."},{"text":"كلمات الإغلاق المفتاحية لا تسري إلا حين يُدمج الـ pull request في الفرع الافتراضي.","why":"صحيح. ستُغلق الـ issue حين يصل التغيير إلى <code dir=\"ltr\">main</code>، أو يمكنك إغلاقها يدويًا."},{"text":"يجب أن تكون الكلمة المفتاحية في عنوان الـ pull request، لا في وصفه.","why":"الوصف هو المكان المعتاد لكلمات الإغلاق المفتاحية."},{"text":"لا يمكن إغلاق الـ issues إلا من قِبل من فتحها.","why":"أي شخص لديه صلاحية الكتابة يستطيع إغلاق issue، والدمج يستطيع إغلاقها تلقائيًا."}],"answer":1}],"s-git-4":[{"q":"<p>أنشأت ثلاثة commits محلية على فرعك: <code dir=\"ltr\">wip 1</code> و <code dir=\"ltr\">wip 2</code> و <code dir=\"ltr\">wip 3</code>. لم يُرفع أيّ منها. وتريد commit واحدًا نظيفًا بدلًا منها. ما الطريقة الأكثر مباشرة؟</p>","options":[{"text":"<code dir=\"ltr\">git reset --soft HEAD~3</code>، ثم <code dir=\"ltr\">git commit -m &quot;Add contact validation&quot;</code>","why":"صحيح. يرجع الفرع ثلاثة commits بينما تبقى كل تغييراتها مجهّزة، جاهزة لتُسجَّل مرة واحدة."},{"text":"<code dir=\"ltr\">git revert HEAD~3..HEAD</code>","why":"هذا يضيف ثلاثة commits أخرى تتراجع عن الثلاثة الأولى، وهو عكس ما تريده."},{"text":"<code dir=\"ltr\">git reset --hard HEAD~3</code>، ثم commit","why":"<code dir=\"ltr\">--hard</code> يرمي التغييرات أيضًا، فلن يبقى شيء لتسجيله."},{"text":"<code dir=\"ltr\">git commit --amend</code> ثلاث مرات","why":"الـ amend لا يستبدل إلا آخر commit في كل مرة؛ ولا يجمع الـ commits الأقدم."}],"answer":0},{"q":"<p>يعرض <code dir=\"ltr\">git stash list</code> ثلاثة مدخلات. تريد تطبيق الأوسط، <code dir=\"ltr\">stash@{1}</code>، وإبقاءه في القائمة تحسّبًا لحاجتك إليه مجددًا. أيّ أمر؟</p>","options":[{"text":"<code dir=\"ltr\">git stash pop stash@{1}</code>","why":"الـ pop يطبّق الـ stash ثم يحذفه، فلن يبقى في القائمة."},{"text":"<code dir=\"ltr\">git stash show stash@{1}</code>","why":"هذا يعرض محتوى الـ stash دون تطبيق أي شيء."},{"text":"<code dir=\"ltr\">git stash apply stash@{1}</code>","why":"صحيح. <code dir=\"ltr\">apply</code> يستعيد التغييرات ويترك مدخل الـ stash في مكانه."},{"text":"<code dir=\"ltr\">git stash push stash@{1}</code>","why":"<code dir=\"ltr\">push</code> ينشئ stash جديدًا من تغييراتك الحالية؛ ولا يطبّق واحدًا."}],"answer":2},{"q":"<p>أيّ <code dir=\"ltr\">.gitignore</code> يتجاهل كل ملف <code dir=\"ltr\">.log</code> في المشروع باستثناء <code dir=\"ltr\">logs/keep.log</code>؟</p>","options":[{"text":"<code dir=\"ltr\">logs/</code> في سطر، ثم <code dir=\"ltr\">!logs/keep.log</code>","why":"بمجرد تجاهل مجلد <code dir=\"ltr\">logs/</code>، لا ينظر Git داخله أبدًا، فلا يستطيع سطر <code dir=\"ltr\">!</code> إعادة تضمين الملف."},{"text":"<code dir=\"ltr\">*.log</code> في سطر، ثم <code dir=\"ltr\">!logs/keep.log</code>","why":"صحيح. المستبعد هو الملفات فقط لا المجلد، فيستطيع سطر <code dir=\"ltr\">!</code> اللاحق إعادة تضمين ذلك الملف وحده."},{"text":"<code dir=\"ltr\">!logs/keep.log</code> في سطر، ثم <code dir=\"ltr\">*.log</code>","why":"الأسطر اللاحقة هي التي تفوز. <code dir=\"ltr\">*.log</code> يأتي أخيرًا فيتجاهل <code dir=\"ltr\">keep.log</code> مجددًا."},{"text":"<code dir=\"ltr\">*.log # except logs/keep.log</code>","why":"<code dir=\"ltr\">#</code> لا تبدأ تعليقًا إلا في بداية السطر؛ وهذا النمط يطابق ملفًا يحمل ذلك الاسم كاملًا."}],"answer":1},{"q":"<p>يعرض الـ pull request الخاص بك علامة خطأ حمراء بجانب <code dir=\"ltr\">CI / check</code>. ماذا تفعل؟</p>","options":[{"text":"انقر Re-run حتى يصبح أخضر.","why":"إن كان الفحص يفشل لسبب حقيقي، فإعادة التشغيل تعطي النتيجة نفسها. أعد التشغيل فقط للإخفاقات المتقلّبة فعلًا."},{"text":"اطلب من مسؤول الـ repository تجاوز القاعدة والدمج.","why":"التجاوز يُفشل الغرض من الفحص. اعرف سبب فشله أولًا."},{"text":"أغلق الـ pull request وافتح واحدًا جديدًا.","why":"الـ pull request الجديد يشغّل الفحص نفسه على الكود نفسه ويفشل بالطريقة نفسها."},{"text":"افتح Details، واقرأ سجلّ الخطوة الفاشلة، وأعد إنتاج المشكلة محليًا بـ <code dir=\"ltr\">npm test</code>، ثم أصلح وسجّل وارفع إلى الفرع نفسه.","why":"صحيح. الرفع الجديد يعيد تشغيل الـ workflow على الـ pull request المحدَّث."}],"answer":3},{"q":"<p>يجب نشر الـ commit المنشور حاليًا على أنه الإصدار 1.2.0 على GitHub. أيّ الأوامر تفعل ذلك؟</p>","options":[{"text":"<code dir=\"ltr\">git branch v1.2.0</code>، ثم <code dir=\"ltr\">git push origin v1.2.0</code>","why":"الفرع يتحرّك مع كل commit، فهو الأداة الخطأ لتعليم إصدار ثابت."},{"text":"<code dir=\"ltr\">git tag v1.2.0</code>، ثم <code dir=\"ltr\">git push</code>","why":"هذا ينشئ tag خفيفًا ثم لا يرفعه، لأن الرفع العادي لا يرسل الـ tags."},{"text":"<code dir=\"ltr\">git tag -a v1.2.0 -m &quot;Release 1.2.0&quot;</code>، ثم <code dir=\"ltr\">git push origin v1.2.0</code>","why":"صحيح. الـ tag المشروح يسجّل من علّم الإصدار ولماذا، والـ tag يجب أن يُرفع صراحةً."},{"text":"<code dir=\"ltr\">git commit --tag v1.2.0</code>","why":"لا يوجد خيار <code dir=\"ltr\">--tag</code> في <code dir=\"ltr\">git commit</code>. الـ tags تُنشأ بـ <code dir=\"ltr\">git tag</code>."}],"answer":2}]},"final":[{"q":"<p>أيّ وصف للـ branch في Git دقيق؟</p>","options":[{"text":"نسخة كاملة من ملفات المشروع مخزّنة في مجلد خاص بها.","why":"هكذا كانت تعمل بعض الأنظمة القديمة. أما في Git فإنشاء الفرع لا ينسخ شيئًا."},{"text":"مؤشّر مسمّى ومتحرّك إلى commit واحد؛ والسجلّ يأتي من تتبّع الآباء.","why":"صحيح. ولهذا فإنشاء الفروع والتنقّل بينها وحذفها رخيص."},{"text":"مجلد داخل <code dir=\"ltr\">.git</code> يحتوي كل commit أُنشئ على ذلك الفرع.","why":"الـ commits لا تُصنَّف بحسب الفروع. وملف الفرع يحمل معرّف commit واحدًا."},{"text":"نسخة الـ repository التي تعيش على GitHub.","why":"هذا هو الـ remote. والفروع موجودة محليًا وعلى الـ remotes كليهما."}],"answer":1},{"q":"<p>أنشأت commitين على <code dir=\"ltr\">main</code> كانا مقصودين لفرع جديد. لم يُرفع شيء بعد. ما الإصلاح الأنظف؟</p>","options":[{"text":"<code dir=\"ltr\">git revert HEAD~2..HEAD</code>، ثم أعد العمل على فرع.","why":"الـ revert يضيف commits تراجع إلى <code dir=\"ltr\">main</code> لعمل لم يره أحد غيرك، وستعيد العمل بيدك."},{"text":"<code dir=\"ltr\">git switch -c contact-form</code> ثم تابع.","why":"الفرع الجديد فيه الـ commits، لكن <code dir=\"ltr\">main</code> ما زال يحتويها أيضًا."},{"text":"<code dir=\"ltr\">git push</code>، ثم أصلح الأمر على GitHub.","why":"هذا ينشر الخطأ أولًا، فيحوّل إصلاحًا محليًا سهلًا إلى مشكلة في سجلّ مشترك."},{"text":"<code dir=\"ltr\">git branch contact-form</code>، ثم <code dir=\"ltr\">git reset --hard origin/main</code>، ثم <code dir=\"ltr\">git switch contact-form</code>.","why":"صحيح. الفرع الجديد يحتفظ بالـ commits، و <code dir=\"ltr\">main</code> يعود ليطابق GitHub."}],"answer":3},{"q":"<p>أيّ أمر يسرد الـ commits الموجودة على <code dir=\"ltr\">feature</code> وليست على <code dir=\"ltr\">main</code>؟</p>","options":[{"text":"<code dir=\"ltr\">git log --oneline main..feature</code>","why":"صحيح. <code dir=\"ltr\">A..B</code> تعني الـ commits التي يمكن الوصول إليها من B لا من A."},{"text":"<code dir=\"ltr\">git log --oneline feature..main</code>","why":"هذا هو العكس: الـ commits الموجودة على <code dir=\"ltr\">main</code> وتنقص <code dir=\"ltr\">feature</code>."},{"text":"<code dir=\"ltr\">git diff main feature</code>","why":"هذا يقارن لقطتين سطرًا بسطر. ولا يسرد commits."},{"text":"<code dir=\"ltr\">git log --oneline --all</code>","why":"هذا يسرد كل commit على كل فرع، دون الفصل بينها."}],"answer":0},{"q":"<p>مع ضبط <code dir=\"ltr\">pull.rebase</code> على <code dir=\"ltr\">true</code>، يتوقّف <code dir=\"ltr\">git pull</code> بتعارض في <code dir=\"ltr\">index.html</code>. أصلحت الملف. ما الذي يُنهي المهمّة؟</p>","options":[{"text":"<code dir=\"ltr\">git add index.html</code>، ثم <code dir=\"ltr\">git merge --continue</code>","why":"لا يوجد merge قيد التنفيذ. مع <code dir=\"ltr\">pull.rebase true</code>، الـ pull ينفّذ rebase."},{"text":"<code dir=\"ltr\">git commit -am &quot;Fix conflict&quot;</code>","why":"أثناء الـ rebase ينشئ Git الـ commits بنفسه. والـ commit اليدوي يضيف commit زائدًا."},{"text":"<code dir=\"ltr\">git add index.html</code>، ثم <code dir=\"ltr\">git rebase --continue</code>","why":"صحيح. جهّز الحلّ ودع الـ rebase يعيد تشغيل بقية الـ commits الخاصة بك."},{"text":"شغّل <code dir=\"ltr\">git pull</code> مجددًا.","why":"هناك rebase قيد التنفيذ أصلًا، فلن يبدأ Git عملية أخرى حتى تُنهي الحالية أو تلغيها."}],"answer":2},{"q":"<p>حذفت <code dir=\"ltr\">experiment</code> بالقوة بـ <code dir=\"ltr\">git branch -D</code> ثم تذكّرت أن فيه commit تحتاجه. كيف تستعيده؟</p>","options":[{"text":"استعده من سلّة المهملات؛ فـ Git ينقل الفروع المحذوفة إليها.","why":"لا يستخدم Git سلّة مهملات نظامك. والـ commit ما زال داخل <code dir=\"ltr\">.git</code>."},{"text":"شغّل <code dir=\"ltr\">git revert experiment</code>.","why":"اسم الفرع لم يعد موجودًا، والـ revert ينشئ commits تراجع على أي حال."},{"text":"جِد الـ commit في <code dir=\"ltr\">git reflog</code>، ثم <code dir=\"ltr\">git switch -c experiment &lt;commit-id&gt;</code>.","why":"صحيح. حذف الفرع يزيل مؤشّرًا؛ والـ reflog يتذكّر أين كان الـ HEAD، فتستطيع توجيه فرع جديد إليه."},{"text":"ضاع إلى الأبد؛ فـ <code dir=\"ltr\">-D</code> يحذف الـ commits نهائيًا.","why":"<code dir=\"ltr\">-D</code> لا يحذف إلا اسم الفرع. والـ commits التي لا يمكن الوصول إليها تبقى قابلة للاستعادة لأسابيع."}],"answer":2},{"q":"<p>يتّفق فريقك على ألّا يرفع أحد إلى <code dir=\"ltr\">main</code> مباشرة وأن يجتاز كل pull request الـ CI. ما الذي يفرض ذلك؟</p>","options":[{"text":"branch ruleset على <code dir=\"ltr\">main</code> مع Require a pull request و Require status checks to pass.","why":"صحيح. يرفض GitHub حينها الرفع المباشر ويُبقي زر الدمج معطّلًا حتى يصبح الفحص المطلوب أخضر."},{"text":"ملاحظة في CONTRIBUTING.md.","why":"الوثائق تشرح القاعدة لكنها لا تستطيع منع أحد من خرقها."},{"text":"workflow يتراجع عن أي رفع مباشر إلى <code dir=\"ltr\">main</code>.","why":"هذا يتصرّف بعد وقوع الضرر، ويضيف ضجيجًا إلى السجلّ. أما القواعد فتمنعه من البداية."},{"text":"إضافة <code dir=\"ltr\">main</code> إلى <code dir=\"ltr\">.gitignore</code>.","why":"<code dir=\"ltr\">.gitignore</code> يتحكّم في الملفات التي يتتبّعها Git. ولا علاقة له بالفروع أو الرفع."}],"answer":0},{"q":"<p>أيّ مما يلي آمن على فرع سحبه زملاؤك بالفعل؟</p>","options":[{"text":"<code dir=\"ltr\">git commit --amend</code> على آخر commit، ثم رفع بالقوة.","why":"الـ amend يستبدل commit يملكونه بالفعل، فلا يعود سجلّهم يطابق سجلّك."},{"text":"<code dir=\"ltr\">git rebase -i</code> لضغط commits قديمة، ثم رفع بالقوة.","why":"الـ rebase يعيد كتابة commits مشارَكة، وهذا يخرق القاعدة الذهبية."},{"text":"<code dir=\"ltr\">git reset --hard HEAD~2</code>، ثم رفع بالقوة.","why":"هذا يحذف commits من فرع بنى عليه آخرون."},{"text":"<code dir=\"ltr\">git revert &lt;commit&gt;</code> ورفع عادي.","why":"صحيح. الـ revert يضيف commit جديدًا ولا يعيد كتابة شيء، فيكفي أن يسحبه زملاؤك."}],"answer":3},{"q":"<p>في <code dir=\"ltr\">styles.css</code> تغييران لا علاقة بينهما: إصلاح لون وبداية تخطيط جديد. تريد تسجيل إصلاح اللون فقط. ماذا تستخدم؟</p>","options":[{"text":"<code dir=\"ltr\">git commit -a</code>","why":"هذا يسجّل كل تغيير متتبَّع، بما في ذلك التخطيط غير المكتمل."},{"text":"<code dir=\"ltr\">git add -p styles.css</code>، مع تجهيز كتلة اللون فقط","why":"صحيح. وضع الـ patch يتيح لك تجهيز الكتل (hunks) منفردة، فلا يحتوي الـ commit إلا الإصلاح."},{"text":"<code dir=\"ltr\">git stash</code>، ثم commit","why":"الـ stash يزيل التغييرين كليهما، فلا يبقى شيء لتسجيله."},{"text":"<code dir=\"ltr\">git restore styles.css</code>، ثم commit","why":"هذا يرمي التغييرين كليهما من الملف."}],"answer":1},{"q":"<p>أيّ عبارة عن مصادقة SSH مع GitHub صحيحة؟</p>","options":[{"text":"تلصق مفتاحك الخاص في GitHub ليتحقّق منك.","why":"المفتاح الخاص يجب ألّا يغادر جهازك أبدًا. ولا يحتاج GitHub إلا المفتاح العام."},{"text":"مفاتيح SSH تنتهي صلاحيتها كل 30 يومًا ويجب توليدها من جديد.","why":"مفاتيح SSH في GitHub ليس لها تاريخ انتهاء (يحذف GitHub المفتاح فقط إن بقي عامًا دون استخدام). تحذفها أنت حين تتخلّى عن الجهاز."},{"text":"بمجرد إعداد SSH، تستخدمه كل الـ remotes تلقائيًا.","why":"عنوان الـ remote هو الذي يقرّر. العناوين <code dir=\"ltr\">https://</code> تواصل استخدام HTTPS حتى تغيّرها."},{"text":"تضيف المفتاح العام <code dir=\"ltr\">.pub</code> إلى GitHub، ويبقى المفتاح الخاص على جهازك.","why":"صحيح. يتحقّق GitHub من أنك تملك المفتاح الخاص المطابق للمفتاح العام في حسابك."}],"answer":3},{"q":"<p>لم يتحرّك <code dir=\"ltr\">main</code> منذ أنشأت <code dir=\"ltr\">footer</code>، لكنك تريد أن يُظهر سجلّ <code dir=\"ltr\">main</code> عمل الـ footer كـ feature واحدة مجمّعة. أيّ أمر؟</p>","options":[{"text":"<code dir=\"ltr\">git merge footer</code>","why":"مع عدم تحرّك <code dir=\"ltr\">main</code>، ينفّذ هذا fast-forward، فلا merge commit ولا تجميع."},{"text":"<code dir=\"ltr\">git merge --no-ff footer</code>","why":"صحيح. يفرض merge commit حتى حين يكون الـ fast-forward ممكنًا."},{"text":"<code dir=\"ltr\">git merge --ff-only footer</code>","why":"هذا لا يسمح إلا بالـ fast-forward، وهو عكس ما تريده."},{"text":"<code dir=\"ltr\">git rebase footer</code>","why":"تنفيذ rebase لـ <code dir=\"ltr\">main</code> على <code dir=\"ltr\">footer</code> يعطيك خطًا مستقيمًا، دون تجميع أيضًا."}],"answer":1},{"q":"<p>في سير عمل الـ fork، إلى أين يذهب فرع الـ feature الخاص بك قبل أن تفتح الـ pull request؟</p>","options":[{"text":"مباشرة إلى الـ repository الأصلي (<code dir=\"ltr\">upstream</code>).","why":"دون صلاحية كتابة يُرفض هذا الرفع. ولهذا يوجد الـ fork."},{"text":"لا مكان؛ يقرأ GitHub الفروع من حاسوبك.","why":"لا يرى GitHub إلا ما رُفع إلى repository على GitHub."},{"text":"إلى الـ fork الخاص بك (<code dir=\"ltr\">origin</code>)، ثم يذهب الـ pull request من فرع الـ fork إلى <code dir=\"ltr\">main</code> في الأصل.","why":"صحيح. ترفع إلى حيث لديك صلاحية الكتابة، وتقترح التغيير على الأصل."},{"text":"إلى فرع <code dir=\"ltr\">main</code> في الـ fork الخاص بك.","why":"إبقاء <code dir=\"ltr\">main</code> مرآة للـ upstream واستخدام فرع منفصل لكل مساهمة يتجنّب الـ pull requests الفوضوية."}],"answer":2},{"q":"<p>workflow في <code dir=\"ltr\">.github/workflow/ci.yml</code> لا يعمل أبدًا على الـ pull requests، وتبويب Actions لا يعرض شيئًا. ما السبب الأرجح؟</p>","options":[{"text":"يجب أن يكون اسم المجلد <code dir=\"ltr\">.github/workflows</code> (بالجمع).","why":"صحيح. لا يقرأ GitHub ملفات الـ workflows إلا من <code dir=\"ltr\">.github/workflows/</code>."},{"text":"يجب أن يكون اسم الملف <code dir=\"ltr\">main.yml</code>.","why":"ملفات الـ workflows يمكن أن تحمل أي اسم ينتهي بـ <code dir=\"ltr\">.yml</code> أو <code dir=\"ltr\">.yaml</code>."},{"text":"الـ Actions لا تعمل إلا على الـ repositories الخاصة.","why":"الـ Actions تعمل على الـ repositories العامة والخاصة على حدّ سواء."},{"text":"الـ workflows لا تعمل إلا بعد أن يشترطها ruleset.","why":"الـ rulesets تستطيع اشتراط فحص، لكن الـ workflows تعمل بمحفّزاتها الخاصة بغضّ النظر عن ذلك."}],"answer":0}],"glossary":[{"id":"version-control","term":"التحكم في الإصدارات (Version control)","definition":"نظام يسجّل لقطات من المشروع عبر الزمن، فتستطيع أن ترى ما الذي تغيّر ومتى ولماذا، وأن تعود إلى نسخ سابقة، وأن تجمع عمل عدة أشخاص.","lesson":"l-git-1-1"},{"id":"repository","term":"المستودع (Repository)","definition":"السجلّ الكامل للمشروع وإعداداته، مخزّنًا في مجلد .git المخفي؛ يحتوي كل commit وكل فرع وكل tag، ويُنشأ بـ git init أو git clone.","lesson":"l-git-1-2"},{"id":"commit","term":"Commit","definition":"لقطة مسجّلة لكل ملف متتبَّع، مع المؤلف والتاريخ والرسالة ومؤشّر إلى الـ commit الأب. معرّفه hash مشتقّ من كل ذلك، لذا لا يتغيّر أبدًا.","lesson":"l-git-1-1"},{"id":"working-tree","term":"مجلد العمل (Working tree)","definition":"ملفات المشروع على القرص التي تفتحها وتعدّلها. يقارنها Git بالـ index وبآخر commit ليبلّغك بما تغيّر.","lesson":"l-git-1-3"},{"id":"index","term":"منطقة التجهيز (Index / staging area)","definition":"الـ commit التالي قيد التجميع. ينسخ git add المحتوى الحالي للملف إليه، ويحوّله git commit إلى commit، ويُخرج git restore --staged المحتوى منه.","lesson":"l-git-1-3"},{"id":"untracked-file","term":"ملف غير متتبَّع (Untracked file)","definition":"ملف في مجلد العمل لم يسجّله Git قط وليس مجهّزًا. يبقى خارج الـ commits حتى تنفّذ git add له، ويستطيع .gitignore إخفاءه.","lesson":"l-git-1-2"},{"id":"dag","term":"رسم الـ commits البياني (DAG)","definition":"سجلّ Git كرسم بياني موجَّه غير دوري: يشير كل commit إلى أبيه أو أبويه، وتتبّع هذه الروابط إلى الخلف من أي commit يعطيك سجلّه.","lesson":"l-git-1-4"},{"id":"merge-commit","term":"Merge commit","definition":"commit له أبوان، يُنشأ حين يجمع Git فرعين تقدّم كلاهما. ولقطته تحتوي العمل من خطَّي السجلّ معًا.","lesson":"l-git-1-4"},{"id":"branch","term":"الفرع (Branch)","definition":"مؤشّر مسمّى ومتحرّك إلى commit واحد. إنشاء commit وأنت على فرع يقدّمه إلى الـ commit الجديد؛ وإنشاء فرع لا ينسخ أي ملفات.","lesson":"l-git-2-1"},{"id":"head","term":"HEAD","definition":"مؤشّر Git إلى مكانك. يسمّي عادةً الفرع الحالي، والفرع يسمّي commit؛ و HEAD~1 تعني الـ commit الذي يسبق الـ commit الذي أنت عليه.","lesson":"l-git-2-1"},{"id":"detached-head","term":"Detached HEAD","definition":"الحالة التي يشير فيها الـ HEAD مباشرة إلى commit بدل فرع. الـ commits الجديدة المنشأة هناك لا تنتمي إلى أي فرع حتى تنشئ واحدًا بـ git switch -c.","lesson":"l-git-2-1"},{"id":"fast-forward","term":"Fast-forward","definition":"دمج لم يتحرّك فيه الفرع الحالي منذ بدأ الفرع الآخر، فيقدّم Git المؤشّر إلى الأمام ببساطة ولا ينشئ merge commit.","lesson":"l-git-2-2"},{"id":"merge-base","term":"Merge base","definition":"أحدث commit يتشاركه فرعان. يقارن Git طرف كل فرع به أثناء الدمج الثلاثي ليقرّر أيّ التغييرات يأخذ.","lesson":"l-git-2-2"},{"id":"merge-conflict","term":"تعارض الدمج (Merge conflict)","definition":"ما يحدث حين يغيّر الفرعان الأسطر نفسها منذ الـ merge base. يكتب Git النسختين بين علامات وينتظر منك أن تحرّر الملف وتنفّذ git add وتكمل.","lesson":"l-git-2-3"},{"id":"rebase","term":"Rebase","definition":"إعادة تشغيل commits فرع فوق commit آخر، فتنتج commits جديدة بمعرّفات جديدة وسجلّ خطّي. آمن فقط للـ commits التي لم يبنِ عليها أحد غيرك.","lesson":"l-git-2-4"},{"id":"force-with-lease","term":"git push --force-with-lease","definition":"رفع بالقوة لا يكتب فوق الفرع البعيد إلا إن كان ما زال يشير إلى حيث رأيته آخر مرة، فلن تمحو بصمت commits رفعها شخص آخر منذ آخر fetch لك.","lesson":"l-git-2-4"},{"id":"remote","term":"المستودع البعيد (Remote)","definition":"عنوان URL مسمّى لنسخة أخرى من الـ repository، على GitHub في الغالب. و origin هو الاسم المتعارف عليه للنسخة التي استنسخت منها أو ترفع إليها.","lesson":"l-git-3-1"},{"id":"remote-tracking-branch","term":"فرع تتبّع بعيد (Remote-tracking branch)","definition":"سجلّ محلي للقراءة فقط، مثل origin/main، لمكان فرع ما على الـ remote عند آخر fetch أو pull أو push. لا يتحرّك إلا حين تتواصل مع الـ remote.","lesson":"l-git-3-1"},{"id":"upstream-branch","term":"Upstream branch","definition":"الفرع البعيد الذي يتتبّعه فرع محلي، ويُضبط بـ git push -u. يجعل git push و git pull المجرّدين يعملان، ويتيح لـ git status عرض عدد الـ commits المتقدّمة والمتأخّرة.","lesson":"l-git-3-1"},{"id":"ssh-key","term":"زوج مفاتيح SSH (SSH key pair)","definition":"مفتاحان مترابطان يُستخدمان لإثبات هويتك أمام GitHub: المفتاح الخاص يبقى على جهازك، والمفتاح العام ذو الامتداد .pub يُضاف إلى حسابك على GitHub.","lesson":"l-git-3-2"},{"id":"pull-request","term":"طلب الدمج (Pull request)","definition":"اقتراح على GitHub لدمج فرع في آخر، يعلّق فيه المراجعون وتعمل فيه الفحوص الآلية، ثم يُدمج التغيير في النهاية. والرفع الجديد إلى الفرع يحدّثه.","lesson":"l-git-3-3"},{"id":"fork","term":"Fork","definition":"نسختك الخاصة على GitHub من repository يملكه شخص آخر. ترفع إليها الفروع وتفتح منها pull requests عائدة إلى الأصل، الذي يُسمّى بحسب العُرف upstream.","lesson":"l-git-3-4"},{"id":"revert","term":"git revert","definition":"أمر ينشئ commit جديدًا يطبّق العكس التام لـ commit سابق. يتراجع عن التغييرات دون إعادة كتابة السجلّ، لذا فهو آمن على الفروع المشتركة.","lesson":"l-git-4-1"},{"id":"reflog","term":"Reflog","definition":"سجلّ محلي لكل موضع شغله الـ HEAD، مثل الـ commits وعمليات الـ reset والـ rebase والانتقال بين الفروع. يتيح لك العثور على commits لم يعد يشير إليها أي فرع واستعادتها.","lesson":"l-git-4-1"},{"id":"stash","term":"Stash","definition":"مكان تركن فيه التغييرات غير المسجّلة لتحصل على مجلد عمل نظيف. يحفظها git stash push، ويعيدها git stash pop؛ أضف -u لتضمين الملفات غير المتتبَّعة.","lesson":"l-git-4-2"},{"id":"gitignore","term":".gitignore","definition":"ملف يسرد أنماط الملفات غير المتتبَّعة التي يجب ألّا يتتبّعها Git أبدًا، مثل node_modules/ وناتج البناء و .env. ولا يؤثّر في الملفات المتتبَّعة أصلًا.","lesson":"l-git-4-2"},{"id":"conventional-commits","term":"Conventional Commits","definition":"صيغة لرسائل الـ commits، type(optional scope): description، تستخدم أنواعًا مثل feat و fix و docs، لكي تستطيع الأدوات توليد سجلّات التغيير وأرقام الإصدارات الدلالية.","lesson":"l-git-4-3"},{"id":"annotated-tag","term":"Tag مشروح (Annotated tag)","definition":"علامة دائمة ومسمّاة على commit تخزّن أيضًا اسم الواسم والتاريخ ورسالة. يُنشأ بـ git tag -a، وهو الطريقة المعتادة لتعليم الإصدارات.","lesson":"l-git-4-3"},{"id":"github-actions-workflow","term":"GitHub Actions workflow","definition":"ملف YAML في .github/workflows/ يشغّل jobs على آلات GitHub حين تقع أحداث مثل pull_request. وكل job يبلّغ عن status check على الـ pull request.","lesson":"l-git-4-4"},{"id":"ruleset","term":"Ruleset","definition":"مجموعة مسمّاة من قواعد GitHub للفروع أو الـ tags، مثل اشتراط pull request، واشتراط نجاح الـ status checks، ومنع الرفع بالقوة إلى main.","lesson":"l-git-4-4"}]}