Conventional Commits: jak pisać commity, które w końcu coś znaczą

Avatar użytkownika Patryk Penczek
Patryk Penczek
Opublikowano: 27 maja 2026Edytowano: 30 maja 2026
Miniatura artykułu Conventional Commits: jak pisać commity, które w końcu coś znaczą

Dlaczego to ważne

Każdy, kto spędził kilka godzin na szukaniu momentu, w którym do projektu wkradł się błąd, wie, jak bardzo boli historia w stylu „fix stuff", „update", „kolejne poprawki" czy „teraz działa". Historia git commit messages to dokumentacja projektu w czasie rzeczywistym i bez żadnej konwencji jest kompletnie bezużyteczna. Conventional Commits to lekka specyfikacja, która nadaje wiadomościom commitów strukturę zrozumiałą zarówno dla ludzi, jak i dla narzędzi automatyzujących wydania.

Konwencja powstała jako rozwinięcie podejścia stosowanego przez zespół Angular i doczekała się oficjalnej specyfikacji pod adresem conventionalcommits.org. Dziś jest standardem w tysiącach projektów open source i coraz częściej w firmowych repozytoriach. Warto ją poznać nie tylko dlatego, że porządkuje historię projektu, ale przede wszystkim dlatego, że otwiera drzwi do automatyzacji wersjonowania i generowania changelogów, których ręczne podejście zwyczajnie nie zapewni.

Typy commitów

Sercem konwencji jest typ commita, który komunikuje intencję zmiany każdemu, kto czyta historię lub przetwarza ją automatycznie. Specyfikacja wyróżnia dwa typy o ściśle zdefiniowanym znaczeniu semantycznym: feat dla nowych funkcjonalności i fix dla naprawionych błędów. Wszystkie pozostałe typy są oficjalnie opcjonalne, choć w praktyce stosuje się ustaloną przez społeczność listę.

Popularny preset @commitlint/config-conventional, wywodzący się z konwencji Angulara, definiuje pełen zestaw typów stosowanych w większości projektów:

  • feat: nowa funkcjonalność; wyzwala bump wersji MINOR

  • fix: naprawa błędu; wyzwala bump wersji PATCH

  • refactor: przebudowa kodu bez zmiany zachowania

  • perf: optymalizacja wydajności

  • test: dodanie lub poprawa testów

  • docs: wyłącznie zmiany w dokumentacji

  • style: formatowanie, spacje, przecinki; zero wpływu na logikę

  • build: zmiany w systemie budowania lub zewnętrznych zależnościach

  • ci: konfiguracja pipeline'ów ciągłej integracji

  • chore: czynności techniczne niewidoczne dla użytkownika

Kluczowa zasada: typ powinien opisywać skutek dla użytkownika, nie technikę, którą zastosowałeś.

Połączenie tej konwencji z Semantic Versioning (SemVer) tworzy spójny system zarządzania wersjami. Commity fix przekładają się na wersję PATCH, commity feat na wersję MINOR, a zmiany oznaczone jako BREAKING CHANGE wymuszają skok wersji MAJOR. Dzięki temu narzędzia takie jak semantic-release czy release-please mogą automatycznie wyznaczać kolejne numery wersji na podstawie samej historii commitów, bez żadnej ręcznej ingerencji.

Struktura wiadomości

Każdy commit w tej konwencji ma konkretny kształt, który trudno pomylić po kilku dniach praktyki. Wiadomość składa się z nagłówka, opcjonalnej treści i opcjonalnych stopek.

<typ>[opcjonalny zakres]: <opis>

[opcjonalna treść]

[opcjonalne stopki]

Nagłówek jest obowiązkowy i powinien zmieścić się w 50 znakach, co wynika z ograniczeń wielu narzędzi wyświetlających historię. Opis po dwukropku piszemy w trybie rozkazującym: „add feature", nie „added feature", „fix memory leak", nie „fixed memory leak". Dzięki temu wszystkie wpisy w historii mają jednolity rytm i czyta się je jak instrukcje, nie opisy przeszłości.

