Kurs Git - Tworzenie gałęzi

Praca nad projektem informatycznym odbywa się najczęściej w kilkuosobowym zespole. W takim środowisku jedną z najważniejszych rzeczy do jakiej chcemy dążyć jest stworzenie i utrzymanie odpowiedniego porządku pracy. Dlatego też bardzo ważna jest właściwa organizacja zadań programistycznych. Deweloper otrzymuje lub sam wybiera swoje zadanie do wykonania (feature), a następnie pracuje nad nim przez pewien czas na swoim lokalnym komputerze. W tym samym czasie inni deweloperzy również realizują swoje zadania.

Wszystkie te prace muszą być wykonywane przez jakiś czas niezależnie, by następnie połączyć ich rezultat w jedną całość. W takich warunkach bardzo często dochodzi do żonglerki kodem. Aby była ona bezpieczna dla całego projektu, wymagane jest użycie funkjonalności Git-a, jaką jest tworzenie nowych gałęzi kodu.

Każdy deweloper rozpoczynając pracę nad swoim zadaniem tworzy zatem nową gałąź (nowego brancha), który jest niczym innym jak jego kopią kodu pobraną w konkretnym momencie czasu ze stabilnego brancha głównego całego projektu (mastera). Tym samym jeśli kilku programistów stworzy swoje branche, wówczas będą oni pracowali na niezależnych kopiach kodu projektu.
Appa Notka. Objerzyj ten materiał na filmie. Jeśli chcesz więcej filmów takich jak ten — subskrybuj i polub. Takie działanie z Twojej strony pozwoli nam lepiej zrozumieć, czy ta seria filmów jest dla Ciebie wartościowa i czy powinna być kontynuowana.

Tworzenie brancha - git branch

Tworzenie nowego brancha w Gicie jest bardzo proste. Wchodzimy do głównego katalogu projektu (tam gdzie znajduje się katalog .git) i uruchamiamy komendę:
git branch <nazwa brancha>
Przykładowo jeśli naszym zadaniem jest stworzenie szkieletu modułu zarządania użytkownikami wówczas stworzymy brancha o nazwie zgodnej z jego przeznaczeniem:
git branch feature/Add-user-management-skeleton
Oprócz wykreowanej w naszej głowie nazwy dodaliśmy jeszcze na początku prefix feature co spowoduje, że Git zapisze tego brancha w "katalogu" o nazwie feature. Dzięki temu przeglądając w przyszłości spis branchy będziemy od razu wiedzieć, że dany branch dotyczy nowej funkcjonalności, a nie jest na przykład poprawą błędu (bugfix). Bugfixy powinny mieć tworzony osobny katalog o nazwie bugfix. Taka kategoryzacja branchy jest zgodna z dobrymi praktykami.

Teraz zwróćmy uwagę na jeszcze jeden fakt. Samo stworzenie brancha nie oznacza, że będziemy mogli od razu rozpocząć pracę nad kodem. Owszem, stworzyliśmy niezależną kopię (osobną gałąź) w stosunku do głównego brancha master, ale ona jak narazie jest częścią naszego lokalnego repozytorium. Jeśli chcemy przełączyć na tego brancha kod projektu znajdujący się w working directory, wówczas musimy wykonać komendę:
git checkout <nazwa brancha>
W ten sposób kod naszego projektu będzie zawierał dokładnie tą wersję kodu, która znajduje się na branchu w naszym lokalnym repozytorium. W tym miejscu dobrze jest wspomnieć, że istnieje pewne uproszczenie, które pozwala wykonać operacje stworzenia brancha jak i przełączenia się na niego w working directory za pomocą jednego kroku. Wystarczy że użyjemy komendy:
git checkout -b <nazwa brancha>
Appa Notka. Objerzyj ten materiał na filmie. Jeśli chcesz więcej filmów takich jak ten — subskrybuj i polub. Takie działanie z Twojej strony pozwoli nam lepiej zrozumieć, czy ta seria filmów jest dla Ciebie wartościowa i czy powinna być kontynuowana.

Praca na branchu

