الباب الثاني: أسس جت

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

الحصول على مستودع جت

في المعتاد تحصل على مستودع جت بإحدى طريقتين:

  1. تأتي مجلدًا محليًّا ليس تحت إدارة نُسخ، وتحوله إلى مستودع جت،

  2. أو تستنسخ مستودع جت موجودا بالفعل.

في كلتا الحالتين، سيصير معك مستودع جت على حاسوبك المحلي وجاهز للعمل.

ابتداء مستودع في مجلد موجود

إذا كان لديك مجلد مشروع ليس تحت إدارة نسخ الآن، وتريد أن تبدأ في إدارته باستخدام جت، تحتاج أولا إلى الذهاب إلى ذلك المجلد. إن لم تفعل هذا من قبل، فهذا قد يختلف قليلا حسب نظامك:

للينكس:

$ cd /home/user/my_project

لماك أو إس:

$ cd /Users/user/my_project

لويندوز:

$ cd C:/Users/user/my_project

ثم اكتب:

$ git init

هذا ينشئ لك مجلد فرعيا جديدا يُسمى .git (يبدأ اسمه بنقطة، فتجعله مجلدا مخفيا) ويحتوى كل الملفات الضرورية لمستودعك — أيْ هيكل مستودع جت. حتى الآن، لا شيء في مشروعك متعقَّب بعد. انظر دواخل جت للمزيد من المعلومات عن تفاصيل الملفات التي في مجلد .git الذي أنشأته للتو.

إذا أردت أن تبدأ في إدارة نسخ ملفات موجودة (وليس مجلدا فارغا)، فعليك بدء تعقب هذه الملفات وصنع إيداع مبدئي. يمكنك تحقيق هذا ببعض أوامر الإضافة، git add، والتي تحدد الملفات التي تريد تعقبها، ثم أمر الإيداع، git commit:

$ git add *.c
$ git add LICENSE
$ git commit -m 'Initial project version'  # إيداع «النسخة المبدئية من المشروع»‏

سنعرف ماذا تفعل هذه الأوامر خلال لحظات. لكن الآن، لديك مستودع جت به ملفات متعقبة وإيداع مبدئي.

استنساخ مستودع موجود

إذا كنت تريد الحصول على نسخة من مستودع جت موجود — مثلا مشروع تحب المشاركة فيه — فإن الأمر الذي تريده هو أمر الاستنساخ، git clone. إذا كنت تعرف أنظمة أخرى لإدارة النسخ مثل Subversion، ستلاحظ أن الأمر هو clone (استنساخ) وليس checkout (سحب). هذا فرق مهم؛ فبدلا من جلب مجرد نسخة للعمل عليها، يحضر لك جت تقريبا كل شيء لدى الخادوم؛ كل نسخة من كل ملف عبر تاريخ المشروع، يجذبها جت إليك عندما تكتب git clone. في الحقيقة، إذا تلف قرص الخادوم، يمكنك في الغالب استخدام ربما أي استنساخ من أي عميل لإرجاع الخادوم إلى حالته عندما اُستنسخ (قد تفقد بعض الخطاطيف الخاصة بالخادوم وأشياء من هذا القبيل، لكن جميع البيانات التي تحت إدارة النسخ ستكون موجودة — انظر تثبيت جت على خادوم للمزيد من التفاصيل).

استنسخ مستودعًا بالأمر git clone <رابط>. مثلا إذا أردت استنساخ مكتبة جت القابلة للربط المسماة libgit2، يمكنك فعل ذلك هكذا:

$ git clone https://github.com/libgit2/libgit2

هذا ينشئ مجلدًا اسمه libgit2، ويبتدئ مجلد .git فيه، ويجذب جميع بيانات هذا المستودع، ويسحب نسخة عمل من النسخة الأخيرة منه. فإذا ذهبت إلى داخل مجلد libgit2 الجديد الذي أُنشئ آنفا، فستجد فيه ملفات المشروع تنتظرك للعمل عليها أو استخدامها.

إذا أردت استنساخ المستودع إلى مجلد باسم غير libgit2، يمكنك تعيين هذا الاسم الجديد بإضافته إلى معاملات الأمر:

$ git clone https://github.com/libgit2/libgit2 mylibgit

هذا الأمر يفعل الشيء نفسه الذي يفعله الأمر السابق، لكن اختلف المجلد الهدف فصار mylibgit.

يستطيع جت التعامل مع عدد من موافيق (بروتوكولات) النقل المختلفة. استخدم المثال السابق ميفاق https://، ولكنك قد ترى أيضا git://، أو user@server:path/to/repo.git الذي يستخدم ميفاق SSH. يخبرك تثبيت جت على خادوم بجميع الخيارات التي يستطيع الخادوم إعدادها حتى يمكنك الوصول إلى مستودع جت الخاص بك، ومزايا وعيوب كلٍ منها.

تسجيل التعديلات في المستودع

الآن لديك مستودع جت أصيل على حاسوبك، وأمامك نسخة مسحوبة من جميع ملفاته، أي «نسخة عمل». غالبا ستود البدء بعمل تعديلات وإيداع لقطات من هذه التعديلات في مستودعك كل مرة يصل مشروعك إلى مرحلة تريد تسجيلها.

تذكر أن كل ملف في مجلد العمل لديك يمكن أن يكون في حالة من اثنتين: متعقَّب أو غير متعقَّب. الملفات المتعقبة هي الملفات التي كانت في اللقطة الأخيرة أو أي ملف أُهِّل حديثًا. ويمكن أن تكون غير معدَّلة، أو معدلة، أو مؤهلة. باختصار، الملفات المتعقبة هي الملفات التي يعرفها جت.

الملفات غير المتعقبة هي كل شيء آخر: أيّ ملفات في مجلد عملك لم تكن ضمن لقطتك الأخيرة وليست في منطقة التأهيل. عندما تستنسخ مستودعا أول مرة، تكون جميع ملفاتك متعقبة وغير معدلة، لأن جت سَحَبها لك للتو ولم تعدّل فيها شيئا بعد.

وعندما تبدأ في تعديل الملفات، سيراها جت معدلة، لأنك غيّرتها عما كانت عليه في إيداعك الأخير. وعندما تشرع في العمل، ستنتقي من هذه الملفات ما تؤهله ثم تودِع هذه التعديلات المؤهلة، ثم تعيد الكَرَّة.

دورة حياة حالة ملفاتك
شكل ٨. دورة حياة حالة ملفاتك

فحص حالة ملفاتك

الأداة الرئيسية التي تستعملها لتحديد أي الملفات في أي حالة هي أمر الحالة، git status. إذا نفّذت هذا الأمر مباشرةً بعد استنساخ، سترى شيئا مثل هذا:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

هذا يعني أن لديك مجلد عمل نظيف؛ أي أن لا ملف من ملفاتك المتعقبة معدل. وأيضا لا يرى جت أي ملفات غير متعقبة، وإلا لَسَرَدها هنا. وأخيرا، يخبرك هذا الأمر أي فرع أنت فيه، ويعلِمك أنه لم يختلف عن أخيه الفرع الذي في المستودع البعيد. ذلك الفرع حتى الآن هو دائما master، وهو المبدئي؛ لا تحتاج أن تقلق بشأنه هنا. سيناقش التفريع في جت الفروع والإشارات بالتفصيل.

غيّرت شركة جت‌هب (GitHub) اسم المستودع المبدئي من master إلى main في منتصف عام ٢٠٢٠، ثم تبعتها خدمات استضافة جت الأخرى. لذلك قد تجد أن اسم الفرع المبدئي هو main في المستودعات التي أُنشئت حديثًا، وليس master. وأيضا يمكنك تغيير اسم الفرع المبدئي (كما رأيت في اسم الفرع المبدئي)، فربما ترى اسمًا آخر للفرع المبدئي.

ولكن ما زال جت نفسه يستعمل master اسمًا للفرع المبدئي، لذلك فهذا ما سنستعمل خلال الك‍تاب.

لنقُل أنك أضفت ملفًا جديدًا إلى مشروعك، مثلا ملف README («اقرأني») صغير. إذا لم يكن هذا الملف موجودًا من قبل، ونفّذت أمر الحالة git status، فسترى ملفك غير المتعقب هكذا:

$ echo 'My Project' > README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    README

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

نرى أن ملفك الجديد غير متعقب، لأنه تحت عنوان ‪“Untracked files”‬ («ملفات غير متعقبة») في ناتج الحالة. «غير متعقب» لا يعني إلا أن جت يرى ملفًا لم يكن في اللقطة السابقة (الإيداع الأخير)، ولم تؤهله بعد. ولن يبدأ جت في ضمه إلى لقطات الإيداعات إلا بعد أن تخبره بذلك بأمر صريح. إنه لا يفعل ذلك لكيلا تضم بالخطأ ملفات رقمية مولدة أو ملفات أخرى لم تشأ ضمها أصلا. ولكنك تريد ضم README، فهيا بنا نبدأ تعقب هذا الملف.

تعقب ملفات جديدة

لبدء تعقب ملف جديد، استخدم أمر الإضافة git add. مثلا لبدء تعقب ملف README، نفّذ هذا:

$ git add README

إذا نفذت أمر الحالة مجددا، سترى ملف README قد صار متعقبا ومؤهلا للإيداع:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)

    new file:   README

