المستوى: متوسط. الافتراض إنك بتستخدم Git وGitHub Actions، وتقدر تعدّل ملف إعدادات بسيط.
لو بتكتب ملاحظات كل إصدار بإيدك، انت بتحرق حوالي 40 دقيقة في شغل ممكن سكربت واحد يعمله في أقل من ثانية. المقال ده بيوريك إزاي تخلّي رسائل الـ commit نفسها تتحوّل لـ CHANGELOG وRelease جاهز، تلقائيًا.
حوّل رسائل الـ commit إلى سجل إصدارات يكتب نفسه
المشكلة باختصار
قبل كل إصدار، حد في الفريق بيفتح سجل الـ commits ويقعد يلخّص التغييرات في ملاحظات مقروءة. الطريقة دي بتفشل في نقطتين: بتاكل وقت، وبتنسى تغييرات مهمة لما الإصدار يكبر. لو فريقك بيطلّع إصدار أسبوعيًا، ده ~35 ساعة ضايعة في السنة على مهمة متكرّرة بالكامل.
المفهوم الأساسي: رسالة الـ commit كبيانات مش كلام
تخيّل إنك بتحزّم أغراض بيت في كراتين. لو كتبت على كل كرتونة "مطبخ" أو "كتب"، هتعرف تعمل قائمة محتويات في دقيقة. لكن لو الكراتين من غير عناوين، هتفتح كل واحدة عشان تعرف جواها إيه. رسائل الـ commit زي عناوين الكراتين بالظبط.
علميًا، ده اسمه Conventional Commits: اتفاقية بتخلّي أول كلمة في الرسالة تحدّد نوع التغيير. feat لميزة جديدة، fix لإصلاح، perf لتحسين أداء. لما الرسائل تبقى منظّمة بالشكل ده، أداة زي git-cliff تقدر تقرأها وتصنّفها وتبني منها ملف CHANGELOG بدون أي تدخل يدوي.
الحل: git-cliff في ثلاث خطوات
- ثبّت الأداة:
cargo install git-cliffأو نزّل الملف التنفيذي الجاهز من صفحة الإصدارات. - حط ملف إعداد
cliff.tomlفي جذر المشروع يحدّد شكل المخرج وطريقة التصنيف. - شغّل أمر واحد يبني الملف من كل تاريخ الـ commits.
[changelog]
header = "# سجل التغييرات\n"
body = """
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group }}
{% for commit in commits %}
- {{ commit.message }}
{% endfor %}
{% endfor %}
"""
[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "ميزات جديدة" },
{ message = "^fix", group = "إصلاحات" },
{ message = "^perf", group = "تحسينات الأداء" },
]
# يبني CHANGELOG.md لكل التاريخ ويربطه بوسم الإصدار
git cliff --tag v1.2.0 --output CHANGELOG.md
على مستودع فيه مئات الـ commits، الأمر ده بيخلص في أقل من ثانية. النتيجة ملف مقسوم لأقسام واضحة: ميزات، إصلاحات، تحسينات.
الخطوة الجاية: خليه تلقائي بالكامل في CI
بدل ما تفتكر تشغّل الأمر، خلّي GitHub Actions تشغّله لوحدها كل ما تعمل وسم إصدار جديد، وتنشئ Release بالملاحظات جاهزة.
name: Release
on:
push:
tags: ["v*"]
permissions:
contents: write
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: توليد ملاحظات الإصدار
uses: orhun/git-cliff-action@v3
with:
args: --latest --strip header
env:
OUTPUT: RELEASE_NOTES.md
- name: إنشاء Release
uses: softprops/action-gh-release@v2
with:
body_path: RELEASE_NOTES.md
ملاحظة مهمة: fetch-depth: 0 ضروري عشان الأكشن يقدر يقرأ كل تاريخ الـ commits والوسوم، مش آخر commit بس. من غيرها هيطلع سجل ناقص.
الـ trade-offs اللي لازم تعرفها
المكسب واضح: توفير ~40 دقيقة لكل إصدار، وسجل متسق ميعتمدش على مزاج اللي بيكتبه. الثمن: الأتمتة دي بتشتغل بجودة قد جودة رسائل الـ commit عندك. لو الفريق بيكتب "update" و"fix stuff"، المخرج هيبقى بلا معنى.
يعني التكلفة الحقيقية مش تقنية، هي تغيير عادة. محتاج تفرض اتفاقية الرسائل، ويفضّل تحطّ فحص تلقائي زي commitlint يرفض أي رسالة مش متوافقة قبل الدمج. ده يوم أو يومين onboarding للفريق مقابل مكسب دائم.
متى لا تستخدم هذه الطريقة
سيبها لو المشروع تجربة شخصية عمرها أسبوع، أو لو بتطلّع إصدار مرة كل كام شهر — العائد مش مستاهل إعداد pipeline. وسيبها لو فريقك رافض يلتزم باتفاقية رسائل ثابتة؛ ساعتها الأتمتة هتنتج سجلًا رديئًا يضلّل أكتر ما يفيد. الأداة بتكبّر الانضباط الموجود، مش بتخلقه.
الخطوة التالية
افتح مستودع فيه شوية commits، حط ملف cliff.toml اللي فوق، وشغّل git cliff --unreleased. بصّ على المخرج وقارنه بآخر ملاحظات إصدار كتبتها بإيدك. لو التصنيف طلع منطقي، انت جاهز تنقله للـ CI.
المصادر
- توثيق git-cliff الرسمي: git-cliff.org/docs
- مواصفة Conventional Commits: conventionalcommits.org
- Semantic Versioning: semver.org
- git-cliff GitHub Action: github.com/orhun/git-cliff-action
- توثيق GitHub Actions: docs.github.com/actions