Conventional Commits, to standard który określa format commit message w jasny sposób. W taki, który jest czytelny zarówno dla człowieka jak i maszyn. Czytelny dla człowieka, bo łatwo prześledzić historię zmian w repozytorium. Dla maszyn, bo taki ustandaryzowany format można oprogramować.
Mógłbym rzecz, że polecenie git commit
, to jedna z niepozornych komend git. Dzięki tej krótkiej, ale za to jak ważnej wiadomości można dużo się dowiedzieć. Między innymi to, co autor miał na myśli.
Niestety, realia są, jakie są i w większości projektów ta wiadomość nic nie mówi. Czasami odnoszę wrażenie, że ludzie muszą tam wpisać cokolwiek, bo to jedyny wymóg by opublikować kawałek swojej pracy. W niektórych projektach jedynym wymogiem jest umieszczenie numeru ticketa. Commity o treści refactor
, added XXX
czy cr fixes
, to smutna i nudna rzeczywistość.
A co gdyby można było to poprawić? Gdyby można było czytać historię zmian w naszym kodzie tylko za pomocą historii commitów – bez zaglądania w szczegóły? Gdyby trzeba było zrobić revert kodu na produkcji – wiedziałbyś który commit trzeba usunąć bez zaglądania w szczegóły?
Okazuje się, że jest pewien standard, który porządkuje te wiadomość w bardzo prosty prosty sposób. Cytując pewną znaną scenę, z polskiego kultowego filmu: i teraz wchodzę ja, cały na biało.
Conventional Commits
Conventional Commits określa listę typów commitów odpowiadających za faktyczne zmiany w kodzie. Commit message wygląda następująco:
type(scope): description
Conventional Commits – type
Type, jak sama nazwa określna rodzaj zmian w kodzie. Czy wprowadzone zmiany to nowa funkcjonalność a może jakaś poprawka? Spójrz na poniższą listę typów wiadomości jakie można użyć:
feat
– nowa funkcjonalnośćfix
– poprawka do istniejącej funkcjonalnościdocs
– zmiany tylko w dokumentacjichore
– zmiany które nie mają wpływu z zawartość kodu źródłowego czy testów (np. aktualizacja paczek)refactor
– zmiany, które nie są zarówno poprawkami jak i nowymi funkcjonalnościamitests
– wszystko co związane z testami (dodawanie, edycja)perf
– zmiany w kodzie usprawniające wydajność,styles
– wszelkiego rodzaju formatorania kodu, białe znaki, przecinki czy brakujące średnikici
– zmiany na potrzeby CI (konfigi, skrypty),build
– zmiany wpływające na proces builda,revert
– revert ostanich zmian
Conventional Commits – scope
W dokumentacji jest informacja, że powinien być to rzeczownik określający miejsce w kodzie np. validator, form, itd. Z doświadczenia wiem (ilość i złożoność projektów), że nie zawsze jest idealne rozwiązanie. Ta „sugestia” sprawdza się bardzo dobrze w małych projektach gdzie funkcjonalność nie jest skomplikowana.
Problem pojawia się wtedy gdy nad projektem pracuje duża liczba osób. W takim przypadkach określenie scope
jest problematyczne.
Dlatego w zespołach w których pracuję używany numeru ticketa jako scope
. Dodatkową zaletą tego podejścia jest to, że gdy korzystasz z takiego systemu jak JIRA, to taki numer jest automatycznie linkowany. Podczas generowania CHANGELOGu taki dodatek jest bardzo fajny.
scope
jest opcjonalny.
Conventional Commits – description
Najistotniejsza część. Trzeba się na prawdę postarać by napisać dobry komentarz. Na szczęście tylko na początku. Im lepiej (epicko) opiszesz swoje zmiany, tym lepiej dla wszystkich.
Najlepszy przykład z jakim sam się spotkasz to commity o wdzięcznej treści jak: cr fixes
. Ile razy to widzisz? Wiesz co kryje się za tym magicznym zwrotem? A gdyby można było napisać, że zmieniłeś sposób wywołania funkcji lub poprawiłeś literówkę w nazwie zmiennej albo usunąłeś wywałania razem z definicją jakieś metody? Czy takie wiadomości nie są czytelniejsze?
Idealna wiadomość to powinna:
- Zwięźle opisywać zmiany jakie zaszły w kodzie.
- Być w trybie rozkazującym w czasie teraźniejszym (ang. present tense). Oznacza to, że nie powinno być czegoś takiego jak
added polish translations
lubadding polish translations
tylkoadd polish translations
.
BREAKING CHANGE (łamiące zmiany)
Warto jeszcze wspomnieć, że nasze niektóre zmiany mogą być łamiące (np. w API). Dlatego warto jeszcze wspomnieć o footerze commita. Jest to idealne miejsce, w którym możemy oznaczyć „łamiące zmiany”. Footer wiadomości powinien być oddzielny nową linią i praktyce wygląda następująco:
feat: allow provided config object to extend other configs BREAKING CHANGE: `extends` key in config file is now used for extending other config files
Kilka przykładów z wykorzystujących Conventional Commits:
docs: add short circuit to hook example (#665) docs: fix typo in code inside README.md (#662) fix(deps): update dependency cz-conventional-changelog to v3 (#654) fix(deps): update dependency lodash to v4.17.15 (#652) chore: update mocha, other dev deps (#653) fix(node): remove node 6 and 8 support (#649) chore(deps): update dependency semantic-release to v15.13.18 (#644) fix: update dependencies for security (#645) fix(deps): update dependency lodash to v4.17.14 [security] (#641) docs: highlight pre-requisties and bubble up related sections (#613) fix(cli): allow argv to be overridden in bootstrap (#621) feat(cli): implement --hook option for git hooks integration (#615) tests(ED-439): add tests for Modal component styles: format code with new prettier config
Ale po co to wszystko?!
- Conventional Commits wprowadza porządek w commitach.
- Wbrew pozorom poprawia komunikację w projekcie (zespole).
- Pozwala wygenerować CHANGELOG (historię zmian).
- Jest możliwość sprawdzania czy commit message jest poprawny (git hook pre-commit).
- W bardzo łatwy sposób można określić które zmiany w kodzie mogę być opublikowane jako major, minor czy patch (jeśli korzysta się z Semantic Versioning –
major.minor.patch
).
W Conventional Commits jest jeden problem – Ty!
Oczywiście jest jeden problem w tym wszystkim. Jest to oczywiście czynnik ludzki. Developerzy przede wszystkim muszą chcieć dawać lepsze opisy i rozumieć jakiego rodzaju wprowadzili zmiany w kodzie. Tak więc przed każdym commitem trzeba się dodatkowo „wysilić”. Dobra wiadomość jest taka, że szybko to wchodzi w krew.
Przydatne linki:
- Conventional Commits – oficjalna dokumentacja
- commitlint (nodejs) – pozwala sprawdzić czy commit jest poprawny
- semantic release (nodejs) – automatyzuje proces publikowania i release’owania paczek
- generate-changelog (nodejs) – generuje changelog na podstawie commitów
- commitizen (nodejs) – pomaga w tworzeniu commitów poprzez prompty w konsoli
- Conventional commit for php – libka znajomego do sprawdzania poprawności commit message (WIP)
komentarze 3
Podawanie numeru zlecenia w commicie jest bardzo dobre i przydatne, ale nie jako scope. To zabija tę funkcję, która właśnie w dużych projektach pokazuje swoją siłę, bo łatwo się zorientować jakiego obszaru dotyka commit. Świetnym przykładem jest niemały przecież Angular: https://github.com/angular/angular/blob/master/CHANGELOG.md
fix(forms) lub perf(compiler) mówią znacznie wiecej niż fix(jira-997) i dodatkowo pozwalają grupować i grepować 😉 po zakresach zmian.
Jak załatwisz sprawę konfliktów i commitowania ich?
Każdy merge z konfliktami wrzucam z flagą `–no-verify`