Articles

Udany model rozgałęziania w Git

Posted on

Nota refleksyjna (5 marca 2020)

Model ten został wymyślony w 2010 roku, czyli już ponad 10 lat temu i niedługo po powstaniu samego Git. W ciągu tych 10 lat, git-flow (model rozgałęzienia opisany w tym artykule) stał się niezwykle popularny w wielu zespołach programistycznych do tego stopnia, że ludzie zaczęli traktować go jak swego rodzaju standard – ale niestety również jako dogmat lub panaceum.

W ciągu tych 10 lat, sam Git zdobył świat szturmem, a najpopularniejszy typ oprogramowania tworzonego przy użyciu Gita przesuwa się coraz bardziej w stronę aplikacji internetowych – przynajmniej w mojej bańce filtracyjnej. Aplikacje webowe są zazwyczaj dostarczane w sposób ciągły, nie są cofane i nie trzeba wspierać wielu wersji oprogramowania działającego na wolności.

To nie jest klasa oprogramowania, którą miałem na myśli, kiedy pisałem ten blog 10 lat temu. Jeśli twój zespół zajmuje się ciągłym dostarczaniem oprogramowania, sugerowałbym przyjęcie znacznie prostszego przepływu pracy (jak GitHubflow), zamiast próbować wcielić git-flow do swojego zespołu.

Jeśli jednak budujesz oprogramowanie, które jest wyraźnie wersjonowane lub jeśli musisz wspierać wiele wersji swojego oprogramowania działającego w środowisku naturalnym, to git-flow może być nadal tak samo dobrym rozwiązaniem dla twojego zespołu, jak był dla ludzi w ciągu ostatnich 10 lat. W takim przypadku, proszę czytać dalej.

Na zakończenie, zawsze pamiętaj, że panacea nie istnieją. Weź pod uwagę swój własny kontekst. Nie bądź nienawistny. Zdecyduj sam.

W tym poście przedstawiam model rozwoju, który wprowadziłem do kilku moich projektów (zarówno w pracy jak i prywatnie) około rok temu, a który okazał się bardzo udany. Miałem zamiar napisać o tym już od jakiegoś czasu, ale nigdy tak naprawdę nie znalazłem czasu, aby zrobić to dokładnie, aż do teraz. Nie będę mówił o żadnych szczegółach projektu, a jedynie o strategii rozgałęziania i zarządzaniu wydaniami.

Dlaczego git? Ś

Dokładną dyskusję na temat zalet i wad Gita w porównaniu do scentralizowanych systemów kontroli kodu źródłowego można znaleźć na stronie internetowej. Toczy się tam wiele sporów o flamewary. Jako deweloper, preferuję Git ponad wszystkie inne narzędzia dostępne w dzisiejszych czasach. Z klasycznego świata CVS/Subversion, z którego pochodzę, scalanie/gałęziowanie zawsze było uważane za nieco przerażające („uważaj na konflikty scalania, one cię ugryzą!”) i coś, co robisz tylko raz na jakiś czas.

Ale z Gitem te działania są niezwykle tanie i proste, i są uważane za jedną z podstawowych części codziennego przepływu pracy. Na przykład w książkach o CVS/Subversion rozgałęzianie i scalanie jest najpierw omawiane w późniejszych rozdziałach (dla zaawansowanych użytkowników), podczas gdy w każdej książce o Gicie jest to już omówione w rozdziale 3 (podstawy).

W konsekwencji swojej prostoty i powtarzalności, rozgałęzianie i scalanie nie są już czymś, czego należy się obawiać. Narzędzia kontroli wersji mają za zadanie pomagać w rozgałęzianiu i scalaniu bardziej niż cokolwiek innego.

Dość o narzędziach, przejdźmy do modelu rozwoju. Model, który zamierzam tutaj zaprezentować, jest w zasadzie niczym więcej niż zestawem procedur, których każdy członek zespołu musi przestrzegać, aby dojść do zarządzanego procesu rozwoju oprogramowania.

