Wstęp do rozproszonej kontroli wersji na przykładzie Git’a


W standardowych (centralnych) systemach wersjonowania mamy zazwyczaj jedno główne repozytorium kodu, z którego korzysta cały zespół. Praca w projekcie wiąże się z ciągłym pobieraniem i wgrywaniem zmian i w związku z tym wymaga stałego połączenia z jednym centralnym serwerem. Cała historia zmian trzymana jest w centralnym repozytorium, członkowie zespołu lokalnie posiadają jedynie kopię źródeł.

Rozproszona kontrola wersji jest zorganizowana w zupełnie inny sposób. Repozytoriów jest tyle ilu członków zespołu. Każdy z nich lokalnie posiada pełne źródła projektu wraz z historią zmian. Wszystkie repozytoria są równe – żadne z nich nie jest w żaden sposób wyróżnione. To, z którego repozytorium na przykład zbudujemy wersję jest sprawą czysto umowną.

Rozpoczęcie pracy z projektem wiąże się ze stworzeniem nowej kopii projektu przez sklonowanie wybranego istniejącego repozytorium. Dalsza praca może odbywać się lokalnie. W wybranych momentach następuje scalanie zmian dokonywanych przez różnych członków zespołu.

Co zyskujemy dzięki rozproszeniu?

Przede wszystkim pracujemy lokalnie – tworzymy branche wtedy kiedy my ich potrzebujemy, nazywamy je tak jak chcemy (nie ma konfliktów nazw często spotykanych w repozytoriach centralnych), commitujemy zmiany kiedy uznamy jakąś część pracy za wykonaną. Pomimo pracy lokalnej jesteśmy cały czas pod kontrolą wersji – możemy zarządzać nawet małymi lokalnymi zmianami, które potencjalnie mogą zawierać błędy.

Zarządzamy zmianami a nie wersjami. Wbrew pozorom to bardzo duża zmiana w podejściu do zarządzania kodem – zamiast zastanawiać się jak uaktualnić jedną wersję tak, żeby była taka sama jak inna – zarządzamy zmianami – mówimy więc ‘daj mi swój zestaw zmian’. Przy takim podejściu mergowanie zmian staje się codziennością w związku z czym musi być wydajne i bezproblemowe i takie właśnie jest. Dzięki temu pośrednio zyskujemy też silne wsparcie dla nieliniowego rozwoju projektu.

Kompletny projekt wraz z historią zmian istnieje w wielu miejscach. Podczas zakładania (klonowania) projektu dostajemy pełną kopię repozytorium (wraz z historią zmian). Dzięki temu nie mamy jednego newralgicznego punktu, ale te same źródła w wielu miejscach.

Systemy rozproszonego zarządzania wersjami

Na rynku istnieje kilka systemów DVCS (Distributed Version Control System). Do głównych należą darmowe Mercurial, Bazaar, Git oraz komercyjny BitKeeper. Nie ma dużych różnic projektowych pomiędzy nimi (być może poza sposobem synchronizacji), są za to znaczące różnice w implementacji, które wpływają na wydajność i komfort pracy. Ciekawostką jest to, ze Bazaar może pracować zarówno jako repozytorium rozproszone jak i w modelu z centralnym repozytorium.

Ze względu na rosnącą popularność Git’a proponuję pouczyć się co to jest DVCS na jego przykładzie.

Git

Git jest szybkim rozproszonym systemem kontroli wersji dostępnym na licencji open source. Został zaprojektowany i pierwotnie stworzony przez Linusa Torvaldsa na potrzeby zarządzania źródłami jądra Linuxa.

Wśród głównych cech Git’a autorzy wymieniają to, że jest systemem rozproszonym, wspiera nieliniowy development, jest wydajny w dużych projektach oraz zapewnia spójność repozytorium przez zastosowanie algorytmów szyfrujących (SHA1).

Instalacja

