تخطَّ إلى المحتوى
GitTree

الدرس 31 · الاستعادة

احفظ العمل المنفصل وابدأ تاريخًا يتيمًا

سمِّ إيداعًا أُجري على HEAD منفصل قبل أن تغادره، ثم ابدأ فرعًا يتيمًا في نسخة منفصلة وانظر ما يزيله وما يُبقيه.

حُفظ التقدّم في هذا المتصفح.

يحمّل التشغيل الفيديو من YouTube (وضع الخصوصية المحسّنة).

تعليق صوتي بالإنجليزية · ترجمة إنجليزية وعربية

افتح صفحة الفيديوشاهد على YouTube

ما ستتعلمه

  • أن تميّز HEAD المربوط والمنفصل وغير المولود.
  • أن تضع فرعًا على إيداع منفصل ليبقى قابلًا للوصول.
  • أن تتوقع ما يفعله الفرع اليتيم بالملفات المتتبَّعة وغير المتتبَّعة والفرع القديم.
  • أن تقرأ رفض تبديل سببه تغييرات محلية.

قبل البدء

نسختان مؤقتتان من ناتج الدرس 18 (M). في الأولى تبدّل إلى الإيداع M نفسه بدل الفرع وتجري تغييرًا صغيرًا على ورقة الأنماط وتريد الاحتفاظ به. وفي الثانية تريد تاريخًا جديدًا غير مرتبط لفرع توثيق فقط دون المساس بـ main.

كل شيء هنا محلي وضمن الخطة المجانية (يطلب GitTree تسجيل الدخول): دون حساب استضافة أو مزوّد أو إنترنت. كل الهويات خيالية (example.com). لا تنفّذ الدرس في مشروع حقيقي.

نفّذ التغييرات في GitTree أو بأوامر Git الأصلية، لا بالطريقتين في النسخة نفسها. القراءة بعد التنفيذ آمنة دائمًا. تسميات القوائم مأخوذة من نصوص GitTree 2.0 نفسها، وشُغّلت الأوامر الأصلية بـ Git 2.43 في نسخ مؤقتة. لم تُشغَّل خطوات GitTree على macOS أو Windows أو Linux.

الفكرة وأثر الأفعال

يسمّي HEAD عادةً فرعًا، والإيداع يحرّك ذلك الفرع. وحين يسمّي HEAD إيداعًا مباشرة يكون منفصلًا: الإيداع الجديد حقيقي لكن لا شيء يسمّيه، فإن غادرته صار قابلًا للوصول عبر سجل المراجع فقط. ووضع فرع عليه يعالج ذلك. وإنشاء فرع لا يبدّل إليه ما لم تختر ذلك.

أما الفرع اليتيم فشيء آخر. يسمّي HEAD فرعًا لا إيداع فيه بعد (فرع غير مولود). وإيداعه الأول بلا أب، فلا يشارك main أي تاريخ. ويُخرج Git الملفات المتتبَّعة من مجلد العمل؛ وتبقى غير المتتبَّعة والمتجاهَلة. وإن كان .gitignore القديم متتبَّعًا فقواعده تذهب معه، وقد تظهر الملفات التي تجاهلها غير متتبَّعة الآن. ويحتفظ فرعك القديم بكل إيداعاته.

الحالتان طبيعيتان لا ضرر فيهما. ويُرفض التبديل حين يكتب فوق تغييرات محلية؛ ولا يضيع شيء.

إعداد حالة المشروع المحددة

ابدأ من نسختين عاديتين منفصلتين من ناتج الدرس 18 وأبقِ أصلك دون مساس. النصوص أدناه هي ورقة الأنماط الأساسية (BASE_CSS) والموسَّعة (LAYOUT) وتعديل غير مودَع (DIRTY_LAYOUT) وملف readme اليتيم (ORPHAN_README) وملاحظة محلية (LOCAL_NOTE) وملاحظة متجاهَلة (IGNORED_NOTE).