Zdecentralizowany, ale scentralizowany Ś

Ustawienie repozytorium, którego używamy i które działa dobrze z tym modelem rozgałęziania, jest takie z centralnym repo „prawdy”. Zauważ, że to repo jest uważane tylko za centralne (ponieważ Git jest DVCS, nie ma czegoś takiego jak centralne repo na poziomie technicznym). Będziemy odnosić się do tego repo jako origin, ponieważ ta nazwa jest znana wszystkim użytkownikom Gita.

Każdy deweloper ciągnie i pcha do origin. Ale oprócz scentralizowanych relacji push-pull, każdy deweloper może również wyciągać zmiany od innych peerów, tworząc pod-zespoły. Na przykład, może to być przydatne, aby pracować razem z dwoma lub więcej programistami nad dużą nową funkcjonalnością, przed przedwczesnym wypchnięciem pracy w toku doorigin. Na powyższym rysunku widać podzespoły Alice i Bob, Alice i David oraz Clair i David.

Technicznie oznacza to nic innego, jak to, że Alice zdefiniowała zdalnego Gita o nazwie bob, wskazującego na repozytorium Boba i vice versa.

Główne gałęzie Ś

W samym rdzeniu, model rozwoju jest w dużym stopniu inspirowany istniejącymi modelami. Centralne repo posiada dwie główne gałęzie z nieskończonym czasem życia:

  • master
  • develop

Gałąź master w origin powinna być znana każdemu użytkownikowi Gita. Równolegle do gałęzi master istnieje inna gałąź o nazwie develop.

Uważamy, że origin/master jest główną gałęzią, w której kod źródłowyHEAD zawsze odzwierciedla stan gotowy do produkcji.

Uważamy, że origin/develop jest główną gałęzią, w której kod źródłowyHEAD zawsze odzwierciedla stan z najnowszymi dostarczonymi zmianami rozwojowymi dla następnego wydania. Niektórzy nazywają to „gałęzią integracyjną”. To jest miejsce, z którego budowane są wszelkie automatyczne, nocne kompilacje.

Gdy kod źródłowy w gałęzi develop osiągnie stabilny punkt i będzie gotowy do wydania, wszystkie zmiany powinny zostać scalone z powrotem do master w jakiś sposób, a następnie oznaczone numerem wydania. Jak to się robi w szczegółach, zostanie omówione dalej.

Zatem, za każdym razem, gdy zmiany są scalane z powrotem do master, jest to z definicji nowe wydanie produkcyjne. Mamy tendencję do bycia bardzo rygorystycznymi w tym zakresie, więc teoretycznie moglibyśmy użyć skryptu Git hook do automatycznego budowania i rozwijania naszego oprogramowania na naszych serwerach produkcyjnych za każdym razem, gdy pojawi się commit namaster.

Gałęzie wspierające Ś

Następnie do głównych gałęzi master i develop, nasz model rozwoju używa różnych gałęzi wspierających, aby pomóc w równoległym rozwoju pomiędzy członkami zespołu, ułatwić śledzenie funkcji, przygotować się do wydań produkcyjnych i pomóc w szybkim rozwiązywaniu problemów produkcyjnych na żywo. W przeciwieństwie do głównych gałęzi, te gałęzie zawsze mają ograniczony czas życia, ponieważ w końcu zostaną usunięte.

Różne typy gałęzi, których możemy używać to:

  • Gałęzie funkcjonalności
  • Gałęzie wydań
  • Gałęzie hotfix

Każda z tych gałęzi ma swój konkretny cel i jest związana ścisłymi regułami dotyczącymi tego, które gałęzie mogą być ich gałęziami źródłowymi, a które muszą być ich celami scalania. Omówimy je za chwilę.

