Testy automatyczne pisane dla procesów w Ferryt mają za zadanie symulować działanie użytkownika końcowego. Weryfikowane są poszczególne ekrany, działanie akcji, ale także ogólne działanie procesu od strony biznesowej. Nawet po ukończeniu developmentu, zdarzają się zmiany, które finalnie mają wpływ na uruchamiane testy automatyczne. Zachodzi konieczność wprowadzenia poprawek w kodzie, a następnie ich efektywne połącznie z głównym repozytorium. Tutaj z pomocą przychodzą polecenia git merge oraz git rebase, które mają ten sam cel, aby integrować takie zmiany. Istnieją jednak pewne kluczowe różnice w ich używaniu, które należy wziąć pod uwagę, jeżeli chcemy zapobiec wprowadzeniu chaosu w swoim projekcie. W tym artykule zdefiniuję funkcje tych poleceń oraz przedstawię na praktycznych przykładach podobieństwa i różnice każdej z nich.
Czemu służy scalanie kodu?
Scalenie kodu w praktyce oznacza to, że GIT bierze zawartość wskazanych gałęzi, porównuje ich różnice i tworzy z nich jeden spójny kod źródłowy. Dzięki temu nowe zmiany trafiają do jednej wspólnej linii rozwoju kodu, z której korzystają wszyscy członkowie zespołu. Zarówno polecenia git merge jak i git rebase służą do połączenia takich zmian. Różnią się jednak sposobem, w jaki to robią i jak wygląda później historia wszystkich commitów po wprowadzonym scaleniu.
Scalenie = zmiany z gałęzi A + zmiany z gałęzi B → wspólna, aktualna wersja projektu
GIT MERGE
Najprościej mówiąc git merge łączy zmiany z jednej gałęzi (branch) z drugą gałęzią, gdzie historia zatwierdzeń w tych gałęziach pozostaje nienaruszona. Co to oznacza? GIT pobiera wszystkie nowe zmiany z wybranej gałęzi i tworzy nowe zatwierdzenie (commit) zwane zatwierdzeniem scalania (merge commit). Tym samym dostajemy dwa zatwierdzenia nadrzędne: jedno z bieżącej gałęzi i jedno z gałęzi scalanej. Zostaje zachowane informacje o historii commitów, pokazane jest dokładnie kiedy i gdzie dana gałąź odbiegła od naszej głównej gałęzi master.
Przykład – git merge
Zanim zabierzemy się do procesu scalenia upewnijmy się, że lokalne repozytorium jest aktualne o najnowsze zmiany ze zdalnego serwera, pobierając te zmiany (git fetch). Jeżeli jesteśmy pewni, że nie mamy ich więcej, a wszystkie interesujące nas branche są odświeżone (git branch -va) , przełączmy się na gałąź do której chcemy scalić zmiany (git checkout <docelowy branch>).
Mamy dwie gałęzie:
– master – gałąź główna, na której rozwijany jest projekt
– myChange – branch, na którym wykonywaliśmy zmiany i które chcemy scalić z najnowszą wersją master
Wykonujemy serię poleceń:
git checkout master
git status // (opcjonalnie) upewniamy się, że jesteśmy na właściwym branch’u)
git pull //używamy w celu pobrania najnowszych danych z mastera
git merge myChange

W tym momencie nastąpiło scalenie zmian do branch’a myChange z gałęzi master. Ten sposób scalenia jest bezpieczny i najczęściej stosowany ze względu na to, że istniejące gałęzie nie ulegają zmianom. Zostają zachowane historyczne informacje o rozgałęzieniach, gdzie i kiedy dana gałąź odbiegła od naszej głównej gałęzi master. Zaletą jest, że przechowywane są szczegóły historii wszystkich użytkowników, ale należy mieć też na uwadze, że może to nieść ze sobą trudność w analizie całości i nieliniową historię commitów, jeżeli projekt jest bardzo rozbudowany.
GIT REBASE
Kolejnym sposobem na integrację wprowadzonych zmian z jednej gałęzi do drugiej, jest git rebase, który działa nieco inaczej niż git merge. Zamiast tworzyć nowy merge commit, polecenie przenosi i łączy powstałe commity, tworząc ich liniową historię, Wygląda to jakby powstałe zmiany zostały wprowadzone bezpośrednio na gałęzi docelowej. Git rebase jest pomocne do porządkowania historii zatwierdzonych zmian, umożliwiając prześledzenie początku danej funkcji bez tworzenia wielu kolejnych rozgałęzień. Dzięki temu można usunąć niechciane commity, scalić zmiany lub zaktualizować komunikaty dla naszych commitów
Jak najlepiej to wykonać?
Na początku przełącz się na branch, który chcesz scalić
git checkout myChange
W kolejnym kroku wykonuj rebase zmiany z gałęzi master do gałęzi myChange, aby zaktualizować wszystko o najnowsze zmiany
git rebase master