Treść commita, oddzielona pustą linią od nagłówka, służy wyjaśnieniu dlaczego coś zostało zmienione, nie jak. To miejsce na kontekst, który za kilka miesięcy będzie nieoceniony podczas debugowania, refaktoryzacji czy onboardingu nowego członka zespołu. Niezależnie od treści, wiadomość może zawierać też stopki: oddzielone pustą linią od treści, służą do metadanych takich jak odniesienia do ticketów (Closes #892), lista recenzentów (Reviewed-by: Jan Kowalski) lub oznaczenia breaking change. Pełny commit wygląda wtedy tak:

fix(payments): prevent duplicate charge on network retry

Stripe's idempotency key was not being forwarded on retried requests, causing some users to be charged twice on unstable connections. The key is now derived from the order ID and persisted across retries.

Closes #892 Reviewed-by: Jan Kowalski

Zakres (scope)

Scope, czyli zakres, to opcjonalny element umieszczany w nawiasie po typie. Jego celem jest wskazanie, której części projektu dotyczy zmiana, co sprawia, że historia staje się znacznie bardziej nawigowalna. W dużym projekcie commit feat(auth): add OAuth2 token refresh od razu mówi, gdzie szukać kodu i kogo pytać o szczegóły.

Nazewnictwo zakresów powinno być spójne w całym projekcie, dlatego warto je zdefiniować i udokumentować, zanim historia rozrośnie się do setek wpisów. Najlepiej mapować zakresy na moduły, katalogi lub obszary funkcjonalne projektu, tak żeby każdy w zespole używał identycznych nazw. Połowa zespołu piszącego feat(authentication) i połowa piszącego feat(auth) sprawia, że powiązane zmiany trafiają do osobnych sekcji automatycznie generowanego changelogu i tracą swój kontekst.

Breaking changes

Breaking changes (zmiany niekompatybilne wstecz) zasługują na szczególne traktowanie i konwencja zapewnia ku temu dwa mechanizmy. Wykrzyknik po typie lub zakresie, jak w feat(api)!: remove deprecated endpoints, to skrócona forma sygnalizująca wagę commita w samym nagłówku. Stopka BREAKING CHANGE: z kolei pozwala dodać szczegółowy opis tego, co się zmieniło i co konsumenci muszą zaktualizować, dlatego jest lepszym wyborem zawsze wtedy, gdy sama nazwa commita nie wystarczy do zrozumienia skali zmiany. Oba mechanizmy można stosować jednocześnie: wykrzyknik dla widoczności, stopka dla szczegółów.

Pominięcie oznaczenia breaking change to jeden z najpoważniejszych błędów przy stosowaniu tej konwencji, bo żadne narzędzie nie jest w stanie automatycznie wykryć, że coś zepsuje projekty zależne. Jeśli konsumenci twojej biblioteki lub API będą musieli cokolwiek zmienić po stronie swojego kodu, musisz to oznaczyć jawnie. W efekcie automatyczna wersja skacze do MAJOR, changelog zawiera ostrzeżenie, a nikt nie jest zaskoczony aktualizacją.

Narzędzia

Sama wiedza o formacie nie wystarczy, żeby cały zespół stosował go konsekwentnie. Na szczęście ekosystem narzędzi wokół Conventional Commits jest dojrzały i pozwala zarówno ułatwić pisanie poprawnych wiadomości, jak i uniemożliwić commitowanie wiadomości niezgodnych z konwencją.

Pisanie commitów: Commitizen

Commitizen to interaktywne narzędzie CLI, które prowadzi przez proces tworzenia wiadomości krok po kroku. Po uruchomieniu git cz lub npx cz zamiast git commit pojawia się pytanie o typ, zakres, opis i ewentualne breaking changes. Dzięki temu nowi członkowie zespołu uczą się konwencji przez działanie, nie przez czytanie dokumentacji.

Walidacja i git hooks: Commitlint, Husky i Lefthook

Commitlint pełni rolę walidatora i sprawdza każdą wiadomość commit pod kątem zgodności ze zdefiniowanymi regułami, zwracając błąd, jeśli coś się nie zgadza. Do podpięcia go pod git hook commit-msg służą dwa popularne narzędzia: Husky, będący standardem w projektach Node.js, oraz Lefthook, napisany w Go i działający znacznie szybciej, bez zależności od środowiska Node.js. Oba podejścia dają ten sam efekt: niepoprawna wiadomość zostaje odrzucona jeszcze przed zapisem w historii, bez potrzeby przypominania o konwencji na code review.

Instalacja podstawowego zestawu narzędzi sprowadza się do jednego polecenia:

pnpm add -D husky @commitlint/cli @commitlint/config-conventional commitizen
Automatyczne release: semantic-release i Release-please

semantic-release jest narzędziem, które czyta całą historię commitów od ostatniego tagu, wyznacza odpowiedni numer wersji, generuje automatyczny changelog i publikuje release, wszystko bez ręcznej interwencji. Release-please, rozwiązanie od Google, działa podobnie, tworząc automatyczne pull requesty z proponowaną wersją i zmianami w changelogu, co daje zespołowi możliwość przejrzenia i zatwierdzenia przed publikacją.

Typowe błędy

Wdrożenie konwencji w praktyce ujawnia kilka powtarzających się problemów, które warto znać z wyprzedzeniem.

Zbyt ogólne opisy

Najczęstszym błędem jest pisanie opisów, które nic nie mówią nikomu, kto nie pisał danego kodu. Wiadomość powinna być zrozumiała bez otwierania difa, a dobrym testem jest pytanie: „czy po roku będę wiedział, czego dotyczyła ta zmiana?"

fix: fix issue

feat: update auth

fix(auth): resolve token expiry using wrong comparison operator

feat(auth): add biometric login support for mobile web

Zbyt duże commity

Jeśli opis chciałbyś zakończyć słowem „oraz", to znak, że warto rozdzielić zmiany. Atomic commits, czyli commity zawierające jedną logiczną zmianę, są znacznie łatwiejsze do rewertowania, cherry-pickowania i rozumienia w izolacji.

feat(checkout): add promo code, fix cart rounding, update styles

To trzy oddzielne zmiany upchane w jeden commit. Każda z nich zasługuje na własny wpis w historii.

Niespójne zakresy w zespole

Połowa zespołu piszącego feat(authentication) i połowa piszącego feat(auth) sprawia, że powiązane zmiany trafiają do osobnych sekcji changelogu. Rozwiązaniem jest udokumentowanie uzgodnionych zakresów w pliku CONTRIBUTING.md i skonfigurowanie commitizen tak, żeby podpowiadał wyłącznie zaakceptowane wartości.

Wdrożenie w zespole

Pierwsze tygodnie z konwencją przebiegają znacznie sprawniej, jeśli podejść do wdrożenia etapami. W pierwszym tygodniu warto skupić się na konfiguracji narzędzi: zainstalować commitlint z presetem @commitlint/config-conventional, podpiąć go przez Husky'ego lub Lefthook pod git hook i opcjonalnie dodać commitizen jako wygodny interfejs do pisania wiadomości. Odrzucanie commitów przez narzędzie jest skuteczniejsze niż prośby o poprawki w code review, bo działa natychmiastowo i bez emocji.

W kolejnym kroku zespół powinien uzgodnić zestaw zakresów odpowiadający strukturze projektu i udokumentować go w miejscu widocznym dla wszystkich. Krótka sesja retrospektywna albo nawet piętnastominutowe spotkanie wystarczy, żeby wszystkim nadać wspólny język. Dobre praktyki zaczną procentować szybko, bo już przy pierwszym automatycznie wygenerowanym changelogu widać wartość całego wysiłku. Czas analizy incydentów skraca się, bo historia mówi wprost co i gdzie się zmieniło, code review staje się bardziej przewidywalne, a zarządzanie wersjami przestaje być ręcznym rytuałem przed każdym releasem.

Podsumowanie

Conventional Commits to konwencja prosta w założeniu, ale bogata w konsekwencje. Zamiast traktować git commit messages jako techniczny obowiązek, pozwala zbudować z nich dokumentację, narzędzie komunikacji i podstawę do automatyzacji wydań. Połączenie czytelnej struktury z narzędziami takimi jak commitlint, Husky, commitizen i semantic-release sprawia, że historia projektu staje się zasobem, a nie balastem.

Jeśli zaczynasz nowy projekt, konwencja jest gotowa do wdrożenia w ciągu jednego popołudnia. Jeśli pracujesz w istniejącym repozytorium, wystarczy zacząć stosować ją od następnego commita, bez przepisywania historii. Efekty będą widoczne szybciej, niż się spodziewasz, szczególnie w chwili, gdy zamiast spędzać godzinę na pisaniu release notes, uruchomisz jedno polecenie i przeczytasz gotowy changelog.

Wyświetlenia: 250

Zaloguj się, aby polubić i zapisać artykuł.

Komentarze

(0)

Zaloguj się, aby dodać komentarz.
Musisz być zalogowany, aby móc komentować i wchodzić w interakcje z innymi użytkownikami.

Powiązane artykuły