Conventional Commits adalah aturan penamaan pesan commit agar histori Git bisa dibaca manusia sekaligus diproses alat otomatis. Jika diterapkan dengan benar, format ini membantu reviewer memahami intent perubahan lebih cepat, memudahkan pembuatan changelog yang konsisten, dan menjadi fondasi release otomatis berbasis jenis perubahan seperti feat, fix, atau breaking change.
Masalah yang sering muncul di banyak repository bukan kurangnya commit, melainkan histori yang sulit dipakai: pesan seperti update, fix bug, atau final tidak memberi konteks yang cukup. Akibatnya, tim kesulitan menelusuri perubahan, menyusun catatan rilis, dan menentukan apakah sebuah perubahan aman dirilis sebagai patch, minor, atau major. Di sinilah Conventional Commits menjadi praktis: ia memberi struktur yang sederhana tetapi cukup kuat untuk otomatisasi.
Apa itu Conventional Commits dan mengapa berguna
Secara umum, format Conventional Commits mengikuti pola berikut:
<type>(<scope>): <subject>
[optional body]
[optional footer]Contoh:
feat(auth): tambah refresh token untuk sesi login
fix(api): tangani null response pada endpoint profile
docs(readme): perbarui contoh instalasi lokal
refactor(cache): sederhanakan strategi invalidasi
chore(ci): rapikan workflow validasi commitBagian-bagian utamanya:
- type: kategori perubahan, misalnya
featataufix. - scope: area yang terdampak, misalnya
auth,api,ui. Ini opsional tetapi sangat membantu. - subject: ringkasan singkat perubahan dalam satu baris.
- body: penjelasan tambahan jika diperlukan, terutama untuk keputusan teknis atau dampak perubahan.
- footer: metadata seperti referensi issue atau penanda breaking change.
Kenapa format ini bekerja? Karena ia memaksa commit menjawab pertanyaan dasar yang paling sering dibutuhkan saat review dan saat menelusuri histori:
- Perubahan ini jenisnya apa?
- Bagian sistem mana yang terdampak?
- Apakah ini fitur baru, perbaikan bug, atau perubahan internal?
- Apakah perubahan ini memecahkan kompatibilitas?
Ketika informasi itu konsisten, alat otomatis bisa memakainya untuk:
- mengelompokkan changelog berdasarkan jenis perubahan,
- menentukan versi rilis berikutnya,
- memvalidasi kualitas pesan commit di CI,
- memudahkan audit histori saat incident atau rollback.
Format commit utama yang perlu dipakai
feat
Gunakan feat untuk fitur baru yang terlihat sebagai penambahan kemampuan sistem. Biasanya commit ini relevan untuk bump versi minor dalam alur release otomatis.
feat(billing): tambah endpoint invoice previewJangan gunakan feat untuk semua perubahan besar. Jika Anda hanya memindahkan kode tanpa menambah kemampuan baru, lebih tepat memakai refactor.
fix
Gunakan fix untuk perbaikan bug atau perilaku yang salah. Dalam banyak workflow otomatis, ini biasanya dipetakan ke bump patch.
fix(upload): cegah file kosong lolos validasiHindari subject yang kabur seperti fix bug. Tulis gejala atau area yang diperbaiki.
docs
Untuk perubahan dokumentasi saja, seperti README, panduan setup, atau komentar dokumentatif yang bukan perubahan perilaku runtime.
docs(api): tambahkan contoh request untuk webhookrefactor
Gunakan saat struktur kode diubah tanpa mengubah perilaku eksternal yang diharapkan. Ini penting agar reviewer tahu perubahan berfokus pada maintainability, bukan fitur atau bugfix.
refactor(payment): pecah service settlement menjadi modul kecilKesalahan umum adalah memakai refactor untuk perubahan yang sebenarnya memperbaiki bug. Jika ada bug yang benar-benar diperbaiki, fix lebih jujur dan lebih berguna untuk changelog.
chore
Dipakai untuk pekerjaan rutin yang tidak langsung memengaruhi perilaku aplikasi dari sudut pandang pengguna, misalnya perubahan tooling, script, dependensi build, atau workflow CI.
chore(repo): tambah aturan commitlint di pipelinePerlu hati-hati: upgrade dependensi yang memperbaiki bug produksi kadang lebih informatif jika dijelaskan di body atau dipisah ke commit yang lebih spesifik.
Breaking change
Breaking change berarti perubahan yang memutus kompatibilitas. Ini bisa ditandai dengan dua cara yang umum:
- menambahkan tanda seru setelah type atau scope,
- menulis footer
BREAKING CHANGE:.
Contoh:
feat(api)!: ubah format respons endpoint user
BREAKING CHANGE: field `name` diganti menjadi `full_name` pada respons API user.Penanda ini sangat penting untuk release otomatis karena menandakan bump major. Tanpa penanda yang jelas, perubahan yang memutus kompatibilitas bisa lolos sebagai patch atau minor dan menyebabkan masalah pada pengguna downstream.
Aturan penulisan commit yang realistis untuk tim
Conventional Commits akan gagal jika aturannya terlalu teoritis atau terlalu ketat sejak hari pertama. Tujuan utamanya bukan membuat commit terlihat formal, melainkan membuat histori benar-benar berguna.
Gunakan subject yang spesifik dan bisa dipindai cepat
Pesan commit harus membantu reviewer memahami perubahan tanpa membuka semua file. Bandingkan:
- Buruk:
fix: bug upload - Lebih baik:
fix(upload): tolak file tanpa mime type yang valid
Subject yang baik biasanya:
- ringkas,
- menyebut area yang terdampak,
- menjelaskan hasil perubahan, bukan proses pribadi penulis.
Scope opsional, tetapi sebaiknya konsisten
Scope tidak wajib, tetapi sangat membantu di repository menengah dan besar. Tentukan konvensi yang sesuai struktur proyek, misalnya berdasarkan domain bisnis (billing, auth), layer teknis (api, db, ci), atau paket/module di monorepo.
Hindari scope yang berubah-ubah tanpa aturan, seperti kadang backend, kadang server, kadang api-v2 untuk area yang sama. Ketidakkonsistenan akan mengurangi nilai histori dan menyulitkan filter changelog.
Body commit dipakai saat konteks teknis penting
Tidak semua commit perlu body. Tambahkan body jika Anda perlu menjelaskan:
- alasan desain,
- dampak migrasi,
- trade-off performa atau keamanan,
- perubahan yang tidak langsung terlihat dari diff.
Contoh:
fix(cache): hindari stale data setelah update profil
Invalidate cache dipindah ke level service agar semua jalur update
memakai mekanisme yang sama dan tidak bergantung pada controller.Jangan campur banyak intent dalam satu commit
Commit seperti berikut sulit dipakai:
feat(auth): tambah refresh token dan perbaiki bug logout dan update docsIni membuat changelog kabur dan menyulitkan revert. Jika memungkinkan, pisahkan berdasarkan intent:
feat(auth): tambah refresh token untuk sesi loginfix(auth): hapus token sesi saat logoutdocs(auth): tambahkan alur login dan refresh token
Trade-off-nya, commit lebih banyak. Namun kualitas histori biasanya jauh lebih baik.
Manfaat Conventional Commits untuk review, changelog, dan release otomatis
Mempercepat review code
Reviewer biasanya ingin tahu dua hal sebelum membaca diff: apa tujuan perubahan, dan apakah perubahan ini berisiko. Dengan Conventional Commits, reviewer bisa memprioritaskan commit fix yang sensitif, memahami commit refactor sebagai perubahan struktural, dan mengidentifikasi breaking change lebih awal.
Ini tidak menggantikan deskripsi pull request, tetapi mengurangi waktu orientasi saat menelusuri commit satu per satu.
Menghasilkan changelog yang konsisten
Changelog manual sering tidak konsisten karena bergantung pada interpretasi masing-masing orang. Dengan pesan commit yang terstruktur, alat otomatis bisa mengelompokkan perubahan menjadi kategori seperti fitur, perbaikan, dokumentasi, atau breaking changes.
Hasilnya bukan hanya lebih cepat, tetapi juga lebih dapat diaudit karena sumber datanya adalah histori commit yang sama dengan kode.
Mendukung release otomatis
Dalam workflow release otomatis, sistem akan membaca commit sejak tag rilis terakhir lalu menentukan jenis bump versi berdasarkan isi commit. Pola yang umum:
fix-> patchfeat-> minorBREAKING CHANGEatau!-> major
Pendekatan ini bekerja karena commit sudah menyimpan intent perubahan dalam format yang bisa diparsing. Namun ia hanya akurat jika disiplin penulisan commit dijaga. Satu commit yang salah tipe bisa menghasilkan versi rilis yang keliru.
Contoh validasi dengan commitlint
Salah satu cara praktis menjaga konsistensi adalah memvalidasi pesan commit menggunakan commitlint. Tujuannya sederhana: commit yang tidak mengikuti aturan ditolak lebih awal, idealnya di mesin developer dan di CI.
Contoh konfigurasi dasar
Buat file commitlint.config.js:
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'refactor', 'chore', 'test', 'build', 'ci', 'perf', 'style', 'revert']
],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'header-max-length': [2, 'always', 100]
}
};Penjelasan singkat:
@commitlint/config-conventionalmenyediakan aturan dasar sesuai konvensi umum.type-enummembatasi type yang boleh dipakai agar tim tidak membuat kategori liar sepertimiscatauupdate.subject-emptydantype-emptymencegah commit yang tidak informatif.header-max-lengthmembantu menjaga keterbacaan di terminal dan antarmuka Git.
Jika tim ingin scope tertentu saja yang diizinkan, Anda bisa menambah aturan khusus. Tetapi jangan terburu-buru membuat whitelist scope jika struktur proyek masih sering berubah.
Validasi lokal sebelum commit
Validasi paling efektif dilakukan sedekat mungkin dengan saat commit dibuat. Biasanya tim memasang hook Git agar pesan commit langsung diperiksa. Implementasinya bisa berbeda tergantung tool yang dipakai untuk mengelola hook, tetapi idenya sama: jalankan commitlint terhadap pesan commit pada hook commit-msg.
Contoh perintah validasi yang umum dipakai dalam hook:
npx commitlint --edit $1Dengan pendekatan ini, developer mendapat feedback langsung tanpa menunggu pipeline CI.
Hook lokal berguna untuk feedback cepat, tetapi jangan hanya mengandalkan hook lokal. Hook bisa dilewati atau tidak terpasang dengan benar di mesin tertentu. Tetap lakukan validasi di CI.
Integrasi sederhana di CI agar commit invalid gagal divalidasi
Selain di mesin lokal, validasi juga perlu dijalankan di CI untuk memastikan semua commit yang masuk ke branch utama mengikuti aturan yang sama.
Contoh workflow CI sederhana
Berikut contoh generik dengan GitHub Actions yang memeriksa commit pada pull request. Prinsipnya bisa diterapkan juga di platform CI lain.
name: commitlint
on:
pull_request:
jobs:
validate-commits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: |
npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verboseKenapa fetch-depth: 0 penting? Karena validasi rentang commit membutuhkan histori yang cukup. Checkout dangkal sering membuat commitlint tidak bisa membaca range yang diinginkan.
Jika Anda tidak menggunakan GitHub Actions, pola umumnya tetap sama:
- checkout repository dengan histori commit yang cukup,
- install dependency yang dibutuhkan,
- jalankan commitlint pada range commit yang masuk,
- gagalkan job jika ada commit yang tidak valid.
Apa yang divalidasi di CI
Untuk pull request, yang ideal adalah memeriksa semua commit baru pada branch tersebut. Dengan begitu, squash merge bukan satu-satunya pagar kualitas. Ini penting jika tim ingin histori commit tetap berguna, bukan hanya pesan merge terakhir.
Jika tim menggunakan strategi squash and merge secara konsisten, Anda juga bisa menambahkan validasi pada judul pull request atau pesan squash commit. Namun itu adalah lapisan tambahan, bukan pengganti validasi commit individual.
Alur implementasi bertahap di proyek yang sudah berjalan
Menerapkan Conventional Commits di proyek lama tidak perlu dilakukan secara drastis. Pendekatan bertahap biasanya lebih berhasil dan lebih sedikit menimbulkan resistensi.
Tahap 1: sepakati subset type yang kecil
Mulai dari type yang paling sering dipakai:
featfixrefactordocschore
Subset kecil ini sudah cukup untuk banyak tim. Menambahkan terlalu banyak kategori di awal sering membuat developer bingung membedakan build, ci, style, atau perf sebelum manfaat dasarnya terasa.
Tahap 2: terapkan pada commit baru, jangan paksa menulis ulang histori lama
Tidak perlu mengubah histori lama kecuali ada alasan khusus. Fokus pada semua commit baru mulai tanggal atau branch tertentu. Ini mengurangi beban migrasi dan menghindari risiko rewrite history pada repository aktif.
Tahap 3: pasang validasi lokal dan CI
Setelah aturan disepakati, barulah pasang hook lokal dan CI. Jangan pasang validasi keras sebelum tim paham format dasarnya, karena itu hanya akan dianggap hambatan.
Tahap 4: mulai otomatisasi changelog atau release
Begitu kualitas commit cukup stabil, Anda bisa menghubungkan histori commit ke generator changelog atau proses release otomatis. Urutannya penting: otomatisasi akan rapuh jika data inputnya belum konsisten.
Tahap 5: evaluasi aturan yang terlalu kaku
Setelah beberapa minggu, lihat pola kegagalan yang paling sering. Jika banyak developer gagal karena hal yang tidak memberi nilai nyata, sederhanakan aturan. Contohnya, mewajibkan scope mungkin belum perlu untuk repository kecil.
Strategi adopsi tim agar tidak terasa sebagai beban
Sediakan contoh yang dekat dengan domain proyek
Dokumentasi satu halaman berisi 10 contoh commit yang sesuai dengan modul proyek biasanya lebih efektif daripada dokumen panjang berisi definisi abstrak.
Contoh:
feat(cart): tambah dukungan kupon bertingkat
fix(checkout): cegah duplikasi order saat retry request
refactor(order): pindahkan validasi stok ke service layer
docs(deploy): perbarui langkah rollback di staging
chore(ci): tambahkan validasi commit pada pull requestGunakan template di pull request dan code review
Reviewer bisa ikut menjaga kualitas commit dengan memberi masukan pada type atau subject yang tidak tepat. Ini jauh lebih efektif jika reviewer memiliki pedoman yang sama, misalnya:
- Apakah type commit sesuai intent perubahan?
- Apakah subject cukup spesifik?
- Apakah ada breaking change yang belum ditandai?
- Apakah satu commit mencampur beberapa jenis perubahan?
Jangan menghukum kasus tepi
Ada commit yang sulit dikategorikan secara sempurna, misalnya perubahan dependensi yang sekaligus memperbaiki bug dan mengubah konfigurasi CI. Dalam kasus seperti ini, pilih type yang paling mewakili dampak utama dan jelaskan sisanya di body. Konvensi yang berguna adalah yang pragmatis, bukan yang memaksa klasifikasi sempurna pada semua situasi.
Kesalahan umum yang membuat histori sulit dipakai
1. Subject terlalu generik
fix: bug
chore: update
refactor: cleanupCommit seperti ini hampir tidak membantu saat pencarian histori. Tulis masalah atau area yang diubah.
2. Salah memilih type demi lolos aturan
Jika semua hal dipaksa menjadi chore, maka changelog dan release otomatis kehilangan makna. chore bukan tempat sampah untuk perubahan yang penulisnya malas mengklasifikasikan.
3. Lupa menandai breaking change
Ini salah satu kesalahan paling berisiko. Jika kontrak API, nama event, skema konfigurasi, atau perilaku publik berubah tidak kompatibel, tandai secara eksplisit.
4. Satu commit berisi refactor, fix, dan feat sekaligus
Commit campuran sulit direview, sulit direvert, dan sulit diklasifikasikan otomatis. Pecah commit berdasarkan intent selama masih masuk akal secara teknis.
5. Aturan terlalu banyak sejak awal
Scope wajib, daftar scope harus tepat, body harus ada, footer wajib, panjang subject harus sangat ketat: kombinasi seperti ini sering membuat tim lebih fokus pada linter daripada kualitas komunikasi.
Debugging dan troubleshooting saat validasi gagal
Commit valid di lokal tetapi gagal di CI
Penyebab umum:
- range commit yang diperiksa di CI salah,
- checkout terlalu dangkal,
- versi dependency berbeda antara lokal dan CI,
- CI memeriksa lebih banyak commit daripada yang diuji lokal.
Mulailah dengan memastikan histori di CI cukup lengkap dan perintah --from serta --to mengarah ke SHA yang benar.
Commit merge atau auto-generated message ditolak
Beberapa tim memilih mengecualikan commit tertentu, tetapi lakukan dengan hati-hati. Terlalu banyak pengecualian akan merusak konsistensi aturan. Jika workflow Anda menghasilkan pesan otomatis, pertimbangkan mengubah workflow tersebut atau validasi di titik yang lebih tepat, misalnya pada squash commit final.
Developer bingung memilih refactor atau fix
Tanya dampak utamanya: apakah pengguna mendapat perilaku yang sebelumnya salah lalu kini benar? Jika ya, cenderung fix. Jika perilaku eksternal tetap sama dan yang berubah terutama struktur internal, gunakan refactor.
Kapan Conventional Commits terasa terlalu ketat
Meskipun berguna, Conventional Commits tidak selalu perlu diterapkan secara penuh. Pendekatan ini bisa terasa terlalu ketat jika:
- repository sangat kecil dan jarang dirilis,
- tim sangat kecil dengan komunikasi sinkron yang intens,
- semua perubahan sudah dirangkum di level pull request dan histori commit tidak dijadikan sumber changelog,
- workflow eksploratif membuat commit sangat sementara dan sering di-squash sebelum merge.
Namun bahkan dalam kasus tersebut, format minimal seperti feat, fix, dan chore tetap sering memberi manfaat tanpa menambah banyak beban.
Jika tim merasa aturan ini menghambat, biasanya masalahnya bukan pada Conventional Commits itu sendiri, tetapi pada detail implementasi yang terlalu kaku. Solusi yang lebih baik adalah menyederhanakan aturan, bukan langsung meninggalkannya.
Penutup
Conventional Commits untuk changelog dan release otomatis bekerja baik jika diperlakukan sebagai alat komunikasi, bukan sekadar format linter. Mulailah dari subset type yang kecil, tetapkan contoh yang relevan dengan proyek, validasi dengan commitlint di lokal dan CI, lalu sambungkan ke otomatisasi changelog atau release setelah kualitas histori stabil.
Hasil yang paling terasa biasanya bukan otomatisasi itu sendiri, melainkan histori Git yang lebih bisa dipercaya. Reviewer lebih cepat memahami intent perubahan, tim lebih mudah menelusuri bug, dan proses rilis menjadi lebih konsisten karena dibangun di atas commit yang jelas dan terstruktur.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!