Tym sposobem nie musieliśmy już dodatkowo zatwierdzać scalania zmian jak to jest wymagane przez git merge. Wszystko wydaje się „łatwiejsze”, ale czy na pewno? Jeżeli pracujemy w projekcie sami to jak najbardziej, ale nie powinniśmy używać tego rozwiązania na gałęziachpublicznych współdzielonych z innymi członkami zespołu.
Przykład – git rebase
Chcemy ponownie wykonać polecenie git rebase z gałęzi master na swoją lokalną gałąź myChange. W międzyczasie na master powstały commity dodane przez innych użytkowników i historia zmian została zmieniona. Wykonane przez nas polecenie git rebase przenosi wszystkie commity z master na początek gałęzi myChange. Cała operacja dzieje się jednak wyłącznie na naszym repozytorium. Zespół cały czas pracuje na oryginalnej wersji głównej gałęzi i nie widzi zmian, które wcześniej wykonaliśmy. Nasze historie będą się między sobą różnić. Może to zdezorganizować pracę. W takiej sytuacji trzeba połączyć commity obu gałęzi – master i myChange poprzez ponowne scalenie. co skutkuje dodatkowym zatwierdzeniem.
Kolejnym przykładem jest wystawiony Pull Request z Naszymi zmianami. Nie powinniśmy używać polecenia git rebase po jego utworzeniu. Osoby wykonujące Code review, będą przeglądać commity, co oznacza, że gałąź myChange będzie na ten czas „publiczna”. Osoby odpowiedzialne za sprawdzenie kodu nie będą mogły śledzić kolejnych commitów dodanych po jego wystawieniu. Lepszym pomysłem jest wykonanie git rebase przed wykonaniem Pull Request’a.