Stworzyliśmy więc brancha feature/Add-user-management-skeleton i jesteśmy przełączeni na niego w naszym projekcie. Teraz chcemy wykonać pierwsze prace w ramach feature'a. Załóżmy, że podjeliśmy decyzje, iż na tym branchu chcemy wykonać jedynie zarys nowej funkcjonalności (dlatego nazwaliśmy go szkieletem). Tworzymy wiec interfejs UserService, a w nim cztery metody: create, read, update, delete czyli popularny CRUD. Na tym poprzestaniemy. Nie będziemy tworzyć klasy implementującej. Chcemy jedynie aby nasi koledzy (albo koleżanki) z zespołu wiedzieli jaki mamy plan na moduł zarządzania użytkownikami.
public interface UserService {

    void create();
    
    List<User> read();
    
    void update();
    
    void delete();
}
Plan jak widać jest bardzo prosty i szczerze mówiąc zbyt prosty. Nie powinniśmy tworzyć branchy tylko po to by dodać do projektu jeden banalny interfejs. Natomiast w tym przypadku uprościliśmy sprawę, aby skupić się na istocie pracy z Gitem. Dla tego przykładu nie ma znaczenia czy stworzymy jeden czy dziesięć plików. Ważne jest co zrobimy dalej. W jaki sposób doprowadzimy do tego, że nasze zmiany w postaci nowego pliku zostaną dodane do wspólnego brancha master. Dopiero wtedy będą one mogły być zauważone i pobrane przez innych programistów.

Korzystając z tego co już wiemy z rozdziału Git - Pierwsze kroki, najpierw dodajemy plik do staging area, a następnie komitujemy do lokalnego repozytorium:
git add UserService.java
git commit -m "Add UserService"
Pamiętamy przy tym, że wszystkie operacje (add, commit) wykonujemy na branchu feature/Add-user-management-skeleton, więc jeśli teraz przełączymy się na branch master (za pomocą komendy git checkout), wówczas w naszym kodzie projektu (working area) nie będzie takiego pliku jak UserService.java.

Skoro ten plik znajduje się tylko na naszym branchu, a chcemy by znalazł się na wspólnym branchu master, to musimy wykonać jeszcze jedną operację...

Z brancha do brancha - git merge

No właśnie. Przyszedł czas na wysłanie naszych zmian na wspólnego brancha. Pamiętajmy, że możemy zmieniać tylko brancha, na którego jesteśmy aktualnie zchekoutowani. Więc jeśli chcemy wgrać zmiany z naszego feature brancha do mastera, to musimy się na niego najpierw przełączyć. W tym celu wykorzystamy znaną nam komendę:
git checkout master
Teraz nie pozostało nam już nic innego jak tylko wywołać komendę, która pobierze zmiany z jednego brancha i wstawi je do drugiego brancha (bieżącego). Operację taką nazywamy scaleniem co zresztą jest zgodne z nazwą komendy, której używamy:
git merge <nazwa brancha>
W naszym przypadku wykonamy więc:
git merge feature/Add-user-management-skeleton

Fast-forward

W efekcie jeśli wszystko poszło zgodnie z planem i merge wykonał poprawne scalenie naszego kodu, otrzymamy informację podobną do poniższej:
Updating 1d50c6a93...96ac73:
Fast-forward
    UserService.java  |     1 +
    1 file changed, 1 insertion(+)
    ...

To nie oznacza nic innego jak to, że operacja zakończyła się sukcesem. Zmiany zostały bezproblemowo zmergowane. W powyższej odpowiedzi Git-a na szczególną uwagę zasługuje wpis Fast-forward. Oznacza on, że pomiędzy momentem, w którym pobraliśmy kopię brancha master, a czasem w którym obecnie wykonujemy merge nic nie zmieniło się na branchu master. Żaden inny programista nie wykonał operacji scalającej jego kod z tym branchem. Trzeba przyznać, że taka sytuacja zdarza się dosyć rzadko, szczególnie w zespołach składających sie z wielu programistów.

Recursive merge

Znacznie częściej spotykamy się z sytuacją, w której branch, do którego chcemy mergować został już zmieniony od czasu kiedy pobieraliśmy jego kopię. W takim przypadku po wykonaniu komendy git merge otrzymamy informację zwrotną do naszego narzędzia edycyjnego obsługującego Gita:
1 Merge branch feature/Add-user-management-skeleton
2
3 # Please enter a commit message to explain why this merge is necessary,
4 # especially if it merges an updated upstream into a topic branch
5 #
6 # Lines starting with '#' will be ignored, and an empty message aborts
7 # the commit.