نعرف أنه مؤهلٌ لأنه تحت عنوان ‪“Changes to be committed”‬ («تعديلات ستُودَع»). إذا أودعت الآن، فإن نسخة الملف وقت تنفيذ أمر الإضافة git add هي التي ستكون في اللقطة التاريخية التالية. تذكر أنك عندما نفذت أمر الابتداء git init سابقا، أتبعته بأمر الإضافة git add <ملفات> والذي بدأ تعقب الملفات التي في مجلدك. أمر الإضافة git add يأخذ مسار ملف أو مجلد. فإن كان مجلدًا فإنه يضيف جميع الملفات التي فيه وفي أي مجلد فرعي فيه.

تأهيل ملفات معدلة

لنعدّل ملفًا جعلناه متعقبًا بالفعل. إذا عدّلت الملف المتعقب CONTRIBUTING.md ونفذت أمر الحالة مجددًا، فترى ما يشبه هذا:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

يظهر اسم الملف CONTRIBUTING.md تحت عنوان ‪“Changes not staged for commit”‬ («تعديلات غير مؤهلة للإيداع») — والذي يعني أن ملفًا متعقبًا قد تغيّر في مجلد العمل، ولكنه لم يؤهل بعد. لتأهيله، نفذ أمر الإضافة git add. يُستخدم أمر الإضافة لأغراض عديدة: لبدء تعقب ملفات جديدة، ولتأهيل الملفات، ولأفعال أخرى مثل إعلان حل الملفات في نزاعات الدمج. ربما من المفيد أن تعتبرها بمعنى «أضف تحديدا هذا المحتوى إلى الإيداع التالي» بدلا من «أضف هذا الملف إلى المشروع». لننفذ git add الآن لتأهيل ملف CONTRIBUTING.md ثم ننفذ git status مجددا:

$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

كلا الملفين مؤهلان وسيكونان في إيداعك التالي. لنقُل أنك تذكرت الآن تعديلًا طفيفًا أردته في ملف CONTRIBUTING.md قبل إيداعه. ستفتح الملف مجددا، وتصنع تعديلك، وتحفظه وتغلقه. الآن أنت جاهز للإيداع. ولكن، لننفذ git status مرة أخرى:

$ vim CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

يا للهول! لقد صار CONTRIBUTING.md مسرودًا أنه مؤهلا وكذلك غير مؤهل. كيف يُعقل هذا؟ يتضح أن جت يؤهل الملف تماما كما هو عندما تنفذ git add. فإذا أودعت الآن، فإن ما سيودع هو نسخة CONTRIBUTING.md التي كانت موجودة عندما نفذت أمر الإضافة git add آخر مرة، وليس نسخة الملف الظاهرة لديك في مجلد العمل عندما تنفذ أمر الإيداع git commit. فإن عدّلت ملفًا بعد تنفيذ أمر الإضافة، فتحتاج إلى تنفيذه مرة أخرى لتأهيل النسخة الأخيرة من الملف:

$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

الحالة الموجزة

مع كون ناتج أمر الحالة git status شاملًا، إلا أنه كثير الكلام. يتيح جت أيضا خيارًا للحالة الموجزة، لترى تعديلاتك بإيجاز: إذا نفذت git status -s أو git status --short، فسيعطيك الأمر ناتجًا أقصر كثيرا:

$ git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

أمام الملفات الجديدة التي لم تُتعقب علامتا ??. والملفات الجديدة المؤهلة أمامها A (اختصار «أضيف»). والملفات المعدّلة أمامها M (اختصار «معدّل»). وهكذا. ويوجد عمودان في الناتج أمام أسماء الملفات: العمود الأيسر يوضّح حالته في منطقة التأهيل، والعمود الأيمن يوضّح حالته في شجرة العمل. لذا ففي ناتج مثالنا هذا، ملف README معدّل في مجلد العمل ولكنه ليس مؤهلا بعد، ولكن ملف lib/simplegit.rb معدّل ومؤهل. وملف Rakefile معدّل ومؤهل ثم معدّل مرة أخرى، ففيه تعديلات مؤهلة وتعديلات غير مؤهلة.

تجاهل ملفات

سيكون لديك غالبا فئة من الملفات التي لا تريد من جت أن يضيفها آليًا ولا حتى أن يخبرك أنها غير متعقبة. هذه غالبا ملفات موّلدة آليًا مثل ملفات السجلات أو ملفات مبنية. يمكنك في مثل هذه الحالات إنشاء ملف يسمى .gitignore (يبدأ بنقطة، لجعله مخفيا) والذي يسرد أنماط أسماء هذه الملفات ليتجاهلها. هذا مثال على ملف .gitignore :

$ cat .gitignore
*.[oa]
*~

يطلب السطر الأول من جت أن يتجاهل أي ملفات ينتهي اسمها بـ ‪“.o”‬ أو ‪“.a”‬ — ملفات الكائنات وملفات المكتبات المضغوطة التي قد تُنتج أثناء بناء مصدرك البرمجي. ويطلب السطر الثاني من جت أن يتجاهل جميع الملفات التي ينتهي اسمها بعلامة التلدة (~)، التي تستعملها محررات نصوص عديدة مثل Emacs لتمييز الملفات المؤقتة. يمكنك أيضا إضافة مجلد log أو tmp أو pid، أو الوثائق المولدة آليًا، إلخ. إعداد ملف التجاهل .gitignore لمستودعك الجديد قبل الانطلاق في المشروع هو تفكير حسن عموما، لكيلا تودع بالخطأ ملفات يقينا لا تريدها في مستودعك.

إليك قواعد الأنماط التي تستطيع استعمالها في ملف التجاهل:

  • تُهمل الأسطر الفارغة أو الأسطر البادئة بعلامة #.

  • يمكن استعمال أنماط توسيع المسارات (glob) المعتادة (ستُوّضح بالتفصيل)، وستُطبق في جميع مجلدات شجرة العمل.

  • يمكنك بدء الأنماط بفاصلة مائلة (/) لمطابقة الملفات أو المجلدات في المجلد الحالي فقط، وليس أي مجلد فرعي.

  • يمكنك إنهاء الأنماط بفاصلة مائلة (/) لتحديد مجلد.

  • يمكنك نفي نمط ببدئه بعلامة تعجب (!).

تشبه أنماط glob نسخة مُيسَّرة من «التعابير النمطية»، وتستعملها الصدفات. فتُطابق النجمة (*) صفر أو أكثر من المحارف؛ ويُطابق [abc] أي حرف داخل القوسين المربعين (أي a أو b أو c في هذه الحالة)؛ وتُطابق علامة الاستفهام الغربية (?) مِحرَفًا واحدًا؛ ولمطابقة مدًى من المحارف، نكتب أول مِحرف وآخر مِحرف (بترتيبهما في Unicode) داخل قوسين مربعين وبينهما شرطة، فمثلا لمطابقة رقمًا من الأرقام المغربية (من 0 إلى 9) نكتب [0-9]. يمكنك أيضا استخدام نجمتين لمطابقة أي عدد من المجلدات الفرعية، فمثلا يطابق النمط a/**/z كلًا من a/z و a/b/z و a/b/c/z وهكذا.

إليك مثال آخر على ملف .gitignore :

#⭅ تجاهل كل الملفات ذات الامتداد a #‬
*.a

‫#⭅ لكن تعقب lib.a، حتى لو كنت تتجاهل جميع ملفات a بالأعلى #‬
!lib.a

‫#⭅ تجاهل فقط TODO في المجلد الحالي، وليس subdir/TODO مثلا #‬
/TODO

‫#⭅ تجاهل أي مجلد اسمه build وكل شيء داخله #‬
build/

‫#⭅ تجاهل doc/notes.txt ولكن ليس doc/server/arch.txt مثلا #‬
doc/*.txt

‫#⭅ تجاهل جميع pdf في مجلد doc أو أي مجلد فرعي فيه #‬
doc/**/*.pdf

إذا أردت نقطة بداية لمشروعك، فإن جت‌هب يرعى قائمةً شاملة نسبيًا من أمثلة ملفات التجاهل الحسنة لعشرات المشروعات واللغات في https://github.com/github/gitignore.

قد يكون لدى المستودع في الحالات اليسيرة ملف تجاهل واحد في مجلد الجذر، والذي يطبق على المستودع بجميع مجلداته الفرعية. ولكن ممكن كذلك وجود ملفات تجاهل أخرى في مجلدات فرعية. وملفات التجاهل الداخلية هذه لا تطبق قواعدها إلا على الملفات التي في مجلداتها. ومثلا لدى مستودع نواة لينكس ٢٠٦ ملف تجاهل.

يخرج عن نطاق الك‍تاب الغوص في تفاصيل ملفات التجاهل المتعددة؛ انظر man gitignore للتفاصيل.

رؤية تعديلاتك المؤهلة وغير المؤهلة

إذا كنت تجد ناتج أمر الحالة git status شديد الغموض — تريد معرفة ما الذي عدّلته على وجه التحديد، وليس مجرد أسماء الملفات التي تعدّلت — فيمكنك استخدام أمر الفرق git diff. نتناوله بالتفصيل فيما بعد، لكنك في الغالب تستخدمه لإجابة أحد التساؤلين: ما الذي عدّلته ولم تؤهله بعد؟ وما الذي أهّلته وعلى وشك إيداعه؟ وبالرغم من أن أمر الحالة git status يجيبهما إجابةً عامة جدا بسرد أسماء الملفات، إلا أن أمر الفرق git diff يُظهر لك بالتحديد السطور المضافة والمزالة: الرُقعة، إن جاز التعبير.

لنقُل أنك عدّلت ملف README مجددا وأهّلته، ثم عدّلت ملف CONTRIBUTING.md من غير تأهيله. إذا نفذت أمر الحالة، سترى من جديد شيئا مثل هذا:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