Nie są to w żadnym wypadku gałęzie „specjalne” z technicznego punktu widzenia. Typy gałęzi są skategoryzowane przez to jak ich używamy. Są to oczywiście zwykłe gałęzie starego Gita.

Gałęzie funkcji Ś

Może rozgałęziać się od:developMusi scalać się z powrotem do:developKonwencja nazewnictwa gałęzi: anything exceptmasterdeveloprelease-*, orhotfix-*

Gałęzie fabularne (lub czasami nazywane gałęziami tematycznymi) są używane do rozwijania nowych funkcji dla nadchodzącego lub odległego przyszłego wydania. W momencie rozpoczęcia rozwoju funkcji, docelowe wydanie, w którym ta funkcja zostanie umieszczona, może być nieznane w tym momencie. Istotą gałęzi funkcji jest to, że istnieje ona tak długo, jak długo rozwijana jest dana funkcja, ale w końcu zostanie scalona z powrotem do develop (aby definitywnie dodać nową funkcję do nadchodzącego wydania) lub odrzucona (w przypadku rozczarowującego eksperymentu).

Gałęzie fabularne zazwyczaj istnieją tylko w repozytoriach deweloperskich, nie w origin.

Tworzenie gałęzi fabularnej Ś

Przy rozpoczynaniu pracy nad nową funkcjonalnością, należy odgałęzić się od gałęzi develop.

$ git checkout -b myfeature developSwitched to a new branch "myfeature"

Włączanie ukończonej cechy do gałęzi develop Ś

Ukończone cechy mogą zostać scalone do gałęzi develop, aby definitywnie dodać je do nadchodzącego wydania:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff myfeatureUpdating ea1b82a..05e9557(Summary of changes)$ git branch -d myfeatureDeleted branch myfeature (was 05e9557).$ git push origin develop

Flaga --no-ff powoduje, że scalenie zawsze tworzy nowy obiekt commit, nawet jeśli scalenie mogłoby być wykonane z fast-forward. Pozwala to uniknąć utraty informacji o historycznym istnieniu danej gałęzi i grupuje wszystkie polecenia, które razem dodały daną cechę. Porównaj:

W tym drugim przypadku, nie jest możliwe sprawdzenie w historii Gita, które z commitów razem zaimplementowały cechę – musiałbyś ręcznie przeczytać wszystkie logi. Odwracanie całej cechy (tj. grupy commitów), jest prawdziwym bólem głowy w tej drugiej sytuacji, podczas gdy jest to łatwe do zrobienia, jeśli użyta została flaga--no-ff.

Tak, stworzy to kilka dodatkowych (pustych) obiektów commit, ale zysk jest znacznie większy niż koszt.

Gałęzie Release Ś

Może rozgałęziać się od:developMusi łączyć się z powrotem do:developimasterKonwencja nazewnictwa gałęzi:release-*

Gałęzie Release wspierają przygotowanie nowego wydania produkcyjnego. Pozwalają w ostatniej chwili postawić kropkę nad „i” oraz przekroczyć „t”. Ponadto, pozwalają na forminor bug fixes i przygotowanie meta-danych dla wydania (numer wersji, builddates, etc.). Wykonując wszystkie te czynności na gałęzi wydania, gałąź develop jest gotowa do przyjęcia funkcji dla następnego dużego wydania.

Kluczowym momentem do odgałęzienia nowej gałęzi wydania z develop jest moment, w którymndevelop (prawie) odzwierciedla pożądany stan nowego wydania. Przynajmniej wszystkie funkcje, które są przeznaczone dla przyszłego wydania muszą być scalone dodevelop w tym momencie. Wszystkie cechy przeznaczone dla przyszłych wydań nie mogą – muszą poczekać do momentu, gdy gałąź wydania zostanie rozgałęziona.

