Kontrola wersji - jak nie zgubić się w czasie

W projektach programistycznych pracuje zazwyczaj wiele osób - klienci, project managerowie, graficy, UX-owcy i developerzy. Podstawą do współpracy jest wspólny zbiór informacji, do którego każdy, w dowolnym momencie, będzie mógł sięgnąć i zorientować się, jaki jest stan projektu.

Wspólnych zbiorów informacji w projektach może być wiele:

  • projekty mogą być prowadzone w narzędziach takich jak Atlassian JIRA, gdzie zapisywane są wszystkie zadania i ich stan, przydzielane są poszczególnym osobom z określonymi terminami;
  • może istnieć wspólny zbiór dokumentów np. na współdzielonym dysku, takim jak Google Drive, gdzie wszyscy mogą pracować na aktualizowanych w czasie rzeczywistym dokumentach;
  • można korzystać z czatów grupowych, takich jak Slack, gdzie przechowuje się historię wszystkich konwersacji;
  • można przesyłać pliki poprzez maile, zmieniając odpowiednio ich nazwy
    (np. Raport_ostateczny_final_v02_wersja_ostatnia.doc).

W przypadku projektu programistycznego ważnym źródłem informacji jest również repozytorium kodu, które pozwala na uruchomienie aplikacji i dalsze jej rozwijanie. Aby developerzy mogli pracować i wprowadzać nowe funkcjonalności czy poprawki, potrzebują aktualnej wersji kodu - może się okazać, że wcześniejsze implementacje kłócą się z nowym podejściem, albo gdzieś używana jest nowsza wersja biblioteki koniecznej do działania programu. Czasami potrzebna jest też możliwość szybkiego powrotu do wcześniejszych wersji kodu, w razie, gdyby nowa wersja nie działała prawidłowo w środowisku produkcyjnym.

Dodatkowo, zanim wprowadzone zostaną zmiany w kodzie, najczęściej powinny one przejść testy automatyczne, a także code review przeprowadzony przez innego członka zespołu. Zawsze warto, żeby ktoś sprawdził i upewnił się, że proponowane zmiany spełniają wymagania, stąd potrzeba łatwego ich przeglądania i możliwości przetestowania przed dodaniem do głównego repozytorium.

Jak zabezpieczyć się przed utratą danych?

Istnieje wiele rozwiązań na zabezpieczenie się przed utratą danych.

Najprościej jest skopiować wszystkie pliki do oddzielnego folderu, który można umieścić na tym samym komputerze, dysku sieciowym lub oddzielnym nośniku danych - zwiększenie liczby kopii danych zwiększa bezpieczeństwo i przy odpowiednio dużej liczbie kopii może zapewnić niemal pewność co do ich zabezpieczenia. Jednak taki system wiąże się z dużym kosztem synchronizacji i nie pozwala na przechowywanie wszystkich wersji i zmian w łatwy sposób.

Wszystkie dane można również przechowywać w formie dokumentów elektronicznych online, jak np. na wspomnianym Google Drive. Pozwala to też na nieco łatwiejsze dzielenie się informacjami, chociaż wiąże się jednocześnie z ryzykiem braku dostępu plików w przypadku braku dostępu do sieci, a także koniecznością ich synchronizowania z kopią zapasową. Wersjonowanie jest trudniejsze niż w przypadku prostej kopii folderu, ponieważ zmiany zachodzą zazwyczaj bezpośrednio w plikach i nie da się łatwo odzyskać wcześniejszych wersji, np. w celu porównania.

Systemy te można łączyć na różne sposoby, jednak w przypadku kodu żaden z nich się do końca nie sprawdza. Jak prześledzić błąd, który nagle pojawił się na produkcji w wyniku ostatniej zmiany, jeżeli nie da się tej zmiany łatwo wyłuskać z historii zmian i odwrócić? Skąd wiadomo, czy ktoś zsynchronizował najnowszą wersję kodu i na pewno niczego przypadkowo nie zmienił?

Jak działa system kontroli wersji?

Git

Git jest rozproszonym systemem kontroli wersji przechowującym zmiany w formie snapshotów (stopklatek).

Co to znaczy, że jest systemem rozproszonym? Istnieje centralne repozytorium, w którym zapisywana jest pełna kopia danych, a na lokalnej maszynie się je kopiuje. W systemach centralnych istnieje tylko jedna kopia danych przechowywana centralnie. Lokalne maszyny się z nią łączą, zaś w systemach lokalnych kopiuje się dane na tej samej maszynie.

System kontroli wersji może przechowywać albo jedynie zmiany pomiędzy plikami i z każdą kolejną zmianą je na siebie nakładać, albo całe pliki ze wszystkimi zmianami. Git łączy nieco te dwa podejścia i każdemu plikowi nadaje indywidualny identyfikator - w przypadku, gdy wystąpią zmiany, identyfikator również się zmienia. Następnie w zbiorze zmian wskazuje on tylko odpowiednie identyfikatory, co oznacza, że nie duplikuje plików, jeżeli nie zostały zmienione.