لرؤية ما الذي عدّلته ولم تؤهله بعد، اكتب git diff من غير أي معاملات أخرى:

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's

يقارن هذا الأمر بين محتويات مجلد العمل ومنطقة التأهيل، ويُخبرك الناتج بما عدّلته ولم تؤهله بعد.

إذا أردت رؤية ما الذي أهّلته ليكون في الإيداع التالي، يمكنك استخدام git diff --staged. يقارن هذا الأمر بين تعديلاتك المؤهلة وإيداعك الأخير:

$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project

مهمٌ ملاحظة أن git diff وحده لا يُظهر جميع التعديلات التي تمت بعد الإيداع الأخير — إنما التعديلات غير المؤهلة فقط. فإذا أهّلت جميع تعديلاتك، فلن يعطيك git diff أي ناتج.

مثالٌ آخر: إذا أهّلت ملف CONTRIBUTING.md ثم عدّلته، يمكنك استخدام أمر الفرق لمعرفة التعديلات على الملف التي أُهّلِت والتعديلات التي لم تؤهل. فإذا كانت بيئتنا تبدو هكذا:

$ git add CONTRIBUTING.md
$ echo '# test line' >> CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   CONTRIBUTING.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

يمكننا إذًا استخدام git diff لرؤية ما الذي لم يؤهل بعد:

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 643e24f..87f08c8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -119,3 +119,4 @@ at the
 ## Starter Projects

 See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
+# test line

واستخدام git diff --cached لرؤية ما الذي أهّلته حتى الآن (الخياران --staged و --cached مترادفان):

$ git diff --cached
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's
فروقات جت باستخدام أداة خارجية

سنستمر في استخدام أمر الفرق git diff بطرائق متنوعة خلال الك‍تاب. ولكن توجد طريقة أخرى لرؤية هذه الفروقات إذا كنت تفضل برنامج عرض فروقات خارجي أو رسومي. يمكنك باستخدام git difftool بدلا من git diff أن ترى هذه الفروقات في برنامج مثل emerge أو vimdiff أو برامج كثيرة أخرى (بما فيها البرامج التجارية). نفذ git difftool --tool-help لترى ما المتاح على نظامك.

إيداع تعديلاتك

الآن وقد هيّأت منطقة تأهيلك كما تحب، يمكنك أن تودع تعديلاتك. تذكر أنه لن يُحفظ في هذا الإيداع أي شي ما زال غير مؤهل — أيْ أيّ ملفات أنشأتها أو عدّلتها ولم تنفذ git add عليها بعدما عدلتها؛ بل ستبقى ملفات معدلة على القرص. لنقُل أنك عندما نفذت أمر git status رأيت أن كل شيء مؤهل، لذا فأنت الآن مستعد لإيداع تعديلاتك. أسهل طريقة للإيداع هي ك‍تابة git commit:

$ git commit

فعل هذا يفتح محررك المختار.

يعيّن «محررَك المختار» متغيرُ بيئة المحرر، EDITOR، في صدفتك، والذي غالبا يكون vim أو emacs. مع أنك تستطيع جعله أي شيء تريده بالأمر git config --global core.editor كما رأيت في البدء

يُظهر المحرر النصَ التالي (المثال من Vim):

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#	new file:   README
#	modified:   CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C

والتي يترجم أولها إلى: «أدخل رسالة الإيداع لتعديلاتك. الأسطر البادئة بعلامة # ستُهمل، ورسالة فارغة ستلغي الإيداع.»

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

إذا احتجت تذكيرًا أشد تفصيلًا بما عدّلت، يمكنك إمرار الخيار -v لأمر الإيداع، git commit. يضع هذا فروقات تعديلاتك في المحرر، كي ترى بالتحديد ما التعديلات الذاهبة للإيداع.