مدخلات الملفات كاملة

احفظ كل محتوى كامل بصيغة UTF-8 وLF وسطر نهائي، في مرحلته فقط ودون أسوار Markdown. BASE_CSS مرجع 48rem الموروث، وLAYOUT إيداع 52rem، وDIRTY_LAYOUT إعادة مستقلة غير مجهزة بعرض 56rem. يخص ملفا الملاحظات نسخة البقاء وحدها. ترث ملفات main الثابتة محتوى L018/M الكامل.

styles.css — BASE_CSS

مقتطف من ملف · css
:root {
  color-scheme: light;
  --page: #f7f8fa;
  --surface: #ffffff;
  --text: #17212e;
  --link: #154ab5;
  --line: #bac4d0;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--page);
  color: var(--text);
  font: 1rem/1.6 system-ui, sans-serif;
}

a {
  color: var(--link);
}

.site-header,
main,
footer {
  width: min(68rem, 100% - 2rem);
  margin-inline: auto;
}

.site-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1rem;
}

.hero {
  max-width: 48rem;
  padding-block: 3rem;
}

h1 {
  font-size: clamp(2rem, 5vw, 3.5rem);
  line-height: 1.2;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 1rem;
}

.feature-card {
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 0.75rem;
  background: var(--surface);
}

section {
  margin-block-end: 2rem;
}

button,
input {
  font: inherit;
}

button,
input {
  padding: 0.5rem 0.75rem;
}

:focus-visible {
  outline: 3px solid var(--link);
  outline-offset: 3px;
}

form {
  display: grid;
  max-width: 28rem;
  gap: 0.5rem;
}

styles.css — LAYOUT

مقتطف من ملف · css
:root {
  color-scheme: light;
  --page: #f7f8fa;
  --surface: #ffffff;
  --text: #17212e;
  --link: #154ab5;
  --line: #bac4d0;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--page);
  color: var(--text);
  font: 1rem/1.6 system-ui, sans-serif;
}

a {
  color: var(--link);
}

.site-header,
main,
footer {
  width: min(68rem, 100% - 2rem);
  margin-inline: auto;
}

.site-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1rem;
}

.hero {
  max-width: 52rem;
  padding-block: 3rem;
}

h1 {
  font-size: clamp(2rem, 5vw, 3.5rem);
  line-height: 1.2;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 1rem;
}

.feature-card {
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 0.75rem;
  background: var(--surface);
}

section {
  margin-block-end: 2rem;
}

button,
input {
  font: inherit;
}

button,
input {
  padding: 0.5rem 0.75rem;
}

:focus-visible {
  outline: 3px solid var(--link);
  outline-offset: 3px;
}

form {
  display: grid;
  max-width: 28rem;
  gap: 0.5rem;
}

styles.css — DIRTY_LAYOUT

مقتطف من ملف · css
:root {
  color-scheme: light;
  --page: #f7f8fa;
  --surface: #ffffff;
  --text: #17212e;
  --link: #154ab5;
  --line: #bac4d0;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--page);
  color: var(--text);
  font: 1rem/1.6 system-ui, sans-serif;
}

a {
  color: var(--link);
}

.site-header,
main,
footer {
  width: min(68rem, 100% - 2rem);
  margin-inline: auto;
}

.site-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1rem;
}

.hero {
  max-width: 56rem;
  padding-block: 3rem;
}

h1 {
  font-size: clamp(2rem, 5vw, 3.5rem);
  line-height: 1.2;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 1rem;
}

.feature-card {
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 0.75rem;
  background: var(--surface);
}

section {
  margin-block-end: 2rem;
}

button,
input {
  font: inherit;
}

button,
input {
  padding: 0.5rem 0.75rem;
}

:focus-visible {
  outline: 3px solid var(--link);
  outline-offset: 3px;
}

form {
  display: grid;
  max-width: 28rem;
  gap: 0.5rem;
}

