Inżynieria

Od conventional commits do changelogu

5 min czytania zaktualizowano

Conventional commits dają changelogowi za darmo trzy rzeczy: typ każdej zmiany, część systemu, której dotknęła, i czy coś psuje. Nie dają nic więcej. Sformułowanie, grupowanie i selekcja, którymi jest changelog, pozostają całkowicie otwarte, a pipeline udający inaczej dostarcza sformatowany git log.

feat(exports): add CSV column selection
fix(auth): reject expired refresh tokens
chore(deps): bump node-pg to 8.11

Trzy commity w formacie Conventional Commits. Z nich maszyna może wam powiedzieć, że jeden to funkcja, jeden to poprawka, jeden to porządki, i którą część systemu dotknął każdy z nich. To naprawdę użyteczne, i to cała obietnica konwencji: historia commitów, którą może odczytać coś innego niż osoba. Błędem jest myślenie, że to daje wam changelog. Daje wam surowy materiał.

Co określa konwencja?

Typ, opcjonalny scope, i opis: type(scope): description. Typy to konwencjonalnie feat, fix, chore, docs, refactor, test, perf, build, ci. Dwie rzeczy oznaczają zmianę łamiącą kompatybilność: ! przed dwukropkiem, lub stopka BREAKING CHANGE:. Narzędzia kierują się feat i fix dla podniesień wersji minor i patch, a znacznikiem breaking dla major.

Commit daje wamChangelog potrzebujeKto wypełnia lukę
feat / fix / choreAdded / Fixed / wewnętrzneMapowanie, automatyczne
(scope)Grupowanie, które rozpozna czytelnikOsoba, raz na scope
! lub BREAKING CHANGE:Kto się zepsuje, do kiedy, i co zrobićOsoba, za każdym razem
Opis, napisany dla recenzentkiRezultat, napisany dla klientkiOsoba, każdy wpis
Jeden commitJedna zmiana, która może być wieloma commitamiZasady squash, lub osoba

Znacznik mówi to narzędziu; nie mówi tego wywołującemu, co jest tematem jak deprecjonować API i czym jest zmiana łamiąca kompatybilność. To mała specyfikacja i warto ją stosować, nawet jeśli nigdy nic z niej nie generujecie, bo wymusza jedną decyzję na commit: czy to zmiana, którą widzą użytkownicy, czy nie.

Gdzie zatrzymują się conventional commits?

Zatrzymują się na zdaniu. Wszystko, co konwencja przechwytuje, to metadane o zmianie; sama zmiana wciąż jest opisana słownictwem recenzentki.

Komunikaty commitów są pisane dla recenzentek. fix(auth): reject expired refresh tokens jest poprawny i nic nie mówi klientce. Czytelniczka changelogu chce “zostaniesz wylogowana, gdy sesja faktycznie wygaśnie, zamiast widzieć sporadyczne 401”.

Scope’y są wewnętrzne. exports, auth, ingest to nazwy modułów. Są stabilne, co czyni je dobrymi do grupowania, i bez znaczenia dla kogokolwiek spoza bazy kodu.

Jedna zmiana to często wiele commitów. Funkcja zmergowana w jedenastu commitach tworzy jedenaście wpisów, dziesięć z nich to szum, a zgniatanie ich, by to ukryć, traci historię recenzji.

chore to kosz, nie kategoria. Aktualizacje zależności, zmiany CI i zmiany nazw trafiają tam wszystkie, a niektóre mają znaczenie dla użytkowników, podczas gdy większość nie.

Więc: konwencja daje wam typ, scope i status breaking za darmo, i pozostawia sformułowanie, grupowanie i selekcję całkowicie otwarte. Te trzy są changelogiem. Kto naprawdę jest właścicielem wpisu w changelogu opisuje, kto powinien zająć się tym sformułowaniem, grupowaniem i selekcją, skoro sama konwencja nie ma na ten temat zdania.

Jak generuje się changelog z conventional commits?

W dwóch warstwach, a druga musi być obowiązkowa.

Warstwa pierwsza, automatyczna. Przy merge’u wyprowadźcie szkicowy wpis z commita: typ zmapowany na typ changelogu (feat na Added, fix na Fixed, znacznik breaking na Changed plus flaga), scope zachowany jako metadane zamiast tekstu, link do PR. Umieśćcie go w sekcji Unreleased, o którą prosi Keep a Changelog.