عندما تحفظ وتغلق المحرر، سيصنع جت إيداعك بالرسالة التي كتبتها (باستثناء الفروقات والتعليقات، أي الأسطر البادئة بعلامة #).

يمكنك عوضًا عن ذلك ك‍تابة رسالة إيداعك في أمر الإيداع نفسه، بالخيار -m، مثل هذا:

$ git commit -m "Story 182: fix benchmarks for speed"
[master 463dc4f] Story 182: fix benchmarks for speed
 2 files changed, 2 insertions(+)
 create mode 100644 README

الآن قد صنعت إيداعك الأول! نرى أن الإيداع أعطاك بعض المعلومات عن نفسه، مثل الفرع الذي أودعت فيه (master)، وبصمة الإيداع (463dc4f)، وعدد الملفات المعدّلة (2 files changed)، وإحصاءات عن السطور المضافة والمزالة في هذا الإيداع (2 insertion(+)).

تذكر أن هذا الإيداع يسجل اللقطة التي أعددتها في منطقة تأهيلك. أيّ شيء عدّلته ولم تؤهله سيظل جالسًا في مجلد العمل وهو معدَّل؛ يمكنك صنع إيداع آخر لإضافته إلى تاريخ مشروعك. في كل مرة تصنع إيداعًا، تسجل من مشروعك لقطة يمكنك إرجاع مشروعك إليها أو المقارنة معها فيما بعد.

تخطي منطقة التأهيل

مع أن منطقة التأهيل مفيدة لدرجة مدهشة في صياغة الإيداعات كما تشاء بالضبط، إلا أنها أحيانا أعقد مما تحتاج في سير عملك. يوفر لك جت اختصارا سهلًا متى أردت تخطي منطقة التأهيل: إضافة الخيار -a إلى أمر git commit تجعل جت يؤهل من نفسه كل ملف متعقب قبل هذا الإيداع، لتتخطى مرحلة الإضافة:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'Add new benchmarks'
[master 83e38c7] Add new benchmarks
 1 file changed, 5 insertions(+), 0 deletions(-)

لاحظ أنك لم تحتجْ إلى تنفيذ git add على ملف CONTRIBUTING.md في هذه الحالة قبل الإيداع، لأن خيار -a يضم جميع الملفات المعدلة. هذا مريح، لكن احذر: قد يضم هذا الخيار تعديلات غير مرغوب فيها.

إزالة ملفات

لإزالة ملف من جت، عليك أن تزيله من ملفاتك المتعقبة (أو بتعبير أدق، من منطقة تأهيلك)، ثم تودع. يفعل أمر الإزالة git rm هذا، وأيضا يزيل الملف من مجلد عملك حتى لا تراه ملفًا غير متعقب في المرة القادمة.

إذا أزلت الملف من مجلد عملك فقط، سيظهر تحت عنوان ‪“Changes not staged for commit”‬ («تعديلات غير مؤهلة للإيداع») في ناتج أمر الحالة:

$ rm PROJECTS.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    PROJECTS.md

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

عندئذٍ تنفيذك أمر git rm يؤهل إزالة الملف:

$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    PROJECTS.md

عندما تودع في المرة القادمة ستجد أن الملف قد ذهب ولم يعد متعقبًا. وإذا كنت قد عدّلت الملف أو كنت قد أضفته بالفعل إلى منطقة التأهيل، فعليك فرض الإزالة بالخيار -f. هذه ميزة أمان لكيلا تزيل بالخطأ بيانات لم تسجلها بعد في لقطة ولا يمكن استردادها من جت.

أمرٌ آخر مفيد قد تود فعله هو إبقاء الملف في شجرة عملك لكن إزالته من منطقة تأهيلك. بلفظ آخر، تريد أن ينسى جت وجوده ولا يتعقبه ولكن يبقيه لك على قرصك. هذا مفيد خصوصا إن نسيت إضافة شيء إلى ملف التجاهل .gitignore ثم أهّلته بالخطأ، مثل ملف سجل كبير أو مجموعة من الملفات المبنية. استعمل الخيار --cached لهذا:

$ git rm --cached README

يمكنك إعطاء الأمر أسماء ملفات أو مجلدات أو أنماط توسيع المسارات (glob). يعني هذا أن بإمكانك فعل أشياء مثل:

$ git rm log/\*.log

لاحظ الشرطة المائلة الخلفية (\) قبل النجمة *؛ هذا ضروري، لأن جت يقوم بنفسه بتوسيع أسماء الملفات بعد أن تقوم صدفتك بتوسيعها. فبغير الشرطة المائلة الخلفية، ستوسع الصدفة أسماء الملفات قبل أن يراها جت. يحذف هذا الأمر جميع الملفات ذات الامتداد .log في مجلد log/. أو يمكنك فعل شيء مثل هذا:

$ git rm \*~

يحذف هذا الأمر جميع الملفات المنتهي اسمها بعلامة التلدة (~).

نقل ملفات

لا يتعقب جت حركة الملفات تعقبًا صريحًا، خلافا لكثير من أنظمة إدارة النسخ الأخرى. فإذا غيّرت اسم ملف في جت، لا يخزن جت بيانات وصفية تخبره أنك غيرته. لكن جت ذكي جدا في تخمين ذلك وهو أمام الأمر الواقع — سنتعامل مع اكتشاف نقل الملفات بعد قليل.

لذا فقد تجد أنه من المحيّر وجود أمر «نقل» (mv) في جت. فإذا أردت تغيير اسم ملف في جت، يمكنك طلبه هكذا:

$ git mv file_from file_to

وسيعمل كما ينبغي. وفي الحقيقة، إذا نفذت أم‍را مثل هذا، ونظرت إلى الحالة، سترى أن جت يعتبره تغيير اسم ملف:

$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

ولكن هذا مكافئ لتنفيذ شيء مثل هذا:

$ mv README.md README
$ git rm README.md
$ git add README

يخمن جت في سِرِّه أن الاسم قد تغير، لذا فلا يهم إن غيّرت اسمه بهذه الطريقة أو عبر نظام التشغيل أو مدير الملفات (مثلا بأمر النظام mv). الفرق الحقيقي الوحيد هو أن git mv أمر واحد وليس ثلاثة؛ إنه وسيلة راحة. والأهم أنك تستطيع استخدام أي أداة تريدها لتغيير أسماء الملفات، ثم تتعامل مع الإضافة والإزالة في جت فيما بعد، قبل الإيداع.

رؤية تاريخ الإيداعات

بعدما صنعت عددًا من الإيداعات، أو استنسخت مستودعًا ذا تاريخ من الإيداعات بالفعل، قد تود الالتفات إلى الماضي ورؤية ماذا حدث. أسهل وأقوى أداة لفعل هذا هي أمر السجل، git log:

تستعمل هذه الأمثلة مشروعًا صغيرا جدا يسمى ‪“simplegit”‬. للحصول على المشروع، نفذ:

$ git clone https://github.com/schacon/simplegit-progit

عندما تنفذ git log داخل هذا المشروع، ترى شيئا مثل هذا:

$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    Change version number

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    Remove unnecessary test

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    Initial commit

عندما تنادي أمر السجل بلا مُعامِلات، أيْ git log فقط، فإنه افتراضيًا يسرد لك الإيداعات التي في هذا المستودع بترتيب زمني عكسي؛ أيْ أن الإيداع الأحدث يظهر أولًا. يسرد هذا الأمر كما ترى كل إيداع مع بصمته واسم مؤلفه وبريده وتاريخ الإيداع ورسالته.

يتيح أمر السجل عددًا عظيمًا متنوعًا من الخيارات لتُظهر بالضبط ما تريد. سنعرض لك هنا بعضًا من أشهرها.

واحد من أكثر الخيارات إفادةً هو -p أو --patch («رُقعة»)، والذي يظهر لك الفرق (أي الرقعة) الذي أتى به كل إيداع. يمكنك أيضا تقييد عدد السجلات المعروضة، مثلا بالخيار -2 لإظهار آخر بيانَيْن فقط.

$ git log -p -2
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    Change version number

diff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
 spec = Gem::Specification.new do |s|
     s.platform  =   Gem::Platform::RUBY
     s.name      =   "simplegit"
-    s.version   =   "0.1.0"
+    s.version   =   "0.1.1"
     s.author    =   "Scott Chacon"
     s.email     =   "schacon@gee-mail.com"
     s.summary   =   "A simple gem for using Git in Ruby code."

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    Remove unnecessary test

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index a0a60ae..47c6340 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -18,8 +18,3 @@ class SimpleGit
     end

 end
-
-if $0 == __FILE__
-  git = SimpleGit.new
-  puts git.show
-end

يعرض هذا الخيار المعلومات نفسها أيضا ولكن مع إتباع كل بيان بالفروقات. هذا مفيد جدا لمراجعة الأكواد (code review) أو للنظر السريع فيما حدث في سلسلة من الإيداعات التي أضافها زميل. ولدى أمر السجل كذلك عددًا من خيارات التلخيص. فمثلا إذا أردت رؤية إحصاءات مختصرة عن كل إيداع، جرّب خيار الإحصاء --stat:

$ git log --stat
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    Change version number

 Rakefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    Remove unnecessary test

 lib/simplegit.rb | 5 -----
 1 file changed, 5 deletions(-)

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    Initial commit

 README           |  6 ++++++
 Rakefile         | 23 +++++++++++++++++++++++
 lib/simplegit.rb | 25 +++++++++++++++++++++++++
 3 files changed, 54 insertions(+)

فخيار --stat كما ترى يطبع لك تحت بيان كل إيداع قائمة بالملفات المعدلة وعددها وعدد السطور المضافة والمزالة في هذه الملفات. ثم يضع تلخيصًا لهذه المعلومات في النهاية.

وخيار آخر مفيد جدًا هو --pretty («جميل»). والذي يغيّر ناتج السجل إلى صيغ أخرى غير الصيغة المبدئية. تأتي مع جت بعض القيم التي يمكن استعمالها مع هذا الخيار. قيمة oneline («سطر واحد») تطبع كل إيداع على سطر وحيد، والذي يفيد عندما تكون ناظرًا إلى إيداعات كثيرة. وكذلك، القيم short («قصير») و full («كامل») و fuller («أكمل») تُظهر لك ناتجًا مثل المبدئي مع زيادة أو نقصان في بعض المعلومات.

$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 Change version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Remove unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 Initial commit

القيمة الأكثر إمتاعًا هي format («صياغة»)، والتي تتيح لك تحديد صيغة ناتج السجل التي تفضلها. هذا مفيد خصوصًا عندما تقوم بتوليد ناتج لكي يقرؤه ويحلله برنامج أو بُريمج (script) — فلأنك تحدد الصيغة بصراحة ووضوح، فإنك تطمئن أنها لن تتغيّر مع تحديث جت.

$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : Change version number
085bb3b - Scott Chacon, 6 years ago : Remove unnecessary test
a11bef0 - Scott Chacon, 6 years ago : Initial commit

يسرد متغيرات مفيدة لصياغة السجلات باستخدام git log --pretty=format بعض المتغيرات المفيدة التي تفهمها format:

جدول ١. متغيرات مفيدة لصياغة السجلات باستخدام git log --pretty=format
المتغير وصف الناتج

%H

بصمة الإيداع

%h

بصمة الإيداع المختصرة

%T

بصمة الشجرة

%t

بصمة الشجرة المختصرة

%P

بصمات الآباء

%p

بصمات الآباء المختصرة

%an

اسم المؤلف

%ae

بريد المؤلف

%ad

تاريخ التأليف (الصيغة تتبع --date=option)

%ar

تاريخ التأليف، نسبي

%cn

اسم المودِع

%ce

بريد المودِع

%cd

تاريخ الإيداع

%cr

تاريخ الإيداع، نسبي

%s

الموضوع

ربما تتساءل عن الفرق بين المؤلف والمودِع. المؤلف هو من كتب العمل في الأصل، بينما المودِع هو من طبّق العمل في النهاية. فمثلا إذا أرسلت رقعة إلى مشروع، وطبّقها أحد الأعضاء الأساسيين، فيجب الاعتراف بالفضل لكليكما — أنت مؤلفًا، والعضو الأساسي مودِعًا. سنتناول هذا التمييز بالتفصيل في جت الموزع.

القيمتان oneline و format مفيدتان خصوصًا مع خيار آخر لأمر السجل يسمى --graph («رسم»). يضيف هذا الخيار رسمًا لطيفًا بالمحارف لإظهار تاريخ التفريع والدمج.

$ git log --pretty=format:"%h %s" --graph
* 2d3acf9 Ignore errors from SIGCHLD on trap
*  5e3ee11 Merge branch 'master' of https://github.com/dustin/grit.git
|\
| * 420eac9 Add method for getting the current branch
* | 30e367c Timeout code and tests
* | 5a09431 Add timeout protection to grit
* | e1193f8 Support for heads with slashes in them
|/
* d6016bc Require time for xmlschema
*  11d191e Merge branch 'defunkt' into local

سيصير هذا النوع من الناتج ممتعًا أكثر أثناء تناولنا التفريع والدمج في الباب التالي.

هذه فقط بعض خيارات تنسيق الناتج اليسيرة المتاحة في git log — متاح عدد أكبر من ذلك كثيرا. يسرد خيارات شائعة لأمر السجل الخيارات التي تناولناها حتى الآن، وكذلك بعض خيارات التنسيق الشائعة الأخرى التي قد تفيد، إضافةً إلى كيفية تعديل ناتج أمر السجل.

جدول ٢. خيارات شائعة لأمر السجل
الخيار الوصف

-p

أظهر الرقعة التي أتى بها كل إيداع.

--stat

أظهر إحصاءات الملفات المعدّلة في كل إيداع.

--shortstat

اعرض فقط سطر التعديلات/الإضافات/الإزالات من أمر --stat.

--name-only

اسرد أسماء الملفات المعدلة بعد كل إيداع.

--name-status

اسرد أسماء الملفات مرفقة بحالتها: معدّل/مضاف/مزال.

--abbrev-commit

أظهر فقط الحروف القليلة الأولى من البصمة، بدلًا من الأربعين جميعًا.

--relative-date

اعرض التاريخ بصيغة نسبية (مثلا ‪“2 weeks ago”‬) بدلا من صيغة التاريخ الكاملة.

--graph

اعرض رسمًا بالمحارف لتاريخ التفريع والدمج بجانب ناتج السجل.

--pretty

اعرض الإيداعات بصيغة أخرى. قيم الخيار المتاحة تشمل oneline و short و full و fuller و format (والتي تتيح لك تحديد صياغتك المخصوصة).

--oneline

اختصار لاستخدام --pretty=oneline --abbrev-commit معًا.

تقييد ناتج السجل

إضافةً إلى خيارات صياغة الناتج، يتيح أمر السجل عددًا من خيارات تقييد الناتج؛ أيْ خيارات تتيح لك إظهار جزء من الإيداعات فقط. لقد رأيت أحد هذه الخيارات بالفعل — خيار -2 الذي يُظهر آخر إيداعين فقط. الحقيقة أن استخدام -<ن>، حيث ن هو أي عدد صحيح موجب، يُظهر لك آخر ن إيداعًا. لن تستخدم هذا كثيرا في الواقع، لأن جت بطبيعته يمرر الناتج كله إلى برنامج عرض (‪“pager”‬ مثل less) حتى ترى ناتج السجل صفحةً صفحة.

لكن خيارات التقييد بالزمن مثل --since («منذ») و --until («حتى») مفيدة جدا. مثلا، هذا الأمر يسرد الإيداعات التي تمت خلال الأسبوعين السابقين:

$ git log --since=2.weeks

يعمل هذا الأمر مع العديد من الصيغ — يمكنك تحديد تاريخ محدد مثل "2008-01-15" أو تاريخ نسبي مثل "2 years 1 day 3 minutes ago".

يمكنك أيضا سرد الإيداعات المطابقة لمعايير بحث معينة. مثلا خيار --author يتيح لك سرد إيداعات مؤلف معين فقط، و --grep يتيح لك البحث عن كلمات معينة في رسائل الإيداعات.

يمكنك استخدام --author أو --grep أكثر من مرة في المرة، والذي يسرد الإيداعات التي توافق أي نمط --author معطى وتوافق أي نمط --grep معطى؛ ولكن إضافة خيار --all-match يقيّد الناتج إلى الإيداعات الموافقة لجميع أنماط --grep.

مصفاة مفيدة جدا أخرى هي خيار -S (والمعروف بالاسم الدارج: خيار «فأس» جت)، والذي يأخذ سلسلة نصية ولا يظهر إلا الإيداعات التي عدّلت عدد تواجداتها. مثلا، إذا أردت إظهار آخر إيداع أضاف أو أزال إشارة إلى دالة معينة، يمكنك تنفيذ:

$ git log -S function_name

آخر خيار تصفية مفيد جدا لأمر السجل هو إعطاؤه مسار. فإذا أعطيته مجلدًا أو ملفًا، فإنه يقيّد ناتج السجل إلى الإيداعات التي عدّلت هذه الملفات. يكون هذا دائما آخر خيار وفي الغالب يُسبق بشرطتين (--) لفصل المسارات عن الخيارات:

$ git log -- path/to/file

نسرد في خيارات تقييد ناتج أمر السجل هذه الخيارات وبعض الخيارات الأخرى حتى تكون مرجعًا لك.

جدول ٣. خيارات تقييد ناتج أمر السجل
الخيار الوصف

-<ن>

أظهر فقط آخر ن إيداعًا.

--since أو --after

قيّد الناتج إلى الإيداعات التي تمت بعد التاريخ المعطى.

--until أو --before

قيّد الناتج إلى الإيداعات التي تمت قبل التاريخ المعطى.

--author

لا تظهر إلا الإيداعات التي يطابق اسم مؤلفها السلسلة النصية المعطاة.

--committer

لا تظهر إلا الإيداعات التي يطابق اسم مودِعها السلسلة النصية المعطاة.

--grep

لا تظهر إلا الإيداعات التي تشتمل رسالتها على السلسلة النصية المعطاة.

-S

لا تظهر إلا الإيداعات التي أضافت أو أزالت سطورًا برمجية فيها السلسلة النصية المعطاة.

مثلا، إذا أردت رؤية أيِّ الإيداعات عدّلت ملفات الاختبارات في مصدر جت والتي أودعها Junio Hamano في شهر أكتوبر عام ٢٠٠٨، وليست إيداعات دمج، يمكنك فعل شيء مثل هذا:

$ git log --pretty="%h - %s" --author='Junio C Hamano' --since="2008-10-01" \
   --before="2008-11-01" --no-merges -- t/
5610e3b - Fix testcase failure when extended attributes are in use
acd3b9e - Enhance hold_lock_file_for_{update,append}() API
f563754 - demonstrate breakage of detached checkout with symbolic link HEAD
d1a43f2 - reset --hard/read-tree --reset -u: remove unmerged new paths
51a94af - Fix "checkout --track -b newbranch" on detached HEAD
b0ad11e - pull: allow "git pull origin $something:$current_branch" into an unborn branch

حوالي أربعين ألف إيداع في تاريخ مصدر جت، وهذا الأمر لا يُظهر منها إلا الإيداعات الستة المطابقة لتلك المعايير.

منع عرض إيداعات الدمج

حسب أسلوب سير العمل في مستودعك، قد يكون عدد ضخم من الإيداعات في تاريخ سجلك مجرد إيداعات دمج، وهي لا تفيد كثيرًا. لمنع عرضها وإزحامها تاريخ سجلك، أضف إلى أمر السجل خيار --no-merges («لا دمج»).

التراجع عن الأفعال

قد تحتاج في أي مرحلة إلى التراجع عن فعلٍ ما. سنرى هنا بعض الأدوات الأساسية للتراجع عن تعديلاتك. كن حذرًا، لأن بعض هذه التراجعات لا يمكن التراجع عنها فيما بعد. هذه من المناطق القليلة في جت التي يمكنك أن تفقد فيها شيئا من عملك إذا فعلت شيئا خطأ.

واحد من أشهر التراجعات هو عندما تودع قبل الأوان وتنسى إضافة ملفات أو تخطئ في رسالة إيداعك. إذا أردت إعادة هذا الإيداع، فقم بالتعديلات التي نسيتها، وأهّلها، ثم أودع مجددا مع خيار التصحيح --amend:

$ git commit --amend

يأخذ هذا الأمر منطقة تأهيلك ويستخدمها للإيداع. وإذا لم تقم بأي تعديلات منذ إيداعك الأخير (مثلا نفذت هذا الأمر مباشرةً بعد إيداعك السابق)، فإن لقطتك ستتطابق تمامًا بلا اختلاف، ولن تتغيّر سوى رسالة الإيداع.

سيظهر لك محرر رسالة الإيداع، ولكنك ستجد فيه رسالة الإيداع السابقة في انتظارك لتعدّلها إن شئت أو تغيّرها تمامًا.

مثلا، إذا أودعت ثم أدركت أنك نسيت تأهيل تعديلات على ملف تريدها في هذا الإيداع، يمكنك فعل شيء مثل هذا:

$ git commit -m 'Initial commit'
$ git add forgotten_file
$ git commit --amend

ستجد في النهاية إيداعًا واحدًا؛ فالإيداع الثاني يحل محل الأول.

مهمٌ فهم أنك عندما تصحح إيداعك الأخير، فإنك لا تصلحه ولكن تستبدله برُمّتِه وتضع مكانه إيداعًا جديدًا محسَّنًا وتزيح القديم عن الطريق. في الحقيقة، هذا كأن الإيداع السابق لم يحدث أصلًا، ولن يظهر في تاريخ مستودعك.

الفائدة الواضحة لتصحيح الإيداعات هو التحسينات الطفيفة للإيداع الأخير، بغير إزحام تاريخ مستودعك برسائل إيداعات من نوعية «عذرا، نسيت إضافة ملف» أو «سحقا، خطأ مطبعي في الإيداع السابق، أصلحته».

لا تصحح إلا الإيدعات التي لا تزال محلية ولم تُدفع بعد إلى أي مكان آخر. فتصحيح إيداع قد دُفع بالفعل ثم فرض الدفع (git push --force) سيسبب مشاكل للمتعاونين معك. لمعرفة ما سيحدث إن فعلت هذا وكيف تتعافي إذا كنت الطرف المتلقي، اقرأ محذورات إعادة التأسيس.

إلغاء تأهيل ملف مؤهل

سيوضح الفصلان التاليان كيف تتعامل مع التعديلات في منطقة تأهيلك ومجلد عملك. الجميل أن الأمر الذي تستخدمه لمعرفة حالة إحدى هاتين المنطقتين يذكّرك أيضا بكيفية التراجع عن تعديلاتهما. لنقُل مثلا أنك عدّلت ملفين وأردت إيداع كلٍ منهما في إيداع منفصل، ولكنك كتبت خطأً git add * فأهّلت كليهما. كيف يمكنك إلغاء تأهيل أحدهما؟ أمر الحالة يذكّرك:

$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README
    modified:   CONTRIBUTING.md

مباشرةً تحت ‪“Changes to be committed”‬ («تعديلات ستُودَع») تجده يقول استخدم git reset HEAD <ملفات> لإلغاء التأهيل. فلنعمل بهذه النصيحة إذًا، لإلغاء تأهيل ملف CONTRIBUTING.md:

$ git reset HEAD CONTRIBUTING.md
Unstaged changes after reset:
M	CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

هذا الأمر غريب قليلًا، لكنه يعمل. ملف CONTRIBUTING.md معدّل لكنه عاد من جديد غير مؤهل.

صدقًا إن git reset أمر خطير، خصوصا مع الخيار --hard. مع ذلك، فإن الملف الذي في مجلد عملك لم يُمس في الموقف الموضح بالأعلى، لذا فهذا الأمر آمن نسبيا في مثل هذا الموقف.

هذا الأمر السحري هو كل ما تحتاج معرفته الآن عن أمر الإرجاع git reset. سنغوص في Reset Demystified في تفاصيل أعمق كثيرا عن أمر الإرجاع وماذا يفعل وكيف تتقنه لتفعل أفعالا شيقة وممتعة جدا.

إعادة ملف معدل إلى حالته قبل التعديل

ماذا لو أدركت أنك لم تعد تريد تعديل ملف CONTRIBUTING.md من الأساس؟ كيف يمكنك إرجاعه إلى حالته عند الإيداع الأخير (أو الاستنساخ الأول، أو كيفما حصلت عليه في مجلد عملك)؟ لحسن الحظ، يخبرك أمر الحالة بهذا أيضا. في ناتج المثال الأخير، كان جزء التعديلات غير المؤهلة هكذا:

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

فيخبرك أن تستخدم الأمر git checkout -- <ملفات> لتجاهل التعديلات التي في مجلد عملك. لنفعل ما يخبرنا به:

$ git checkout -- CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

كما ترى، أُلغيت التعديلات.

من المهم جدا فهم أن git checkout -- <ملفات> أمر خطير؛ أي تعديلات محلية قمت بها على هذا الملف قد ضاعت، فقد أزال جت للتو هذا الملف ووضع مكانه آخر نسخة مؤهلة أو مودعة منه. إياك أبدا أن تستعمل هذا الأمر، إلا أن تكون واعيا أشد الوعي أنك لا تريد هذه التعديلات المحلية غير المحفوظة.

إذا أردت الإبقاء على تعديلاتك على هذا الملف لكنك لا تزال تريد إزاحته جانبًا الآن، فسنشرح التخبئة والتفريع في التفريع في جت؛ هاتان الطريقتان في العموم أفضل.

تذكر أن أي شيء تودعه في جت يمكن شِبه دائمًا استعادته. حتى الإيداعات في الفروع المحذوفة أو الإيداعات المبدلة بخيار التصحيح (--amend) يمكن استعادتها (انظر استرجاع البيانات لاستعادة البيانات). مع ذلك، أي شيء تفقده لم يكن مودعًا، صعب أن تراه مرة أخرى.

التراجع بأمر الاستعادة git restore

أضافت النسخة 2.23.0 من جت أمرًا جديدًا: git restore. هذا في الأصل بديل لأمر الإرجاع git reset الذي ناقشناه للتو. ابتداءً من النسخة 2.23.0 من جت، سيستخدم جت أمر الاستعادة git restore بدلا من أمر الإرجاع git reset في الكثير من عمليات التراجع.

لنرتد على آثارنا قَصصًا ونعيد الكرّة ونتراجع بأمر الاستعادة git restore بدلًا من أمر الإرجاع git reset.

إلغاء تأهيل ملف مؤهل بأمر الاستعادة

سيوضح الفصلان التاليان كيف تتعامل مع التعديلات في منطقة تأهيلك ومجلد عملك بأمر الاستعادة git restore. الجميل أن الأمر الذي تستخدمه لمعرفة حالة إحدى هاتين المنطقتين يذكّرك أيضا بكيفية التراجع عن تعديلاتهما. لنقُل مثلا أنك عدّلت ملفين وأردت إيداع كلٍ منهما في إيداع منفصل، ولكنك كتبت خطأً git add * فأهّلت كليهما. كيف يمكنك إلغاء تأهيل أحدهما؟ أمر الحالة يذكّرك:

$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   CONTRIBUTING.md
	renamed:    README.md -> README

مباشرةً تحت ‪“Changes to be committed”‬ («تعديلات ستُودَع») تجده يقول استخدم git restore --staged <ملفات> لإلغاء التأهيل. فلنعمل بهذه النصيحة إذًا، لإلغاء تأهيل ملف CONTRIBUTING.md:

$ git restore --staged CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    README.md -> README

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:   CONTRIBUTING.md

ملف CONTRIBUTING.md معدّل لكنه عاد من جديد غير مؤهل.

إعادة ملف معدل إلى حالته قبل التعديل بأمر الاستعادة

ماذا لو أدركت أنك لم تعد تريد تعديل ملف CONTRIBUTING.md من الأساس؟ كيف يمكنك إرجاعه إلى حالته عند الإيداع الأخير (أو الاستنساخ الأول، أو كيفما حصلت عليه في مجلد عملك)؟ لحسن الحظ، يخبرك أمر الحالة بهذا أيضا. في ناتج المثال الأخير، كان جزء التعديلات غير المؤهلة هكذا:

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:   CONTRIBUTING.md

فيخبرك أن تستخدم الأمر git restore <ملفات> لتجاهل التعديلات التي في مجلد عملك. لنفعل ما يخبرنا به:

$ git restore CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    README.md -> README

من المهم جدا فهم أن git restore <ملفات> أمر خطير؛ أي تعديلات محلية قمت بها على هذا الملف قد ضاعت، فقد أزال جت للتو هذا الملف ووضع مكانه آخر نسخة مؤهلة أو مودعة منه. إياك أبدا أن تستعمل هذا الأمر، إلا أن تكون واعيا أشد الوعي أنك لا تريد هذه التعديلات المحلية غير المحفوظة.

التعامل مع المستودعات البعيدة

حتى تستطيع التعاون في أي مشروع يستخدم جت، تحتاج إلى معرفة كيف تدير مستودعاتك البعيدة. المستودعات البعيدة هي نُسخ من مشروعك، وهذه النسخ مستضافة على الإنترنت أو على شبكة داخلية. يمكن أن يكون لديك عددًا منها، وكل واحد منها غالبًا يسمح لك إما بالقراءة فحسب («القراءة فقط»)، وإما بالتحرير كذلك («القراءة والك‍تابة»). والتعاون مع الآخرين يشمل إدارة هذه المستودعات البعيدة ودفع البيانات إليها وجذبها منها عندما تحتاج إلى مشاركة العمل. وإدارة المستودعات البعيدة تشمل معرفة كيف تضيفها في مستودعك وكيف تزيلها إن لم تعد صالحة وكيف تدير العديد من الفروع البعيدة وكيف تجعل الفروع البعيدة متعقَّبة أو غير متعقَّبة، وغير ذلك. سنتناول في هذا الفصل بعضًا من مهارات الإدارة هذه.

المستودعات البعيدة قد تكون على جهازك المحلي

من الممكن جدا أن تعمل مع مستودع «بعيد» (‪“remote”‬)، ولكنه في الحقيقة على الجهاز الذي تستخدمه نفسه. كلمة «بعيد» لا تعني بالضرورة أن المستودع في مكانٍ ما آخر على الشبكة أو الإنترنت، ولكنها تعني فقط أنه في مكان آخر. فالعمل مع مستودع بعيد مثل هذا ما زال يحتاج جميع عمليات الدفع والجذب والاستحضار المعتادة مثل أي مستودع بعيد آخر.

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

لسرد المستودعات البعيدة التي هيأتها، استخدم أمر البعيد git remote. فإنه يُظهر لك الاسم المختصر لكل بعيد في مستودعك. وإذا استنسخت مستودعا، فإنك على الأقل سترى origin («الأصل»)، وهو الاسم الذي يعطيه جت للمستودع الذي استنسخت منه:

$ git clone https://github.com/schacon/ticgit
Cloning into 'ticgit'...
remote: Reusing existing pack: 1857, done.
remote: Total 1857 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1857/1857), 374.35 KiB | 268.00 KiB/s, done.
Resolving deltas: 100% (772/772), done.
Checking connectivity... done.
$ cd ticgit
$ git remote
origin