Konflikty po scaleniu zmian
Nie ukończymy operacji scalenia plików między gałęziami, jeżeli pojawią się różnice w ich fragmentach, które nie mogą zostać rozwiązane automatycznie. Należy na początku rozwiązać wszystkie występujące konflikty ręcznie, aby móc dalej pracować nad projektem i w plikach, w których się one pojawiły. Decydujemy, która wersja kodu powinna zostać zachowana – nasza obecna wersja, ta przychodząca lub połączenie obu zmian. Rozwiązujemy taki konflikt i poleceniem git add dodając zmodyfikowane pliki.
Jeżeli występuje sytuacja, gdzie konflikty są trudne do rozwiązania lub prowadzą do nieoczekiwanych problemów, można anulować scalanie. Z takich sytuacji również jest wyjście. Można wykonać jedno z poniższych poleceń, aby cofnąć wprowadzanie niepotrzebnych zmian do kodu.
git merge –abort
git rebase –abort
Po wykonaniu polecenia otrzymamy komunikat informujący, czy wprowadzone scalanie zostało pomyślnie anulowane. Wykonajmy dodatkowo git status, aby sprawdzić status repozytorium i upewnić się, że nastąpiło anulowanie scalania. Jeżeli istnieją nowe pliki to należy je usunąć, aby uniknąć utworzenia nowych konfliktów.
Cofanie udanego scalenia do stanu początkowego
A co w przypadku, kiedy chcemy cofnąć scalenie zmian, które zakończyło się sukcesem i zrobiliśmy commit? Nasze przerwanie –abort już nie zadziała.
Dla merge możemy wybrać jedną z opcji:
- Używamy polecenia git reset –hard HEAD~1
Dobre rozwiązanie, ale do wykonania tylko na naszej lokalnej gałęzi, kiedy nie wykonaliśmy jeszcze git push do głównego repozytorium. Tym samy przywracamy Naszą gałąź do stanu sprzed wykonania git merge czyli do wcześniejszego commita, jednocześnie tracąc wszystkie zmiany w kodzie powstałe podczas scalenia
- Używamy polecenia git revert -m 1 <hash_merge_commita>
To już w przypadku, kiedy commit trafił na główne repozytorium. Tworzymy wtedy nowy commit, który odwraca efekty wykonanego wcześniej mergowania. Informacje o hash_merge_commita znajdziemy pod git log.
Dla rebase zakończonego sukcesem możemy zadziałać wybierając poniższe rozwiązanie:
Używamy polecenia git reflog a później git reset
Po wykonanym rebase Nasze commity mają nowe identyfikatory, dlatego należy wrócić do poprzedniego stanu. Wpisując w terminal polecenie git reflogotrzymujemy informacje o wszystkich działaniach w Git, co zostało zrobione w repozytorium (więcej szczegółów niż przy użyciu git log). Dzięki temu można cofnąć wykonany rebase, przywrócić wcześniejszego commita, odzyskać kod sprzed scalenia, a nawet odtworzyć usuniętą przez Nas gałąź.
Przykładowy wynik:
b1c8e3f HEAD@{0}: checkout: moving from myChange to main
91fd56a HEAD@{1}: rebase finished: returning to refs/heads/feature/login
91fd56a HEAD@{2}: rebase: pick 7c3a1d0 test: poprawki do sekcji logowania
5dfb2ad HEAD@{3}: rebase: pick 1fe8920 dodanie nowego pliku dla e2e
213dfc0 HEAD@{4}: commit: ostatnie zmiany w teście
Co to oznacza?
HEAD@{1} – Twoja gałąź po zakończonym rebase
HEAD@{4}– wcześniejsze commity przed przepisaniem przez rebase
HEAD@{n}: rebase: pick – oznacza konkretne commity, które Git przepisał.
Wybieramy wpis tuż przed rebase HEAD@{4} i cofamy się do niego komendą git reset –hard HEAD@{4} . Teraz wszystkie wcześniejsze commity są z powrotem na swoim miejscu, a nowe po wcześniejszym scaleniu znikają z historii. Tym samym gałąź wróci do poprzedniego stanu.
Podsumowanie – kiedy i jak używać
Każde użycie jednego z poleceń git merge lub git rebase zależy od kontekstu i sytuacji, w której się znajdujemy. Używajmy merge gdy pracujemy na współdzielonych gałęziach, gdzie zależy nam bardziej na zachowaniu pełnej historii zmian, niż na pełnym uporządkowaniu. Używajmy rebase, gdy pracujemy na swojej lokalnej gałęzi i chcemy utrzymywać czystą, liniową historię projektu.
W teorii oba polecenia mają za zadanie scalać zmiany z jednej gałęzi do drugiej. W praktyce zrozumienie różnicy między działaniem tych poleceń jest kluczowe w celu optymalizacji pracy swojej lub innych, zachowując przy tym spójność kodu w projekcie. Współpracując z wieloma użytkownikami, którzy piszą testy automatyczne dla procesów w Ferryt, musimy być bardzo ostrożni, zwłaszcza kiedy współdzielimy pliki między różnymi zespołami – najczęściej metody wykonujące akcje na Workplace czy Smartconsole.
Kiedy decydujemy się na anulowanie scalenia, powinniśmy być pewni tej decyzji. Przerwanie połączenia zmian może mieć przykre konsekwencje, dlatego warto to robić ostrożnie. Dobrym rozwiązaniem jest wykonanie wcześniej kopii swojej pracy. Przed cofnięciem zmian, upewnijmy się również, że wszelkie powstałe konflikty zostały rozwiązane, ponieważ w niektórych przypadkach nie będziemy w stanie odtworzyć oryginalnych zmian.
Uruchomienie testów automatycznych w trakcie każdej regresji przy podniesieniu wersji środowiska Ferryt czy zmianach w procesie dokonanej przez dewelopera, stanowi kluczowy element w wychwyceniu niekontrolowanych błędów.
| Git merge | Git rebase | |
| Główny cel | Łączy ze sobą dwie gałęzie, zachowując pełną historię commitów. | Przenosi commity jednej gałęzi tak, aby dołączyć je na końcu innej gałęzi |
| Historia commitów | Zostaje zachowana – widać rozgałęzienia i momenty łączenia, chroni historię | Zostaje uporządkowana – historia jest liniowa i prosta. |
| Nowe commity | Tworzy tylko jeden dodatkowy commit scalający. | Tworzy nowe wersje istniejących commitów (z nowymi ID). |
| Widoczność połączenia gałęzi | Widać dokładnie, gdzie i kiedy gałęzie zostały połączone. | Nie widać momentu połączenia gałęzi – wygląda na to, że praca była wykonana po kolei. |
| Typowe zastosowanie | Łączenie zmian z główną gałęzią współdzieloną przez wielu użytkowników | Porządkowanie lokalnej historii commitów |