README.md — ORPHAN_README

الناتج
# GitTree Features documentation

This branch contains a documentation-only history for the fictional course project.
The landing page remains on main; no form submits information to a server.
Keep useful detached experiments under a branch name before switching away.

local-review.txt — LOCAL_NOTE

الناتج
Local rehearsal note; preserve this file.

reports/preview.txt — IGNORED_NOTE

الناتج
Ignored rehearsal preview; preserve these bytes.

في GitTree

افتح لوحة الأوامر بـ Command+Shift+P (وCtrl+Shift+P على Ubuntu).

  1. في النسخة الأولى انقر الإيداع M في المخطط بزر الفأرة الأيمن واختر التبديل إلى هذا الإيداع (Check Out This Commit). يقرأ منتقي الفروع «منفصل عند …» ويعرض المخطط HEAD، منفصل.
  2. احفظ LAYOUT باسم styles.css وجهّز الملف وأودِعه بالملخّص style: widen the hero reading measure. تعدّ اللوحة التغيير «على HEAD منفصل» أثناء العمل.
  3. انقر الإيداع الجديد بزر الفأرة الأيمن واختر إنشاء فرع هنا… (Create Branch Here…). سمِّه experiment/layout وأبقِ خيار التبديل إليه معطّلًا واختر إنشاء فرع (Create Branch). يقول الإشعار «أُنشئ experiment/layout.» ويبقى HEAD منفصلًا.
  4. انقر وسم experiment/layout بزر الفأرة الأيمن واختر التبديل إلى الفرع (Check Out Branch). صار HEAD مربوطًا من جديد، وstyles.css هو LAYOUT، وما زال main على M.
  5. تبديل بتغييرات غير مودَعة. في نسخة أخرى، افصل HEAD عند experiment/layout وعدّل styles.css إلى DIRTY_LAYOUT دون إيداع وحاول التبديل إلى main. يسرد حوار «تغييرات محلية تعترض التبديل إلى main» الملف ويعرض الإيداع أولًا (Commit First) وتخبئة ثم تبديل (Stash and Switch) والتخلّص من التغييرات ثم التبديل (Discard and Switch). اختر الإلغاء؛ فالتخلّص يحفظ نسخة تحت refs/ogt/trash لكن لا حاجة إليه هنا.
  6. اليتيم. في النسخة الثانية ومجلد العمل نظيف، نفّذ إنشاء فرع يتيم… (Create Orphan Branch…) من لوحة الأوامر. في «إنشاء فرع يتيم في …؟» اكتب orphan-docs. يذكر الحوار كم ملفًا متتبَّعًا يخرج من مجلد العمل، وأن غير المتتبَّعة والمتجاهَلة تبقى، وأن الإيداع القديم يُحفظ تحت refs/ogt/trash. الإلغاء هو الذي عليه التركيز. اختر إنشاء الفرع اليتيم (Create Orphan Branch).
  7. يقول الإشعار «بُدِّل إلى الفرع اليتيم الجديد orphan-docs. يعيدك التراجع.» ولا ملفات متتبَّعة في المجلد ولا إيداع على الفرع الجديد في المخطط. أنشئ README.md من ORPHAN_README وجهّزه وأودِعه بالملخّص docs: start the separate project guide. الإيداع الجديد بلا أب.
  8. قارن: ما زال main يحمل كل إيداع؛ والفرع اليتيم جذر منفصل. لا تدفع أيًّا من النسختين.

لكل إيداع عادي في الدورة: الملخّص (Summary) هو العنوان المحدد، والوصف (Description) خالٍ، وتبقى الخيارات تعديل الإيداع الأخير وتخطّي الخطّافات وإضافة Signed-off-by ومؤلف مختلف والدفع بعد الإيداع والسماح بإيداع فارغ معطّلة.

في الطرفية

في النسخة الأولى:

الأمر · bash
BASE_SHA="$(git rev-parse main)"
git switch --detach "$BASE_SHA"
git symbolic-ref --quiet --short HEAD; echo $?

يطبع الأمر الأخير 1: HEAD ليس فرعًا. احفظ LAYOUT باسم styles.css ثم:

الأمر · bash
git commit --quiet -am "style: widen the hero reading measure"
git branch experiment/layout
git symbolic-ref --quiet --short HEAD; echo $?
git rev-parse HEAD^ main
git switch --quiet experiment/layout
git symbolic-ref --short HEAD
git status --short
الناتج
1
experiment/layout

لا يربط أمر الإنشاء HEAD (ما زال الاستعلام الثاني يطبع 1)، والمعرّفان من rev-parse متساويان (M)، والحالة خالية. وفي نسخة أخرى منفصلة عند experiment/layout عدّل styles.css ونفّذ git switch main: يرفض Git بعبارة «Your local changes to the following files would be overwritten by checkout» ويبقى الملف كما هو.

في النسخة الثانية ومجلد العمل نظيف:

الأمر · bash
git switch --orphan orphan-docs
git symbolic-ref --short HEAD
git rev-parse --verify --quiet HEAD; echo $?
git ls-files
الناتج
orphan-docs
1

الفرع موجود لكن لا إيداع فيه (rev-parse ينتهي برمز 1) وls-files لا يطبع شيئًا. خرجت الملفات المتتبَّعة من المجلد، وبقي local-review.txt وreports/ وتظهر الآن غير متتبَّعة. أنشئ README.md ثم:

الأمر · bash
git add -- README.md
git commit --quiet -m "docs: start the separate project guide"
git ls-tree -r --name-only HEAD
git rev-list --max-parents=0 --count HEAD
git merge-base --all main orphan-docs; echo $?
الناتج
README.md
1
1

للفرع الجديد إيداع جذري واحد ولا سلف مشترك مع main (لا يطبع merge-base شيئًا وينتهي برمز 1).

النتيجة

أين تنتهي: سُمّي الإيداع المنفصل experiment/layout وبُدِّل إليه، وما زال main على M والمجلد نظيف. وفي النسخة اليتيمة فرع orphan-docs بإيداع جذري واحد بلا أب، والملفات المتتبَّعة القديمة خرجت من المجلد ويحتفظ بها main، ولم يُدفع شيء.

أخطاء شائعة

  • الإيداع على HEAD منفصل ثم التبديل بلا فرع، فيبقى الإيداع قابلًا للوصول عبر سجل المراجع فقط.
  • توقع أن ينقلك إنشاء فرع هنا… إلى الفرع الجديد.
  • بدء فرع يتيم وفي الملفات المتتبَّعة تغييرات غير مودَعة، أو ظن أنه يحذف main.
  • افتراض أن الملف المتجاهَل يبقى متجاهَلًا بعد أن يخرج .gitignore من المجلد.

إن حدث خطأ

إن غادرت إيداعًا منفصلًا بلا اسم فافتح سجل المراجع (الدرس 29) وضع عليه فرعًا. وإن كان الفرع اليتيم خطأً فإن تراجع يعيدك ما دام الفرع الجديد بلا إيداع؛ وإلا فبدّل إلى main من جديد. والتبديل المرفوض لم يغيّر شيئًا.

اختبر فهمك