Warstwa druga, ludzka, i wymagana. Zanim wydanie wyjdzie, każdy szkicowy wpis albo dostaje jednolinijkowe przepisanie w słownictwie użytkownika, albo jest oznaczany jako wewnętrzny i usuwany z publicznego widoku. To krok, który ludzie próbują pominąć, a pominięcie go produkuje changelogi, które czyta się jak diff.

Ważny szczegół projektowy to fakt, że warstwa druga nie jest opcjonalna w pipeline. Jeśli wydanie można wyciąć z nieedytowanymi szkicami, tak się stanie, w tygodniu, gdy wszyscy są zajęci. Które kroki należą do maszyny, a które do osoby, to cała treść automatyzacji changelogu.

Wycinanie wydania to też moment, w którym tag git, wydanie i ten wpis w changelogu albo się zgadzają, albo zaczynają rozjeżdżać; tagi git, wydania i twój changelog opisuje, jak utrzymać te trzy w synchronizacji.

Trzy pułapki

Squash merge zjada stopki. Jeśli wasza platforma zgniata z tytułem PR jako komunikatem, stopka BREAKING CHANGE: z commita wewnątrz tej gałęzi znika, a wasze narzędzia po cichu przestają widzieć zmianę łamiącą kompatybilność. Sprawdźcie, co faktycznie zachowuje wasz szablon squasha.

Commity revert produkują widma wpisów. fix, który jest cofnięty następnego dnia, generuje wpis dla czegoś, co nigdy nie zostało wydane, chyba że wyprowadzanie uwzględnia revert’y. Większość narzędzi tego nie robi.

Podniesienie wersji i changelog rozjeżdżają się. Jeśli wersja jest obliczana z commitów, a changelog jest pisany ręcznie potem, rozjeżdżają się w ciągu około dwóch wydań. Obliczajcie oba w tym samym przebiegu albo zaakceptujcie, że jedno z nich jest błędne.

Jeśli chcesz mechaniczną część bez pipeline’u

Nasz generator changelogu wykonuje krok wyprowadzania w przeglądarce: wklej commity, otrzymaj pogrupowane, otypowane wpisy. Jest celowo deterministyczny i całkowicie po stronie klienta, więc commity, które wklejacie, nigdy nie opuszczają waszej maszyny, co ma znaczenie, gdy komunikaty pochodzą z prywatnego repozytorium. Uczciwie wykonuje połowę zbierania i nie próbuje warstwy drugiej, ponieważ warstwa druga to osąd, a narzędzie, które go udaje, produkuje dokładnie ten changelog, przeciwko któremu argumentuje ten artykuł.

Dla wersji pipeline, narzędzia do changelogu obejmuje to, co istnieje.

Podsumowanie

Conventional commits odpowiadają na “jakiego rodzaju zmiana jest to” niezawodnie i tanio. Nie odpowiadają na “co powinniśmy powiedzieć ludziom”, i żadna ilość narzędzi nad komunikatem commita tego nie zrobi, ponieważ informacja nigdy nie była w komunikacie commita. Zabudżetujcie przepisanie.

FAQ

Czy conventional commits generują changelog automatycznie? Generują szkic automatycznie: otypowane, ze scope’em, połączone wpisy. Sformułowanie dla klientki, grupowanie i decyzja, co pominąć, wciąż potrzebują osoby, a pipeline, który pomija ten krok, publikuje komunikaty commitów.

Które typy conventional commit pojawiają się w changelogu? feat i fix zawsze, jako Added i Fixed. perf zwykle, jako Changed. chore, docs, refactor, test, build i ci są domyślnie wewnętrzne i pojawiają się tylko, gdy osoba promuje jeden z nich.

Jak conventional commits oznaczają zmianę łamiącą kompatybilność? ! po typie lub scope (feat(api)!: ...), lub stopka BREAKING CHANGE: w treści commita. Oba gubią się, jeśli squash merge zachowuje tylko tytuł PR.

Czy potrzebujesz conventional commits, by zautomatyzować changelog? Nie. Etykiety PR, szablony PR i linki do issue niosą te same metadane dla zespołów, które mergują przez pull request. Conventional commits to najtańsza opcja, gdy jednostką zmiany jest commit.


Twierdzenia techniczne w tym artykule nie zostały niezależnie zweryfikowane. Jeśli coś się nie zgadza, daj nam znać, a poprawimy to.

Powiązane w changeloop: Generator changeloga, Porównanie narzędzi do changeloga

changeloop
Zespół, który tworzy changelog zamykający pętlę. Użytkownicy o coś proszą, Twój zespół to dostarcza, proszący się dowiaduje.