يمكنك أيضا استخدام الخيار -v («إطناب»)، والذي يُظهر لك الروابط التي خزنها جت للأسماء المختصرة للمستودعات البعيدة ليستعملها لقراءة ذلك المستودع البعيد ولتحريره:

$ git remote -v
origin	https://github.com/schacon/ticgit (fetch)
origin	https://github.com/schacon/ticgit (push)

إذا كان لديك أكثر من مستودع بعيد واحد، فإن هذا الأمر سيسردهم جميعًا. مثلا، قد يبدو مستودع مرتبط بعدد من المستودعات البعيدة للعمل مع رهط من المتعاونين هكذا:

$ cd grit
$ git remote -v
bakkdoor  https://github.com/bakkdoor/grit (fetch)
bakkdoor  https://github.com/bakkdoor/grit (push)
cho45     https://github.com/cho45/grit (fetch)
cho45     https://github.com/cho45/grit (push)
defunkt   https://github.com/defunkt/grit (fetch)
defunkt   https://github.com/defunkt/grit (push)
koke      git://github.com/koke/grit.git (fetch)
koke      git://github.com/koke/grit.git (push)
origin    git@github.com:mojombo/grit.git (fetch)
origin    git@github.com:mojombo/grit.git (push)

هذا يعني أننا نستطيع جذب المساهمات من أيٍّ من هؤلاء المستخدمين بسهولة. وقد يكون لدينا إذن الدفع إلى واحد أو أكثر منهم، ولكن لا نستطيع معرفة هذا من هنا.