Większość systemów linuxowych ma już gita w swoich repozytoriach pakietów (git-core). Wersję zbudowaną lub źródła można pobrać ze strony projektu (http://git-scm.com/download). Instalacja jest prosta i nie powinna przysporzyć problemów.

Po zainstalowaniu Git jest gotowy do zarządzania naszym kodem.

Jedyne co powinniśmy ustawić, to swoją nazwę użytkownika i e-mail:

Utworzenie repozytorium

Po założeniu katalogu z projektem wykonujemy polecenie git init. Spowoduje to utworzenie repozytorium – w katalogu z projektem powinien powstać katalog .git.

Za pomocą polecenia git status możemy sprawdzić aktualny stan naszego repozytorium. Powinniśmy dostać odpowiedź podobną do tej:

Dodanie/zmiana plików

Pliki dodajemy i zmieniamy w repozytorium za pomocą polecenia add:

Zatwierdzenie zmian

Wprowadzone zmiany zatwierdzamy za pomocą polecenia commit (uwaga: commit zatwierdza zmiany tylko w lokalnym repozytorium):

Historia zmian

W dowolnej chwili można podejrzeć historię zmian za pomocą polecenia log:

Możemy podejrzeć również listę różnic:

Branchowanie i mergowanie

Stworzenie brancha jest tak proste jak wykonanie instrukcji git branch [nazwa brancha]. Żeby przełączyć się na brancha wykonujemy polecenie git checkout [nazwa brancha].

W dowolnej chwili możemy sprawdzić w jakim branchu aktualnie się znajdujemy (ten oznaczony gwiazdką):

Dokonajmy teraz zmiany w naszym branchu xyz123 i dodajmy zmianę do brancha:

Powyższe dwie instrukcje możemy też zastąpić pojedynczą:

Po przejściu z powrotem do brancha głównego widzimy, że nie ma w nim wprowadzonych zmian:

Możemy teraz dociągnąć zmiany za pomocą polecenia merge:

Na koniec możemy podejrzeć drzewko zmian w narzędziu gitk

I usunąć gałąź xyz123

Tagowanie

Założenie taga to wykonanie polecenia Tag:

Lista tagów:

Następnie możemy stworzyć brancha z dowolnego taga:

Praca w zespole

Jak już wiemy w pracy z rozproszonym systemem kontroli wersji wszystkie repozytoria są równe, a wskazanie repozytorium, z którego na przykład budujemy wersję lub uruchamiany testy jest tylko kwestią umowy.

Załóżmy, że pracujemy w dwuosobowym zespole (user1, user2). User2 tworzy swoje lokalne repozytorium przez sklonowanie repozytorium należącego do user1:

Następnie user2 edytuje lokalnie jakieś pliki umieszcza je w swoim repozytorium:

Gdy skończy może powiadomić usera1 o możliwości zaciągnięcia zmian – wtedy user1 może dociągnąć zmiany z repozytorium usera2 za pomocą polecenia pull:

Polecenie pull powoduje pobranie zmian ze wskazanego repozytorium i zmergowanie ich z naszym kodem (fetch + merge). Wskazane jest zacommitowanie wszystkich lokalnych zmian przed wykonaniem operacji pull – pozwoli to na bezproblemowe przeprowadzenie merge’a.

Istnieje możliwość ściągnięcia zmian z innego repozytorium bez ich mergowania – służy do tego polecenie fetch:

Dla ułatwienia pracy można nadać alias zdalnemu repozytorium, z którym pracujemy:

Z poziomu swojego repozytorium możemy też ‘wepchać’ zmiany do repozytorium zdalnego – praca z Gitem przypomina wtedy pracę z repozytorium centralnym. Służy do tego polecenie push:

Co ważne – Git domyślnie nie pozwoli na wepchnięcie zmian do zdalnego repozytorium jeśli wskazany branch został w nim zacheckoutowany. Dostaniemy wtedy taki komunikat:

Możemy wtedy zrobić dwie rzeczy – albo w zdalnym repozytorium przejść (checkout) na inny branch lub przekształcić zdalne repozytorium w repozytorium centralne (bare), czyli takie, które zawiera jedynie katalog .git i nie zawiera żadnych zacheckoutowanych plików

Rebase

Niniejszy artykuł jest jedynie wprowadzeniem do zagadnienia rozproszonego kontrolowania źródeł, ale ponieważ za przykład wzięliśmy Git’a nie sposób nie wspomnieć o jeszcze jednej bardzo wartościowej i silnej funkcjonalności którą dostarcza – rebase.

Rebase to w uproszczeniu przesunięcie miejsca utworzenia brancha. Załóżmy, że mamy w projekcie branch główny oraz jakiś dodatkowy z funkcjonalnością przygotowywaną dla konkretnego klienta. Jeśli chcemy aby branch dedykowany dla klienta zawierał wszystkie zmiany wrzucane do brancha głównego musimy dokonywać merge’y.

Git daje nam możliwość wykonania operacji rebase, która spowoduje przesunięcie miejsca utworzenia brancha:

Po takiej operacji Git zapisze wszystkie commity od momentu utworzenia brancha jako patche w katalogu .git/rebase, zaktualizuje brancha tak, żeby wskazywał aktualną wersję w branchu głównym i na koniec zaaplikuje odłożone patche. Dzięki temu historia zmian będzie serią commitów bez merge’y.

Podsumowanie

Rozproszone systemy zarządzania wersjami moim zdaniem w pewnym sensie definiują na nowo podejście do zarządzania kodem źródłowym. Skupienie się na zmianach zamiast na wersjach rozwiązało bardzo wiele problemów spotykanych w pracy z SVN’em czy CVS’em – branchowanie i mergowanie staje się standardowym elementem prowadzenia developmentu, a dzięki rozproszeniu repozytorium towarzyszy nam od pierwszej linijki kodu.

Z drugiej strony rozproszone systemy repozytorium kodu nie są żadną nowością, a moim zdaniem wciąż nie zyskały bardzo dużej popularności w projektach komercyjnych. Wydaje mi się, że z punktu widzenia firmy rozproszenie repozytorium nie jest czymś niezbędnym – najczęściej przecież i tak potrzebujemy repozytorium centralnego, z którego automatycznie budujemy aktualną wersję i na przykład puszczamy testy jednostkowe. Tworzenie oprogramowania w firmie jest zazwyczaj prowadzone w sposób bardziej usystematyzowany niż w projektach tworzonych przez otwartą społeczność. Zakłada się, że wszyscy deweloperzy wykonują przydzielone zadania i wyniki ich pracy będą musiały się znaleźć w wersji. Czasem może nawet nie chcemy, żeby szeregowy developer mógł pobrać projekt wraz z kilkuletnią historią jego rozwoju.
Pewnym problemem może być też wsparcie systemu kontroli wersji przez IDE. SVN czy CVS jest wszechobecny – większość narzędzi do projektowania i wytwarzania aplikacji bez względu na język programowania czy modelowania ma albo natywną obsługę albo przynajmniej wtyczki do obsługi najpopularniejszych systemów kontroli wersji. Niestety żaden z rozproszonych systemów kontroli wersji nie należy jeszcze do tych najpopularniejszych.

Jeśli chodzi o Git’a to jest bez wątpienia bardzo silnym narzędziem. Na dzień dzisiejszy wydaje się być dopracowanym projektem gotowym do użycia w komercyjnym projekcie. Nie jest jednak jedynym DVCS’em – moim zdaniem przed rozpoczęciem projektu warto rozpoznać konkurencję pod kątem naszych konkretnych potrzeb (np. chociażby pluginów do wybranych IDE).

Dodaj komentarz

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