Zalety kontroli wersji:

  • możliwość wspólnej pracy nad współdzielonymi informacjami;
  • przechowywanie historii zmian i możliwość przywrócenia wcześniejszych
  • wersji;
  • lepsze rozumienie zdarzeń - zmiany są opisane;
  • brak potrzeby używania systemu nazewnictwa do weryfikowania historii
  • plików;
  • uporządkowanie komunikacji.

GitHub i BitBucket

GitHub i BitBucket to platformy hostingowe, służące do rozproszonej kontroli wersji.

Używa się w nich systemu kontroli wersji git. Repozytorium na GitHubie czy BitBuckecie to kopia projektu przechowywana na serwerze. Repozytoria na platformie hostingowej można utworzyć na dwa sposoby:

  • sforkować czyjeś repozytorium;
  • utworzyć nowe repozytorium.

Słowniczek pojęć:

repozytorium - folder ze wszystkimi plikami projektowymi

fork - kopia repozytorium z głównego repozytorium kodu danego projektu na platformie hostingowej

clone - kopia repozytorium z platformy hostingowej na lokalnej maszynie

branch - wersja repozytorium, główną wersją jest zazwyczaj branch master

log - historia zmian na branchu

commit - zbiór zmian z własnym identyfikatorem, będący pojedynczym wpisem w historii zmian

git fetch / git pull - polecenia na ściągnięcie kodu ze zdalnego repozytorium

git push - polecenie na wrzucenie kodu do zdalnego repozytorium

merge - włączenie zmian do kodu repozytorium, zazwyczaj na branch master

Git flow

Pull request

Aby dodać zmiany do repozytorium, developer musi stworzyć pull request - propozycję zmian do repozytorium, umieszczaną na platformach takich jak GitHub czy Bitbucket. Pull request tworzony jest na kopii repozytorium należącej do proponującego zmiany (forku) i jest oddzielony od głównego repozytorium do momentu włączenia go (zmergowania). Pull requesty pozwalają na pracowanie nad proponowanymi zmianami bez ryzyka dla stabilności głównego repozytorium. Taką zmianę można przeczytać, obejrzeć, ściągnąć lokalnie i przetestować. Można też dodawać do niej komentarze bezpośrednio na BitBuckecie czy GitHubie.

Cykl życia pull requesta to:

  • zgłoszenie zmian - dodanie pull requesta do repozytorium;
  • (code) review - ocena zmian przez inne osoby mające dostęp do
  • repozytorium;
  • zakończenie - dodanie zmian z pull requesta do repozytorium (merge),
  • zamknięcie pull requesta bez dodania zmian lub coś pomiędzy (np. dodanie
  • wybranych zmian).

Aby móc zgłosić pull requesta developer musi:

  • ściągnąć kopię repozytorium (sklonować);
  • stworzyć feature brancha - stworzyć oddzielną, roboczą kopię repozytorium,
  • na której wprowadzane będą zmiany (zbranchować);
  • wprowadzić zmiany na feature branchu - napisać kod, który dodawany jest
  • do repozytorium poprzez commity (zacommitować);
  • zsynchronizować feature brancha - zsynchronizować zmiany lokalne z kopią
  • zdalną (zpushować);
  • otworzyć pull requesta na Bitbuckecie lub GitHubie.

Pozwala to wszystkim osobom mającym dostęp do repozytorium na BitBuckecie czy GitHubie na komentowanie zmian i wspólną pracę nad nimi. Proces sprawdzenia proponowanych (bądź już dodanych do repozytorium) zmian, mający na celu weryfikację jakości i sensowności, nazywa się code review.

Code review

Mając dostęp do repozytorium można ocenić i skomentować pull requesty. Komentarze w pull requestach są rozpatrywane przez osobę proponującą rozwiązanie. Czasem nie wymagają one większego zastanowienia (np. literówki), ale często zdarzają się długie dyskusje nt. kwestii merytorycznych i podejścia do problemu.

W trakcie review osoba proponująca może zdecydować się np. na częściowe wprowadzenie poprawek i wrzucenie nowej wersji pull requesta (technicznie - zrobienie force pusha na feature branch), co automatycznie jest pokazywane w BitBuckecie/GitHubie. Pozwala to na stopniowe dochodzenie do porozumienia i poprawianie zmian.

Pull request może zostać zakończony na kilka sposobów:

  • wprowadzenie zmian w całości (merge) - po przejściu i zaakceptowaniu pull
  • requesta, jest on dodawany do głównego repozytorium w całości;
  • częściowe wprowadzenie zmian (partial merge) - osoba zarządzająca
  • repozytorium może się zdecydować na jedynie częściowe dodanie zmian z
  • pull requesta;
  • zamknięcie pull requesta (close) - pull request zostaje zamknięty, z różnych
  • powodów (nieaktualna zmiana, brak aktywności przez długi czas etc.).
Alicja Raszkowska