لاحظ أن هذه المستودعات البعيدة تستخدم موافيق (بروتوكولات) متنوعة؛ سنتحدث عن هذا في تثبيت جت على خادوم.

إضافة مستودعات بعيدة

ذكرنا أن أمر الاستنساخ git clone يضيف لك من تلقاء نفسه الأصل البعيد origin، ورأيت مثالين على ذلك. إليك الآن معرفة كيف تضيف مستودعا بعيدا بأمر صريح. لإضافة مستودع جت بعيد جديد وإعطائه اسمًا مختصرًا للإشارة إليه به بسهولة فيما بعد، نفذ git remote add <shortname> <url>، أي الاسم المختصر ثم الرابط:

$ git remote
origin
$ git remote add pb https://github.com/paulboone/ticgit
$ git remote -v
origin	https://github.com/schacon/ticgit (fetch)
origin	https://github.com/schacon/ticgit (push)
pb	https://github.com/paulboone/ticgit (fetch)
pb	https://github.com/paulboone/ticgit (push)

يمكنك الآن استخدام الاسم pb في سطر الأوامر، بدلا من الرابط بكامله. مثلا إذا أردت استحضار جميع المعلومات التي لدي پول ولكن ليست لديك في مستودعك بعد، يمكنك استخدام أمر الاستحضار معه، أي git fetch pb:

$ git fetch pb
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 43 (delta 10), reused 31 (delta 5)
Unpacking objects: 100% (43/43), done.
From https://github.com/paulboone/ticgit
 * [new branch]      master     -> pb/master
 * [new branch]      ticgit     -> pb/ticgit

الآن صار فرع master من مستودع پول متاحا محليًا بالاسم pb/master؛ يمكنك دمجه في أحد فروعك، أو سحب إيداعه الأخير إلى فرع محلي إذا أردت تفقّده. سنتناول ما هي الفروع وكيف نستعملها بتفصيل عميق في التفريع في جت.

الاستحضار والجذب من مستودعاتك البعيدة

كما رأيت للتو، للحصول على بيانات من مستودعاتك البعيدة، يمكنك تنفيذ:

$ git fetch <البعيد>

يذهب هذا الأمر إلى المستودع البعيد ويجذب منه كل البيانات التي لديه وليست لديك بعد. بعد أن تفعل هذا، ستجد لديك إشارات لجميع الفروع التي لدى هذا البعيد، فيمكنك دمجها أو فحصها في أي وقت.

إذا استنسخت مستودعًا، فإن أمر الاستنساخ يضيف آليًّا هذا المستودع البعيد بالاسم ‪“origin”‬. لذا فإن git fetch origin يستحضر أي عمل قد دُفع إلى هذا المستودع بعدما استنسخته (أو استحضرت منه) آخر مرة. مهمٌ ملاحظة أن أمر الاستحضار ينزّل فقط البيانات إلى مستودعك المحلي؛ ولكنه لا يدمجها مع عملك ولا يعدّل أي شيء تعمل عليه. عليك دمجها يدويًا مع عملك عندما تكون مستعدًا لذلك.

إذا كان الفرع الحالي مضبوطًا ليتعقب فرعًا بعيدًا (انظر الفصل التالي و الباب الثالث: التفريع في جت للمزيد من المعلومات)، فيمكنك استخدام أمر الجذب git pull ليستحضر آليًّا هذا الفرع البعيد ثم يدمجه في الفرع الحالي. هذا قد يكون أسهل أو أريح لك. وأمر الاستنساخ بطبيعة الحال يضبط لك آليًّا الفرع المبدئي المحلي ليتعقب الفرع المبدئي البعيد (master أو main أو أيًّا كان اسمه) في المستودع الذي استنسخت منه. يستحضر أمر الجذب البيانات من المستودع الذي استنسخت منه في الأصل عادةً ثم يحاول دمجها آليًّا في الفرع الذي تعمل فيه حاليا.

ابتداءً من النسخة 2.27 من جت، سيحذرك أمر الجذب git pull إن لم يكن متغير pull.rebase مضبوطًا. وسيبقى يحذرك حتى تعيّن له قيمة.

إن أردت سلوك جت المبدئي (التسريع متى أمكن، وإلا فإنشاء إيداع دمج)، فنفذ:
git config --global pull.rebase "false"

وإذا أردت إعادة التأسيس عند الجذب، فنفذ:
git config --global pull.rebase "true"

الدفع إلى مستودعاتك البعيدة

عندما يكون مشروعك في مرحلة تود مشاركتها، عليك دفعه إلى المنبع. الأمر الذي يفعل هذا يسير: git push <remote> <branch>، أي اسم المستودع البعيد ثم الفرع: فإذا أردت دفع فرع master الخاص بك إلى المستودع الأصل origin (غالبا يضبط لك الاستنساخ هذين الاسمين آليًّا)، فتنفيذ هذا الأمر يدفع أي إيداعات صنعتها إلى الخادوم:

$ git push origin master

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

فحص مستودع بعيد

إذا أردت رؤية معلومات أكثر عن بعيد معين، فاستخدم الأمر git remote show <البعيد>. إذا نفذت هذا الأمر مع اسم مختصر معين، مثل origin، فسترى شيئا مثل هذا:

$ git remote show origin
* remote origin
  Fetch URL: https://github.com/schacon/ticgit
  Push  URL: https://github.com/schacon/ticgit
  HEAD branch: master
  Remote branches:
    master                               tracked
    dev-branch                           tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

إنه يسرد لك رابط المستودع البعيد إضافةً إلى معلومات تعقب الفروع. وللإفادة يخبرك كذلك بأنك إذا كنت في فرع master واستخدمت أمر الجذب git pull فإنه تلقائيا سيدمج فرع master البعيد في الفرع المحلي بعد استحضاره. ويسرد لك أيضا جميع الإشارت البعيدة التي جذبها إليك.

هذا مثال يسير غالبا ستقابله. ولكن عندما تستخدم جت بكثرة، فسيعطيك git remote show معلومات أكثر كثيرًا:

$ git remote show origin
* remote origin
  URL: https://github.com/my-org/complex-project
  Fetch URL: https://github.com/my-org/complex-project
  Push  URL: https://github.com/my-org/complex-project
  HEAD branch: master
  Remote branches:
    master                           tracked
    dev-branch                       tracked
    markdown-strip                   tracked
    issue-43                         new (next fetch will store in remotes/origin)
    issue-45                         new (next fetch will store in remotes/origin)
    refs/remotes/origin/issue-11     stale (use 'git remote prune' to remove)
  Local branches configured for 'git pull':
    dev-branch merges with remote dev-branch
    master     merges with remote master
  Local refs configured for 'git push':
    dev-branch                     pushes to dev-branch                     (up to date)
    markdown-strip                 pushes to markdown-strip                 (up to date)
    master                         pushes to master                         (up to date)

يُظهر لك هذا الأمر ما الفرع الذي يجذب جت إليه تلقائيا عندما تنفذ git push وأنت في فرع معين. ويُظهر لك كذلك ما فروع المستودع البعيد التي ليست لديك بعد، وما الفروع البعيدة التي لديك وحُذفت من البعيد، وما الفروع المحلية التي يمكن الدمج تلقائيا في فروعها المتعقِبة للبعيد عندما تنفذ git pull.

تغيير اسم بعيد أو حذفه

يمكنك استعمال git remote rename لتغيير الاسم المختصر لمستودع بعيد. مثلا، لتغيير اسم pb إلى paul، نفّذ:

$ git remote rename pb paul
$ git remote
origin
paul

مهم ملاحظة أن هذا يغيّر أسماء فروعك المتعقِبة للبعيد أيضا. فالذي كان يسمى pb/master صار paul/master.

وإذا أردت حذف بعيد لسبب ما — نقلت المستودع، أو لم تعد تستخدم خادوم مرآة معين، أو ربما مساهم لم يعد يساهم — يمكنك استخدام إما git remote remove وإما git remote rm:

$ git remote remove paul
$ git remote
origin

وما إن تحذف الإشارة إلى بعيدٍ هكذا، فإن جميع الفروع المتعقِبة له وجميع إعدادات التهيئة المرتبطة به ستُحذف كذلك.

الوسوم

مثل معظم أنظمة إدارة النسخ، يستطيع جت وسم المراحل المهمة في تاريخ المشروع. يستعمل الناس هذه الآلية في الغالب لتمييز الإصدارات (v1.0 و v2.0 وهكذا). سنتعلم في هذا الفصل كيف نسرد الوسوم الموجودة وكيف ننشئ وسومًا ونحذفها وما أنواع الوسوم المختلفة.

سرد وسومك

سرد الوسوم الموجودة في مستودع جت سهل جدا؛ فقط اكتب git tag (اختياريًا مع -l أو --list):

$ git tag
v1.0
v2.0

يسرد لك هذا الأمر الوسوم بترتيب أبجدي؛ أي أن ترتيب عرضها ليس له أهمية حقيقية.

يمكنك أيضا البحث عن الوسوم التي تطابق نمطًا معينًا. يحتوي مستودع مصدر جت مثلا على أكثر من خمسمئة وسم. فإذا كنت مهتمًا برؤية سلسلة 1.8.5 فقط، فنفذ هذا:

$ git tag -l "v1.8.5*"
v1.8.5
v1.8.5-rc0
v1.8.5-rc1
v1.8.5-rc2
v1.8.5-rc3
v1.8.5.1
v1.8.5.2
v1.8.5.3
v1.8.5.4
v1.8.5.5
سرد الوسوم بأنماط يحتاج الخيار -l أو --list

إذا لم تُرِد إلا قائمة الوسوم بكاملها، فتنفيذ git tag يفترض أنك تريد سرد الوسوم فيعطيك إياه؛ استخدام -l أو --list في هذه الحالة اختياري.

لكن إذا أعطيته نمطًا لمطابقة وسوم عديدة، فيجب عليك استخدام خيار السرد: -l أو --list.

إنشاء وسوم

يدعم جت نوعين من الوسوم: خفيفة، ومعنونة.

الوسم الخفيف كأنه فرع لا يتغيّر: مجرد إشارة إلى إيداع معين.

لكن على النقيض، الوسوم المعنونة هي كائنات كاملة في قاعدة بيانات جت؛ يحسب جت بصمتها، ويسجل معها اسم الواسم، وبريده، وتاريخ الوسم، ورسالته، ويمكن توقيعها وتوثيقها باستعمال GNU Privacy Guard‎ (GPG). من الأفضل في العموم إنشاء وسوم معنونة حتى تتمتع بكل هذه المعلومات، لكن إذا أردت وسمًا مؤقتًا أو لسبب ما لم تشأ الاحتفاظ بكل هذه المعلومات، فلا تزال الوسوم الخفيفة متاحة.