Domyślnie Git używa edytora Vi, który nie jest specjalnie intuicyjny. Warto zapoznać się z jego podstawowymi komendami. I tak, jeśli akceptujemy wstawioną przez Gita informację o komicie (commit message), wówczas po prostu wychodzimy z edytora wpisując :q!. Natomiast jeśli chcemy zmienić tą wiadomość, to musimy użyć komendy, która zapisze zmiany i dopiero wtedy doprowadzi do wyjścia z edytora, czyli :wq. Po wszystkim możemy skomitować zmiany do repozytorium tworząc w ten sposób komit mergujący. Git poinformuje nas o rezultacie tego co się właśnie wydarzyło.
Git can't fast-forward since changes were made in both branches 
  Merge made by the 'recursive' strategy.
    0 files changed
    create mode ... feature/Add-user-management-skeleton 

W ten sposób wykonaliśmy merge'a rekursywnego (recursive merge). Odnajdując w przyszłości (w historii komitów) wpis dotyczący takiego merge'a każdy deweloper będzie wiedział, że nie był to zwykły komit typu fast-forward, tylko że dotyczył on scalenia zmian z dwóch branchy.

Fast-forward vs Recursive merge

Zobaczmy teraz na jednym obrazku jak dokładnie wyglądają scalenia typu Fast-forward i Recursive merge. Należy tutaj szczgólnie zwrócić uwagę na dwie różnice. Pierwsza z nich to występowanie niezależnego (wykonanego przez innego dewelopera) komita do brancha master, który pojawił się między stworzeniem przez nas brancha feature/Add-user-management-skeleton , a komitem mergującym (merge commit).
Git merge w trybie Fast-forward
Git recursive merge
Druga różnica polega właśnie na tym, że w przypadku strategii Recursive merge jest wymagany komit scalający, który zmerguje zmiany z komita wrzuconego do brancha master ze zmianami wykonanymi w tym czasie przez nas na branchu feature/Add-user-management-skeleton.

Na koniec wspomnijmy jeszcze, że pomarańczowy komit znajdujący się na branchu master mógł się tam znaleźć nie tylko przez to, że inny programiasta napierw stworzył brancha, a potem zmergował zmiany (w trybie Fast-forward), ale też to my sami mogliśmy stworzyć drugiego brancha i zmergować zmiany zanim wykonaliśmy merge'a brancha feature/Add-user-management-skeleton.

W tym miejscu można postawić pytanie...dlaczego tworzyliśmy kolejnego brancha skoro już pracowaliśmy na innym? Odpowiedź jest dosyć prosta. W trakcie naszej pracy zdarza się, że trzeba nagle zmienić priorytety i wykonać jeszcze jedno zadanie po drodze (np. poprawienie błędu) i takie zadanie musi być wykonane w osobnym branchu z osobnym komitem, gdyż jest ono całkowicie niezwiązane z tym co robiliśmy do tej pory.

Rozdział postanowiliśmy zakończyć, ale temat będzie jeszcze przez nas rozwijany. Istnieją bowiem zagadnienia, które potrafią nieco skomplikować mergowanie. Jednym z problemów, którym zajmiemy się w kolejnym rozdziale jest sytuacja, w której dwie osoby wykonały zmiany dokładnie w tych samych liniach tego samego pliku. Wtedy mergowanie nie może być wykonane automatycznie. Programista musi podjąć decyzje, które zmiany zachować lub jak obie te zmiany połączyć w jedną całość.
Zdjęcie autora
Autor: Jarek Klimas
Data: 03 stycznia 2024
Labele: Backend, Podstawowy, Java
Linki:
https://git-scm.com/book/pl/v1/Gałęzie-Gita-Podstawy-rozgałęziania-i-scalania

Stale się rozwijamy, a więc bądź na bieżąco!
Na ten adres będziemy przesyłać informacje o ważniejszych aktualizacjach, a także o nowych materiałach pojawiających się na stronie.
Polub nas na Facebooku:
Nasi partnerzy: stackshare
Javappa to również profesjonalne usługi programistyczne oparte o technologie JAVA. Jeśli chesz nawiązać z nami kontakt w celu uzyskania doradztwa bądź stworzenia aplikacji webowej powinieneś poznać nasze doświadczenia.
Kliknij O nas .


Pozycjonowanie stron: Grupa TENSE