أجب عن كل سؤال ثم اقرأ السبب. الأسئلة المعلَّمة «مطلوب» تُحتسب لإكمال الدرس.

  1. السؤال 1 من 6

    ما HEAD المنفصل؟

    اختر إجابة واحدة.

  2. السؤال 2 من 6

    أي مما يلي صحيح عن الفرع اليتيم؟

    اختر كل إجابة صحيحة.

  3. السؤال 3 من 6
    مطلوب

    تودع على HEAD منفصل وتنشئ experiment/layout هناك دون التبديل إليه، ثم تنظر إلى HEAD. ما هو؟

    اختر إجابة واحدة.

  4. السؤال 4 من 6

    غيّر المُدخل: في مجلد العمل تعديل غير مودَع على styles.css حين تحاول التبديل إلى main. ماذا يحدث؟

    اختر إجابة واحدة.

  5. السؤال 5 من 6
    مطلوب

    لا يطبع git ls-files شيئًا بعد git switch --orphan orphan-docs مباشرة. هل من خطأ؟

    اختر إجابة واحدة.

  6. السؤال 6 من 6
    مطلوب

    بعد أول إيداع على orphan-docs، أي قراءات تبيّن أنه جذر منفصل؟

    اختر كل إجابة صحيحة.

0 من 6 صحيحة

أجب عن الأسئلة المطلوبة إجابة صحيحة لتكمل الدرس.

جرى التحقق في هذا المتصفح.

تدريب

اذكر أين يشير HEAD في كل حالة (فرع ومنفصل وغير مولود)، ومعرّف الإيداع المنفصل والفرع عليه، وما طبعه git ls-files على الفرع اليتيم. ثم توقّع ما يحدث لملف متجاهَل حين يغيب .gitignore.

تأكيد منك أنت: لا يستطيع الموقع رؤية جهازك.

لإكمال الدرس، أجب عن الأسئلة المطلوبة إجابة صحيحة وأكّد تنفيذ التمرين العملي.

خلاصة

  • الإيداع المنفصل يحتاج فرعًا ليبقى قابلًا للوصول.
  • الفرع اليتيم يبدأ جذرًا منفصلًا ويُخرج الملفات المتتبَّعة من المجلد.
  • التبديل المرفوض يحمي تعديلك.

المصطلحات الجديدة

Detached HEAD — HEAD منفصل; orphan branch — فرع يتيم; unborn branch — فرع غير مولود; root commit — إيداع جذري.

نص الفيديو

ترجمة التعليق الصوتي الإنجليزي للفيديو، مقطعًا بعد مقطع.

اعرض النص
  1. أين HEAD؟ ثلاث حالات.
  2. قد يسمّي HEAD فرعًا أو إيداعًا أو فرعًا بلا إيداع بعد.
  3. الإيداع على HEAD منفصل حقيقي لكن لا شيء يسمّيه.
  4. فرع على ذلك الإيداع يُبقيه قابلًا للوصول.
  5. انقر الإيداع M بزر الفأرة الأيمن واختر التبديل إلى هذا الإيداع.
  6. عدّل ورقة الأنماط وجهّزها وأودِع مرة واحدة.
  7. انقر الإيداع الجديد وأنشئ فرعًا هنا دون التبديل إليه.
  8. الآن بدّل إلى الفرع. صار HEAD مربوطًا من جديد.
  9. مع تعديل غير محفوظ يُرفض التبديل. والإلغاء يُبقي كل شيء.
  10. الإيداع أولًا أو التخبئة ثم التبديل أو التخلّص. والتخلّص هو ما يُتجنب.
  11. لتاريخ جديد نفّذ إنشاء فرع يتيم من لوحة الأوامر.
  12. يقول كم ملفًا متتبَّعًا يخرج، وأن غير المتتبَّعة والمتجاهَلة تبقى.
  13. أكِّد. لا ملفات متتبَّعة في المجلد.
  14. أضف ملف readme وأودِع. هذا الإيداع بلا أب.
  15. في الطرفية ينتهي symbolic-ref برمز 1 حين يكون HEAD منفصلًا.
  16. على فرع غير مولود يفشل rev-parse لـ HEAD.
  17. بعد الإيداع لا يجد merge-base سلفًا مشتركًا.
  18. إن غادرت بلا فرع فسجل المراجع ما زال يحمله.
  19. تدريب: سمِّ HEAD في كل حالة ثم توقّع ملفًا متجاهَلًا.
  20. التالي: الاستعادة من أخطاء بعد الدفع.