Drugi artykuł w mini cyklu o GIT. W dzisiejszym odcinku powiemy o podstawach, właściwie to o najbardziej podstawowej funkcji GITa, czyli łączeniu zmian z różnych gałęzi.
Nie będziemy zajmować się trywialnymi zagadnieniami w stylu „jak to zrobić”, przyjrzymy się natomiast dwóm kwestią: fast-forward i merge-commit. Co to właściwie jest i w jakich sytuacjach wystarczy przesunięcie głowy, a jakich jest konieczne wykonanie dedykowanego commitu łączącego zmiany z dwóch gałęzi.
Dobre zrozumienie tej podstawy jest konieczne aby pojąć co dzieje się „pod maską” polecenia Rebase.
Seria Artykułów o GIT – spis treści
- GIT HEAD – co to jest? Detached Head. Kiedy można stracić głowę?
- GIT fast-forward i Merge Commit. Co to właściwie jest?
- Git REBASE i Squash. Tworzymy ładną historię zmian.
- Przydatne narzędzia i oprogramowanie do pracy z GIT
- Przydatne Materiały o GIT plus BONUS – ściąga z poleceń GIT’a
- Podsumowanie.
Merge commit i fast forward
Zanim dojdziemy do rebase, powinniśmy przypomnieć sobie co oznacza fast-forward i czym jest merge-commit.
Fast-forward
Jeśli gałąź, którą chcemy dołączyć wywodzi się bezpośrednio z gałęzi, do której dołączamy i po drodze nie ma żadnych innych zmian – realizowany jest fast-forward.
Innymi słowy: mamy gałąź master, wychodzimy z niej branchem hot-fix, w branchu hot-fix wykonujemy dwa commity po czym chcemy dołączyć hot-fix do master, wykonujemy więc:
git checkout master
git merge hot_fix
Jeśli w między czasie w gałęzi master nie wykonany żadnych zmian to podczas złączania (merge) GIT wykona fast-forward, czyli zwyczajnie przeniesie wskaźnik master na miejsce, na które wskazuje wskaźnik hot_fix. Fizycznie nie jest realizowane żadne scalenie plików, a tylko przeniesienie wskaźnika. Fast forward daje nam liniową historię i nie wprowadza dodatkowego commita połączeniowego (merge-commit).
Gałąź hot-fix można usunąć gdyż master wskazuje dokładnie na ten sam commit. Proste, prawda? 🙂
Merge-commit
Jeśli historia zawiera dodatkowe zmiany pomiędzy miejsce, „z którego wyszliśmy naszym commitem”, a miejscem „do którego dołączamy gałąź” realizowane jest scalanie trójstronne i powstaje merge commit. Po kolei:
Przed scalaniem mamy następującą sytuacje: wyszliśmy branchem some-feature z brancha main, zrobiliśmy na nim dwa commity. W międzyczasie na branchu main nasz kolega z zespołu wprowadził dodatkową zmianę, która nie jest uwzględniona w naszej gałęzi some-feature.
Konieczne jest więc scalenie zmian.
Takie scalanie nazywa się scalaniem trójstronnym, ponieważ biorą w nim udział trzy strony:
- wspólny przodek obu gałęzi, czyli miejsce, „z którego wyszliśmy”,
- nasz aktualny stan (czyli aktualny stan gałęzi some-feature),
- stan gałęzi, do której dołączamy, czyli aktualny stan gałęzi main.
Wniosek?
Merge commit nie jest wykonywany gdy wystarczy fast-forward! A co jeśli z jakiegoś powodu, np. ze względu na ustalenia projektowe chcemy wykonać merge-commit w sytuacji gdy wystarczyłoby proste przesunięcie HEAD (fast-forward)?
Mamy do tego specjalną komendę:
git merge --no-ff <branch>
Rzecz o konfliktach
Nieodłącznym elementem wspólnej pracy wielu programistów są konflikty, także konflikty w kodzie! 🙂 Jeśli zarówno my jak i inny członek zespoóu zmodyfikował dany plik najczęściej GIT nie będzie wiedziałjak dokonać połączenia naszych zmian. Oczywiście do rozwiązywania konfliktów możemy wykorzystać kilka bardzo dobrych narzędzi, o narzędziach będzie w oddzielnym wpisie! Jednak GIT ma swój wbudowany sposób na rozwiązywanie konfliktów: GITwprowadza znaczniki w pliku dotkniętym konfliktem, wygląda to mniej więcej tak:
here is some content not affected by the conflict
<<<<<<< main
this is conflicted text from main
=======
this is conflicted text from feature branch
>>>>>>> feature branch;
- Przed znakami <<< znajduje się część kodu źródłowego, w której nie ma konfliktu,
- pomiędzy znakami <<< nazwa_gałęzi do której dołączamy zmiany, a znakami „====” znajduje się część dotknięta konfliktem pochodząca z gałęzi do której dołączamy zmiany (cel)
- pomiędzy znakami „===”, a „>>>” znajduje się fragment „naszego kodu”, czyli kodu, który chcemy dołączyć do gałęzi „głównej” (źródło).
- Git tworzy plik o powyższej strukturze, po naszej stronie jest odpowiednie jego skorygowanie oraz wykonanie commitu zmian.
To tyle jeśli chodzi o przypomnienie bazowej wiedzy.
P.S. Teraz już wiesz skąd bierze się tłumaczenie programistów „faktycznie, buildy nie idą, ale GIT mi sam wstawił te znaczki <<<< i teraz się nie kompiluje”… 🙂
W kolejnym artykule szerzej przyjrzymy się REBASE i Squash. Zapraszam!
Super seria, kiedy planujesz kolejne artykuły 🙂 ?
Hej. Co tydzień będę publikował nowy artykuł. Kolejne artykuły będą nieco obszerniejsze! Stay Tuned! 🙂
W końcu zrozumiałem co znaczy gdy GIT podczas REBEASE i rozwiązywania konfliktów wyświetla info o „missing HEAD”. Dzieki!