To właśnie na początku gałęzi wydania nadchodzące wydanie otrzymuje numer wersji – nie wcześniej. Do tego momentu, gałąź develop odzwierciedlała zmiany dla „następnego wydania”, ale nie jest jasne, czy to „następne wydanie” ostatecznie stanie się 0.3 czy 1.0, dopóki gałąź wydania nie zostanie uruchomiona. Ta decyzja jest podejmowana w momencie uruchomienia gałęzi release i jest realizowana przez zasady projektu dotyczące podnoszenia numerów wersji.

Tworzenie gałęzi release Ś

Gałęzie release są tworzone z gałęzi develop. Na przykład, powiedzmy, że wersja 1.1.5 jest aktualnym wydaniem produkcyjnym, a my mamy duży release. Stan develop jest gotowy do „następnego wydania” i zdecydowaliśmy, że będzie to wersja 1.2 (a nie 1.1.6 lub 2.0). Więc webranch off i nadaj gałęzi release nazwę odzwierciedlającą nowy numer wersji:

$ git checkout -b release-1.2 developSwitched to a new branch "release-1.2"$ ./bump-version.sh 1.2Files modified successfully, version bumped to 1.2.$ git commit -a -m "Bumped version number to 1.2" Bumped version number to 1.21 files changed, 1 insertions(+), 1 deletions(-)

Po utworzeniu nowej gałęzi i przełączeniu się do niej, podbijamy numer wersji.Tutaj, bump-version.sh jest fikcyjnym skryptem powłoki, który zmienia niektóre pliki w kopii roboczej, aby odzwierciedlić nową wersję. (Może to być oczywiście zmiana ręczna – chodzi o to, że niektóre pliki się zmieniają). Następnie, podbity numer wersji jest zatwierdzany.

Ta nowa gałąź może istnieć przez jakiś czas, aż do momentu, gdy wydanie zostanie definitywnie rozwinięte. W tym czasie, poprawki błędów mogą być stosowane w tej gałęzi (a nie w gałęzi develop branch). Dodawanie dużych nowych funkcji jest surowo zabronione. Muszą one zostać scalone do develop, a więc poczekać na następne duże wydanie.

Zakończenie gałęzi wydania Ś

Kiedy stan gałęzi wydania jest gotowy do stania się prawdziwym wydaniem, należy wykonać kilka czynności. Po pierwsze, gałąź release jest scalana domaster (ponieważ każdy commit na master jest z definicji nowym wydaniem, pamiętaj). Następnie, ten commit na master musi być oznaczony tagiem, aby ułatwić przyszłe odniesienia do tej historycznej wersji. Na koniec, zmiany dokonane w gałęzi release muszą zostać scalone z powrotem do develop, tak aby przyszłe wydania również zawierały te poprawki.

Pierwsze dwa kroki w Git:

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)$ git tag -a 1.2

Wydanie jest już zrobione i oznaczone do przyszłych odniesień.

Edit: Równie dobrze możesz użyć flag -s lub -u <key> do kryptograficznego oznaczenia swojego tagu.

Aby zachować zmiany wprowadzone w gałęzi release, musimy jednak scalić je z powrotem do develop. W Git:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)

Ten krok może równie dobrze prowadzić do konfliktu scalania (prawdopodobnie nawet, ponieważ zmieniliśmy numer wersji). Jeśli tak, napraw to i popełnij.

Teraz naprawdę skończyliśmy, a gałąź release może zostać usunięta, ponieważ nie jest nam już potrzebna:

$ git branch -d release-1.2Deleted branch release-1.2 (was ff452fe).

Gałęzie hotfix ś

Może rozgałęziać się od:masterMusi scalać się z powrotem do:developimasterKonwencja nazewnictwa gałęzi:hotfix-*