الوسوم المعنونة

إنشاء الوسوم المعنونة في جت يسير. الطريقة الأسهل هي إضافة -a إلى أمر الوسم:

$ git tag -a v1.4 -m "my version 1.4"
$ git tag
v0.1
v1.3
v1.4

والخيار -m يعيّن رسالة الوسم، التي تخزّن معه. وإذا لم تعيّن رسالة للوسم المعنون، فإن جت سيفتح لك محررك حتى تكتبها فيه.

يمكنك أيضا رؤية تاريخ الوسم مع الإيداع الموسوم بأمر الإظهار git show:

$ git show v1.4
tag v1.4
Tagger: Ben Straub <ben@straub.cc>
Date:   Sat May 3 20:19:12 2014 -0700

my version 1.4

commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    Change version number

يُظهر لك هذا معلومات الواسم وتاريخ وسم الإيداع ورسالة الوسم، ثم معلومات الإيداع نفسه.

الوسوم الخفيفة

طريقة أخرى لوسم الإيداعات هي باستعمال الوسوم الخفيفة. هذا يعني تخزين بصمة الإيداع في ملف؛ لا معلومات أخرى تُخزّن. لإنشاء وسم خفيف، لا تعطِ أمر الوسم أيًّا من الخيارات -a أو -s أو -m؛ أعطه فقط اسم الوسم:

$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5

تنفيذ git show على مثل هذا الوسم لن يعطيك معلومات الوسم الإضافية، بل يُظهر فقط معلومات الإيداع:

$ git show v1.4-lw
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    Change version number

الوسم لاحقًا

يمكنك أيضا وسم إيداعات قديمة تخطيتها. لنفترض مثلا أن تاريخ إيداعاتك يبدو هكذا:

$ git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 Create write support
0d52aaab4479697da7686c15f77a3d64d9165190 One more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc Add commit function
4682c3261057305bdd616e23b64b0857d832627b Add todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a Create write support
9fceb02d0ae598e95dc970b74767f19372d61af8 Update rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc Commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a Update readme

الآن لنفترض أنك نسيت وسم المشروع عند v1.2، والتي كانت عند إيداع ‪“Update rakefile”‬. يمكنك فعل هذا بعدما حدث ما حدث. لوسم ذلك الإيداع، اكتب في نهاية أمر الوسم بصمةَ الإيداع (أو جزءًا من أولها):

$ git tag -a v1.2 9fceb02

والآن ستجد أنك قد وسمت الإيداع:

$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5

$ git show v1.2
tag v1.2
Tagger: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Feb 9 15:32:16 2009 -0800

version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date:   Sun Apr 27 20:43:35 2008 -0700

    Update rakefile
...

مشاركة الوسوم

لا ينقل أمر الدفع، بطبيعته، الوسومَ إلى المستودعات البعيدة. فعليك دفعها بأمر صريح بعد إنشائها. تشبه هذه العملية كثيرًا عملية مشاركة الفروع البعيدة — نفّذ git push origin <اسم‌الوسم>.

$ git push origin v1.5
Counting objects: 14, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (14/14), 2.05 KiB | 0 bytes/s, done.
Total 14 (delta 3), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
 * [new tag]         v1.5 -> v1.5

وإذا كانت لديك العديد من الوسوم التي تريد دفعها جملةً واحدة، فيمكنك إضافة خيار الوسوم --tags إلى أمر الدفع git push، لينقل إلى المستودع البعيد جميع وسومك التي ليست هناك بالفعل.

$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 160 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
 * [new tag]         v1.4 -> v1.4
 * [new tag]         v1.4-lw -> v1.4-lw

والآن، عندما يستنسخ أحدهم مستودعك أو يجذب منه، فسيحصل على جميع وسومك أيضا.

يدفع أمر الدفع كلا النوعين من الوسوم

سيدفع git push <البعيد> --tags الوسوم الخفيفة والوسوم المعنونة. لا يوجد حاليا خيار لدفع الوسوم الخفيفة فقط، لكن الأمر git push <البعيد> --follow-tags سيدفع الوسوم المعنونة فقط إلى الخادوم البعيد.

حذف الوسوم

لحذف وسم من مستودعك المحلي، نفذ git tag -d <اسم‌الوسم>. مثلا، يمكننا حذف الوسم الخفيف الذي أنشأناه سابقًا كالتالي:

$ git tag -d v1.4-lw
Deleted tag 'v1.4-lw' (was e7d5add)

لاحظ أن هذا لا يحذف الوسم من أي مستودع بعيد. توجد طريقتان شائعتان لحذف وسم ما من مستودع بعيد:

الطريقة الأولى هي git push <البعيد> :refs/tags/<اسم‌الوسم>:

$ git push origin :refs/tags/v1.4-lw
To /git@github.com:schacon/simplegit.git
 - [deleted]         v1.4-lw

لاستيعاب ما تفعله هذه الطريقة يمكن ترى أنها تدفع القيمة الفارغة التي قبل النقطتين الرأسيتين إلى اسم الوسم على المستودع البعيد، فعمليًّا تحذفه.

الطريقة الأخرى (والبديهية أكثر) لحذف وسم من مستودع بعيد، هي:

$ git push origin --delete <اسم‌الوسم>

سحب الوسوم

إذا أردت رؤية نُسخ الملفات التي يشير إليها وسمٌ ما، يمكنك سحب هذا الوسم بأمر git checkout، مع إن هذا يضع مستودعك في حالة ‪“detached HEAD”‬، والتي لها بعض الآثار الجانبية السيئة:

$ git checkout v2.0.0
Note: switching to 'v2.0.0'.

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 performing another checkout.

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 99ada87... Merge pull request #89 from schacon/appendix-final

$ git checkout v2.0-beta-0.1
Previous HEAD position was 99ada87... Merge pull request #89 from schacon/appendix-final
HEAD is now at df3f601... Add atlas.json and cover image

في حالة ‪“detached HEAD”‬، إذا أجريت تعديلات وصنعت إيداعًا، فإن الوسم سيبقى كما هو، وإيداعك الجديد لن ينتمي إلى أي فرع ولن يمكن الوصول إليه أبدا، إلا ببصمته. لذا، فإن احتجت إجراء تعديلات — مثلا لإصلاح علة في نسخة قديمة — فغالبا ستحتاج إلى إنشاء فرع:

$ git checkout -b version2 v2.0.0
Switched to a new branch 'version2'

إذا فعلت هذا ثم صنعت إيداعًا، فإن فرع version2 سيكون مختلفًا عن وسم v2.0.0 لأنه سيكون متقدمًا عنه بتعديلاتك، لذا كن حذرًا.

كُنيات جت

قبل أن نتقدم إلى الباب التالي، نود أن نعرّفك ميزة في جت ستجعل استعمالك أسهل وأريح وأكثر ألفة: الكُنيات. لن نستعملها في أي موضع آخر في هذا الك‍تاب للوضوح، لكنك إذا كنت تنوي استعمال جت باستمرار، فيجب أن تعرف الكُنيات.

لا يُخمّن جت الأمر الذي تريده إذا كتبت جزءًا منه. فإذا لم تشأ ك‍تابة كل أمر بكامله، فيمكنك ضبط كُنية لكل أمر تريده بسهولة بأمر التهيئة git config. هذه أمثلة ربما تحب إعدادها:

$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status

هذا يعني أن يمكنك ك‍تابة git ci مثلا بدلا من أن تكتب git commit. وبالاستمرار مع جت، ستجد أوامر أخرى تستعملها كثيرا؛ لا تتردد في إنشاء كُنيات لها.

هذه الطريقة تصلح كذلك لإنشاء الأوامر التي تظن أنها يجب أن توجد. مثلا، لتصحيح صعوبة الاستخدام التي واجهتها عند إلغاء تأهيل ملف، يمكنك إضافة كُنية خاصة بك لإلغاء التأهيل unstage إلى جت:

$ git config --global alias.unstage 'reset HEAD --'

يجعل هذا الأمرين التاليين متكافئين:

$ git unstage fileA
$ git reset HEAD -- fileA

هذا أسهل وأوضح. وكذلك من الشائع إضافة أمر last «الأخير»، مثل هذا:

$ git config --global alias.last 'log -1 HEAD'

فيمكنك عندئذٍ رؤية إيداعك الأخير بسهولة:

$ git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel <dreamer3@example.com>
Date:   Tue Aug 26 19:48:51 2008 +0800

    Test for current head

    Signed-off-by: Scott Chacon <schacon@example.com>

وكما يمكنك أن تخمن، إنما يترجم جت الأمر الجديد إلى ما جعلته كُنيةً له. ولكنك أحيانا قد تريد تنفيذ أمر خارجي، بدلا من أمر فرعي في جت. في هذه الحالة تبدأ الأمر بعلامة تعجب: !. يفيد هذا عندما تكتب أدواتك الخاصة التي تعمل مع مستودع جت. نوضّح ذلك بعمل الكُنية git visual لتشغيل gitk:

$ git config --global alias.visual '!gitk'

الخلاصة

الآن تستطيع فعل جميع عمليات جت المحلية الأساسية: إنشاء مستودع أو استنساخه، وعمل تعديلات، وتأهيلها وإيداعها، وعرض تاريخ جميع التعديلات التي مر بها المستودع. التالي: سنشرح ميزة جت القاتلة للمنافسة: نموذج التفريع.