Git czy SVN? Jak firma Nuance Healthcare wybrała model tworzenia gałęzi w Git?
Matt Shelton
Developer Advocate
Ten gościnny wpis Matta Sheltona z Nuance Healthcare jest pierwszym z serii wpisów na temat przejścia jego zespołu z Subversion do Git, przyczynach tej decyzji i napotkanych problemach. Matt mówił też o tym podczas konferencji Atlassian Summit 2015. W tej serii przedstawimy wszystko, na co nie starczyło mu czasu podczas 30-minutowej prelekcji, łącznie z dodatkowym kontekstem.
Tło
Mój zespół jest częścią działu opieki zdrowotnej w Nuance. Jesteśmy rozproszeni pod względem geograficznym, pracując z kilku biur oraz naszych domów na wschodnim wybrzeżu Stanów Zjednoczonych oraz w biurze w Pune. Opracowujemy usługi sieciowe Java, dostarczając rozwiązania NLP[1] na rynek opieki zdrowotnej.
W przeważającej części konsumentami naszych usług są inne firmy produkujące oprogramowanie medyczne (w tym my sami), takie jak dostawcy rejestratorów parametrów życiowych czy firmy zajmujące się analizami w dziedzinie opieki zdrowotnej. Niektóre produkty sprzedajemy bezpośrednio do szpitali, a użytkownicy końcowi aplikacji to zarówno lekarze, jak i personel ds. rozliczeń medycznych. „Normalni” ludzie, tacy jak Ty czy ja, nigdy nie dotykają oprogramowania, które tworzy mój zespół.
Nasz zespół kilkakrotnie miał styczność z kombinacjami produktów do zarządzania cyklem życia aplikacji. Zaczęliśmy od kombinacji Rally Enterprise i Seapine TestTrack Pro, przez 14 miesięcy trudziliśmy się z Rational Team Concert, a w końcu zdecydowaliśmy się na pełną migrację do produktów Atlassian (Jira, Confluence, Bamboo, Crucible i Bitbucket). Dawniej korzystaliśmy z Subvesion 1.4/1.5 jako naszego rozwiązania SCM, wykorzystując quasi-normalną strukturę opartą na gałęzi głównej, gałęziach i tagach. Od zawsze korzystaliśmy z Maven do zarządzania naszymi projektami kompilacji i zależnościami, a jakiś czas temu przeszliśmy z rozwiązania Jenkins na Bamboo w przypadku ciągłej integracji (CI), aby korzystać ze ściślejszych integracji z systemem Jira, a także elastycznych funkcji agentów wdrożeń i kompilacji. Wszystko, z czego (obecnie) korzystamy, znajduje się z pewnych powodów za zaporą[2].
materiały pokrewne
Jak przenieść pełne repozytorium Git
POZNAJ ROZWIĄZANIE
Poznaj środowisko Git z rozwiązaniem Bitbucket Cloud
Git czy SVN?
Obsługujemy około dziesięciu osobnych produktów z czterech rodzin produktów, a właściciele tych produktów zawsze walczą o jak najlepszą pozycję podczas ustalania priorytetów i harmonogramów. Cieszymy się z dużego zapotrzebowania na naszą pracę i wcale się nie skarżymy, jednak wymusza to również konieczność finalizowania wydań z dziwną kadencją czy zmiany kierunku działania w środku sprintu[3].
W pewnych momentach nasz proces programistyczny naprawdę wydawał się mocno ograniczający. Mój zespół regularnie prowadził rozmowy, które miały mniej więcej następujący przebieg:
Ja: Musimy dostarczyć wydanie 1.8.0 zespołowi QA teraz i przeprowadzić testowanie regresji, aby klient Foo mógł w przyszłym tygodniu przejść na wersję beta. Programista: Ja wciąż pracuję nad ABC-123 w głównej gałęzi. Nie jest jeszcze gotowe. Ja: Foo nie potrzebuje ABC-123. Możemy dodać to do kolejnego wydania. Programista: Ale pracuję nad tym od tygodni. Nie da się wskazać konkretnego punktu do utworzenia gałęzi, aby sfinalizować wydanie. Ja: Cóż, musisz ręcznie ściągnąć wszystkie swoje zmiany. Masz dwie godziny, inaczej zespół QA nie skończy na czas.
Wiem, brzmię jak kretyn! Wcale tego nie chciałem. Choć trochę koloryzuję dla podkreślenia problemu, o którym mówię, ale naprawdę musieliśmy znaleźć sposób, jak wziąć kod znajdujący się w jednym miejscu na chwilę z tego miejsca, aby móc sfinalizować wydanie, a następnie umieścić go tam z powrotem na potrzeby kolejnego wydania[4]. I tak było bez przerwy.
Wiem, niektórzy pewnie myślą sobie „Subversion obsługuje gałęzie, Matt…”. Zdecydowanie tak i czasami korzystaliśmy z nich w SVN 1.4 i 1.5. Tworzenie gałęzi w SVN przebiega gładko, ale scalanie naprawdę potrafi być uciążliwe. W miarę rozwoju SVN poprawa była zdecydowanie widoczna. Jednak zdawaliśmy sobie sprawę, że są opcje, które lepiej sprawdzą się w naszym przypadku. Gdy stanęliśmy więc w obliczu wyboru między systemami SVN i Git, zdecydowaliśmy się na Git.
Tak na marginesie: pobieżnie przyjrzeliśmy się najnowszej wersji SVN (1.8 na tamten moment), aby sprawdzić, czy jest na tyle rozbudowana, aby rozwiązać nasze problemy, ale nie byliśmy w pełni usatysfakcjonowani. Jedna z grup, z którymi współpracujemy, ma dużą instalację Perforce, która w dużej mierze zaspokajałaby nasze potrzeby, ale po prostu nie byłem w stanie przełknąć kosztów licencji. Przez moment zastanawialiśmy się też nad rozwiązaniem Mercurial, ale ostatecznie styczność istniejącego zespołu z Git wystarczyła, abyśmy utwierdzili się w przekonaniu, że zmierzamy we właściwym kierunku.
Nie będę owijać tego w bawełnę — narzędzia Atlassian zdecydowanie najlepiej sprawdzają się w tych zespołach, które korzystają z Git. Inne rozwiązania do zarządzania kodem źródłowym działają dobrze. Nasza integracja z SVN była wystarczająca w tym sensie, że zapewniała powiązania z miejscami, gdzie wprowadzano zmiany z konkretnych historyjek użytkowników. Jednak możliwości integracji dla zespołów korzystających z Bitbucket[5] były nie tylko większe, ale także bardziej naturalne na poziomie interfejsu i środowiska programistycznego Jira Software — podobnie jak w przypadku Bamboo.
Mając to na uwadze i po obejrzeniu kilku iście gwiazdorskich demonstracji w trakcie konferencji Summit 2013, zdecydowanie zachęciłem zespół do obrania tej drogi. Nikt nie zgłaszał sprzeciwów i mieliśmy już licencje, które pozwalały na zmianę.
Wybór modelu tworzenia gałęzi w Git
Po podjęciu decyzji o wprowadzeniu tej zmiany pierwszą napotkaną przez nas trudnością okazał się wybór modelu tworzenia gałęzi w Git, który wdrożymy w zespole. Szczegółowe informacje na temat tego, czym jest model tworzenia gałęzi, można uzyskać w mikrowitrynie Atlassian dotyczącej systemu Git, a także oglądając tę wspaniałą prezentację z konferencji Summit 2013. Krótko mówiąc, jest to opis sposobu korzystania z gałęzi Git do usprawnienia przepływu prac programistycznych.
W SVN mieliśmy model tworzenia gałęzi, który nazywam „utwórz gałąź, gdy dojdziesz do wniosku, że — o zgrozo! — jej potrzebujesz”:
- Najnowszy kod znajduje się w gałęzi
trunk
. Wydania z gałęzi głównej (trunk) będą ponumerowaneA.B.0-{build}
. - Jeśli konieczna jest poprawka do wydania opartego na gałęzi głównej (np. mamy błąd w wersji 1.2.0-64), tworzona jest gałąź i na jej podstawie finalizuje się wydania
A.B.C-{build}
, gdzieC
zwiększa się po każdym udostępnionym wydaniu. Te gałęzie mogą nigdy nie istnieć dla danej wersjiA.B
albo może ich być więcej niż jedna. - Ponadto w katalogu tagów oznaczamy tagiem każde wydanie.
Dygresja na temat wersji: Wiele lat temu, gdy dopiero zdobywałem szlify w zarządzaniu zespołem programistycznym, nasz inżynier ds. wydań miał system wersjonowania, który był… jakby to powiedzieć… naprawdę nieintuicyjny. Zasadniczo każde wydanie było poprawką poprzedniego (A.B.n), bez uwzględniania miejsca, z którego pochodziła poprawka. Ustalenie, skąd coś się wzięło, a w niemal każdym przypadku także kolejności wydań, wymagało skorzystania z polecenia svn log
. W celach informacyjnych wieszaliśmy na ścianie wydruk całego drzewa. Ponadto numery wydań publicznych wyglądały mniej więcej tak: 3.0, 3.1, 3.5, 4.0 itp. lub przybierały inną formę, której mogli spodziewać się klienci. Należy przy tym pamiętać, że mój zespół tworzy usługi sieciowe, a nie produkty pudełkowe. Nasze interfejsy API mają charakter umowy. Kilka lat temu przekonałem zarząd, że kompilacje, a zatem także wydania mojego zespołu będą oznaczane zgodnie z regułami wersjonowania semantycznego. Kilkakrotnie musiałem bronić swojego stanowiska przed kierownictwem wyższego szczebla, ale teraz wszyscy rozumieją, dlaczego reguły są takie, jakie są, i nie wracamy do tego, co było. Partnerzy doceniają ten rodzaj przejrzystości.
Wspominałem wcześniej o problemie polegającym na tym, że pracowaliśmy nad wydaniem (powiedzmy 1.2.0
), a zbliżając się do daty wydania wciąż mieliśmy w toku prace nad jakąś funkcją. Musieliśmy wówczas ściągać ten kod, sfinalizować wydanie, utworzyć gałąź branches/1.2.1
, a potem scalić kod z powrotem, modląc się w duchu, aby w międzyczasie żaden dysk twardy nie uległ awarii[6].
Samo usunięcie całej funkcji ze wspólnej gałęzi głównej już jest trudne. Każdy miał dość, gdy musiał to robić. Polecenie svn blame
bywa przydatne, podobnie jak dobre narzędzie do tworzenia wersji różnicowych, ale korzystanie z nich i tak jest irytujące. Często brałem to do siebie z poczuciem, że to złe planowanie doprowadziło nas do tego, że nie udało się ukończyć wszystkiego przed terminem kolejnego wydania[7]. Mój zespół borykał się z tymi trudnościami już wystarczająco długo.
Czasami wręcz przesadzaliśmy z poprawnością w celu uniknięcia trudności i prosiliśmy programistów, aby przez kilka dni nie dotykali klawiatury (wirtualne zamrożenie kodu, mówiąc inaczej), tylko po to, aby nie zanieczyścić gałęzi głównej tuż przed wydaniem.
Zdawaliśmy sobie sprawę, że potrzebujemy przynajmniej gałęzi funkcji. Istnieje prosty model tworzenia gałęzi w Git, który można zastosować: gałąź main dla wszystkiego co w środowisku produkcyjnym oraz gałęzie funkcji dla każdej funkcji, każdego błędu itp. Zespoły muszą zarządzać kolejnością scalania, aby mieć pewność, że gałąź main będzie zawierała to, co zostało przewidziane w wydaniu. Zasadniczo jest to proces zbliżony do naszego dotychczasowego sposobu pracy, z nieco lepszą izolacją funkcji, jednak my pragnęliśmy swobody, która pozwoliłaby nam rozwinąć skrzydła.
W naszym środowisku często musimy mieć w środowisku produkcyjnym kilka wersji i wprowadzać poprawki usterek w wydaniu, które jest o 2–3 wersje pomocnicze starsze od wydania, nad którym aktualnie pracujemy. Zatem oprócz gałęzi funkcji potrzebowaliśmy także swego rodzaju gałęzi wydania lub czegoś podobnego, co pozwoliłoby nam naprawiać błędy ze starszych wydań. Wprowadza poprawki w długofalowych gałęziach pomocniczych, a następnie scala je ze strumieniem gałęzi, dzięki czemu poprawka trafia do wszystkich obsługiwanych strumieni.
Ich model wyglądał naprawdę dobrze, więc przeprowadziliśmy kilka prototypowych interakcji z tym modelem, aby sprawdzić, czy będzie odpowiadał naszym potrzebom. „Zabójczą aplikację” stanowi dla nich stopniowe scalanie poprawki aż do poziomu ich gałęzi programistycznej. Chociaż podobała nam się ta koncepcja, za każdym razem, gdy próbowaliśmy ją wykorzystać, napotykaliśmy taki czy inny problem z naszymi zależnościami Maven. Ponadto zazwyczaj nie mogliśmy zagwarantować, że chcemy prostego scalenia prac z jednej wersji z inną. W niektórych przypadkach musieliśmy zaimplementować tę samą poprawkę w nieco inny sposób w różnych wersjach, przez co bezpośrednie scalenie nie było możliwe.
Kilku członków zespołu zdecydowanie opowiadało się za odmianą tego modelu noszącą nazwę „git-flow”. Git-flow to zbiór konwencji nazewnictwa gałęzi i wytycznych dotyczących scalania autorstwa Vincenta Driessena. Wydał się on zespołowi bardzo naturalny, a jego struktura nam odpowiadała, ponieważ eliminowała wiele wątpliwości typu „co mam zrobić, gdy muszę zrobić x?”. Odpowiedzi były ogólnie bardzo oczywiste. Nie będę tutaj wyjaśniać meandrów git-flow. Więcej informacji na ten temat znajdziesz w samouczku Atlassian.
Jedynym mankamentem modelu git-flow była dla nas kwestia postępowania z długofalowymi wydaniami w środowisku produkcji. Gałąź main jest stale rozwijana, zatem nie mogliśmy wykorzystać przepływu pracy git-flow dotyczącego wprowadzania poprawek do naprawiania błędów w starszych wydaniach. Z drugiej strony nie zawsze chcieliśmy tworzyć gałąź pomocniczą.
W większości przypadków powinno wystarczyć utworzenie łatki jedynie do najnowszego wydania w środowisku produkcyjnym. Gałąź pomocnicza jest potrzebna tylko wtedy, gdy musimy cofnąć się dalej lub zachować zgodność z tego czy innego powodu. Przeanalizowaliśmy dokładniej ten ostatni przypadek użycia i opracowaliśmy kryteria wyboru użycia gałęzi pomocniczej zamiast poprawki i uaktualnienia w formie wersji pomocniczej:
1. Tego kodu nie da się w prosty sposób scalić z powrotem z gałęzią programistyczną.
2. Partner/klient nie jest w stanie obsłużyć zmiany interfejsu udostępnionej w najnowszym wydaniu.
3. Istnieje zależność wewnętrzna, której nie można zmienić[8].
Obydwa pakiety rozszerzenia git-flow[9] obsługują koncepcję gałęzi pomocniczej, której nie ma w pierwotnej wersji roboczej modelu git-flow, jednak stała się ona na tyle popularna, że warto było ją uwzględnić.
Model git-flow oferował przepływ pracy, który nam się spodobał, wraz z potrzebnym wsparciem dla narzędzi. W kolejnym poście omówię, co się stało, gdy faktycznie spróbowaliśmy go zastosować w projekcie weryfikacji koncepcji, który wykorzystaliśmy do przedstawienia naszego procesu programistycznego. To była… dobra okazja do nauki!
[1]: Przetwarzanie języka naturalnego. MOŻEMY CZYTAĆ W TWOICH MYŚLACH. (Nie. Tak naprawdę to nie).
[2]: Oferta Atlassian Cloud jest pod wieloma względami atrakcyjna, ale na ten moment nie możemy ani na chwilę wypuszczać z rąk naszych serwerów i danych. Choć osobiście niewiele mamy do czynienia z chronionymi informacjami zdrowotnymi, to wykorzystuje je nasze oprogramowanie, dlatego trzeba im zapewnić możliwie jak największe bezpieczeństwo.
[3]: Cii… nie mówcie Kenowi Schwaberowi.
[4]: Co mogło następować zaledwie kilka dni później.
[5]: Dawniej znany jako Stash. Witaj, Atlassianowy rebrandingu!
[6]: Wiem, że zawsze mogliśmy ściągnąć kod z poprzedniego commita. Tylko żartowałem.
[7]: Zazwyczaj tak się nie działo — zasadniczo taka sytuacja następowała, gdy ramy czasowe kogoś innego ulegały zmianie i musieliśmy szybko reagować.
[8]: To jedna z tych rzeczy, o których nie mogę mówić na własnym blogu. Zaufaj mi. Mam „powody”.
[9]: Oryginalny pakiet opracowany przez Vincenta Driessena nie jest już wspierany. Jednak nowy podział jest aktualizowany regularnie.
Footnotes
[1]: Natural Language Processing. WE CAN READ YOUR THOUGHTS. (No. Not really.)
[2]: There is a lot that is attractive about Atlassian's cloud offerings, but we need to keep our fingers wrapped tightly around our servers and data for the time being. While we don't personally need to do much with PHI data, our software does and it's important to keep it as secure as possible.
[3]: Shhhh... don't tell Ken Schwaber.
[4]: Which might have only been a few days later anyway.
[5]: Formerly known as Stash. Hello, Atlassian Fall Rebranding!
[6]: I know we could always pull it out of the previous commit. I was kidding.
[7]: This wasn't usually the case - generally it was because someone else's timeframe moved up and we had to react quickly.
[8]: This is one of those things I can't get into on my own blog. Just trust me. "Reasons".
[9]: The original package by Vincent Driessen isn't being maintained any longer. A new fork, however, is regularly updated.
Udostępnij ten artykuł
Następny temat
Zalecane lektury
Dodaj te zasoby do zakładek, aby dowiedzieć się więcej na temat rodzajów zespołów DevOps lub otrzymywać aktualności na temat metodyki DevOps w Atlassian.