Gałęzie hotfix są bardzo podobne do gałęzi release w tym sensie, że również mają na celu przygotowanie do nowego wydania produkcyjnego, aczkolwiek nieplanowanego. Powstają one z potrzeby natychmiastowego działania w przypadku niepożądanego stanu wersji produkcyjnej. Kiedy krytyczny błąd w wersji produkcyjnej musi być natychmiast usunięty, gałąź hotfix może być odgałęziona od odpowiedniego znacznika na gałęzi master, która oznacza wersję produkcyjną.

Sensem jest to, że praca członków zespołu (na gałęzi develop branch) może być kontynuowana, podczas gdy inna osoba przygotowuje szybką poprawkę produkcyjną.

Tworzenie gałęzi hotfix ś

Gałęzie hotfix są tworzone z gałęzi master branch. Na przykład, powiedzmy, że wersja 1.2 jest bieżącym wydaniem produkcyjnym działającym na żywo i powodującym problemy z powodu poważnego błędu. Ale zmiany na develop są jeszcze niestabilne. Możemy więc odgałęzić gałąź hotfix i zacząć naprawiać problem:

$ git checkout -b hotfix-1.2.1 masterSwitched to a new branch "hotfix-1.2.1"$ ./bump-version.sh 1.2.1Files modified successfully, version bumped to 1.2.1.$ git commit -a -m "Bumped version number to 1.2.1" Bumped version number to 1.2.11 files changed, 1 insertions(+), 1 deletions(-)

Nie zapomnij podbić numeru wersji po odgałęzieniu!

Potem naprawić błąd i przekazać poprawkę w jednym lub więcej oddzielnych commitach.

$ git commit -m "Fixed severe production problem" Fixed severe production problem5 files changed, 32 insertions(+), 17 deletions(-)

Zakończenie gałęzi hotfix Ś

Po zakończeniu, bugfix musi zostać scalony z powrotem do master, ale również musi zostać scalony z powrotem do develop, aby zabezpieczyć, że poprawka zostanie włączona do następnego wydania. Jest to zupełnie podobne do tego, jak kończone są releasebranche.

Po pierwsze, zaktualizuj master i oznacz wydanie.

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)$ git tag -a 1.2.1

Edit: Równie dobrze możesz użyć flag -s lub -u <key> do kryptograficznego oznaczenia swojego tagu.

Następnie uwzględnij również bugfix w develop:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)

Jednym wyjątkiem od reguły jest to, że gdy gałąź wydania już istnieje, zmiany w hotfixie muszą być scalone do tej gałęzi wydania, zamiast do develop. Wsteczne scalanie poprawki do gałęzi wydania ostatecznie doprowadzi do tego, że poprawka do błędu zostanie scalona do develop również, gdy gałąź wydania zostanie ukończona. (Jeśli praca w develop natychmiast wymaga tego bugfixa i nie może czekać na zakończenie gałęzi release, możesz bezpiecznie scalić bugfixinto develop już teraz.)

Na koniec, usuń tymczasową gałąź:

$ git branch -d hotfix-1.2.1Deleted branch hotfix-1.2.1 (was abbe5d6).

Podsumowanie Ś

Chociaż nie ma nic naprawdę szokującego nowego w tym modelu rozgałęzienia, figura „bigpicture”, od której zaczął się ten post, okazała się być ogromnie użyteczna w naszych projektach. Tworzy on elegancki model mentalny, który jest łatwy do zrozumienia i pozwala członkom zespołu rozwijać wspólne zrozumienie procesów rozgałęziania i uwalniania.

Wysokiej jakości wersja PDF rysunku znajduje się tutaj. Śmiało powieś go na ścianie, aby móc się do niego szybko odwołać w każdej chwili.

Uaktualnienie: I dla każdego, kto o to prosił: oto klucz thegitflow-model.src.key głównego obrazu diagramu (Apple Keynote).


Git-branching-model.pdf

Inne posty na tym blogu

  • Narzędzia Git do codziennego użytku
  • Wstęp do dekoderów
  • Dług techniczny to prawdziwy dług
  • Piękny kod
  • Piękna mapa

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *