Git daje nam możliwość tworzenia gałęzi, możemy mieć ich wiele i na każdej inną część kodu tworzącą całość po złączeniu. Git merge oraz git rebase, bo o nich będzie mowa, mechanizm pozwalający scalać, łączyć dwie gałęzie w jedną. Zapraszam do lektury 🙂
Git merge big picture
Tworząc rewizję jedną za drugą, uzyskamy historię liniową. Gdy dodajemy nowy branch wychodzący z komita X oraz drugi branch również wychodzący z tego samego komita to mamy sytuacje jak na obrazku (rys 1)
Teraz poznamy mechanizm pozwalający łączyć dwie rozbieżne gałęzie w jedną rewizję (rys 2)
Dwa przypadki łączenia gałęzi
Łącząc gałęzie, możemy wyróżnić dwa przypadki:
- Przewijanie do przodu (ang. fast forward)
- Łączenie gałęzi rozłącznych
Uwaga
Podczas łączenia dwóch gałęzi w jeden komit, mogą powstać tak zwane konflikty. W tym wpisie przedstawię sytuację bez konfliktów. Konflikty zademonstruje w przyszłych wpisach tej serii.
Przewijanie do przodu (ang. fast forward)
Przewijanie do przodu, oznacza, że podczas wykonywania operacji łączenia dwóch gałęzi, jedna jest zawarta w drugiej. Gdzie gałąź BranchA jest zawarta w gałęzi BranchB. Przykład (rys 3)
W tej sytuacji możemy mówić o dołączeniu gałęzi BranchB do gałęzi BranchA.
Przejdźmy do przykładu w kodzie:
$ git checkout BranchA $ git merge BranchB Updating... Fast-forward ...
Polecenie git merge BranchB powoduje łączenie do aktualnej gałęzi(czyli BranchA), gałęzi o nazwie BranchB. Po wykonaniu tej komendy, gałąź BranchA wskazuje na tę samą rewizję co gałąź BranchB.
Zobrazuje powyższy merge(rys 4):
Jeżeli chcielibyśmy wykonać powyższą operację na odwrót
$ git checkout BranchB $ git merge BranchA Already up-to-date.
Wykonując polecenia w konsoli, dostaniemy komunikat Already up-to-date.
Komunikat informuje o tym, że nie zostały wykonane żadne zmiany w repozytorium, ponieważ gałąź BranchB zawiera wszystkie rewizje gałęzi BranchA.
Łączenie gałęzi rozłącznych
Poniżej przedstawiam obraz struktury repozytorium, który będziemy chcieli połączyć (rys 5):
Przechodzimy na gałąź BranchA poleceniem git checkout branchA a następnie wykonujemy polecenie git merge BranchB. Powstanie nam taka struktura (rys 6):
Po wykonaniu operacji merge warto zwrócić uwagę, że na naszej ostatniej rewizji pojawi się komunikat: Merge branch 'BranchB’ into BranchA. Oczywiście, to jaki jest komunikat zależy tylko od nas, możemy zaakceptować domyślny lub podać własny.
Łączenie gałęzi rozłącznych — praktyka
Teraz zademonstruje praktyczny przykład z komendami, które możesz uruchomić u siebie na środowisku, żeby lepiej zrozumieć działanie merge 🙂
/c/git $ git init /c/git (master) $ touch a.txt $ git add a.txt $ git commit -m "a" $ git log --oneline 031c824 (HEAD -> master) a $ ls a.txt /c/git (master) $ git branch BranchB /c/git (master) $ touch c.txt $ git add c.txt $ git commit -m "c" $ git log --oneline b68e6fe (HEAD -> master) c 031c824 (BranchB) a $ ls a.txt c.txt $ git checkout BranchB /c/git (BranchB) $ touch b.txt $ git add b.txt $ git commit -m "b" $ ls a.txt b.txt $ git log --oneline da95186 b (HEAD -> BranchB) 031c824 a $ git checkout master /c/git (master) $ git log --oneline b68e6fe (HEAD -> master) c 031c824 a $ git merge BranchB Merge made by the 'recursive' strategy. b.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 b.txt $ git log --oneline dea1a36 (HEAD -> master) Merge branch 'BranchB' da95186 (BranchB) b b68e6fe c 031c824 a $ ls a.txt b.txt c.txt
W skrócie co robimy:
- Inicjalizujemy nowe repo
- Dodajemy nowy plik a.txt, dodajemy go do staging area, komitujemy
- Sprawdzamy historie rewizji oraz podglądamy stan fizycznych plików w naszym working directory. Mamy jeden komit a oraz pliki a.txt
- W miejscu rewizji a tworzymy nowy branch BranchB
- Dodajemy nowy plik c.txt(cały czas na gałęzi master), dodajemy go do staging area, komitujemy
- Sprawdzamy historie rewizji oraz podglądamy stan fizycznych plików w naszym working directory. Mamy dwa komity a i c oraz pliki a.txt i c.txt
- Przechodzimy na gałąź BranchB. Teraz w tej gałęzi mamy tylko jeden komit i jeden plik.
- Dodajemy nowy plik b.txt, dodajemy go do staging area, komitujemy
- Sprawdzamy historie rewizji oraz podglądamy stan fizycznych plików w naszym working directory. Mamy dwa komity a i b oraz pliki a.txt i b.txt
- Przechodzimy na gałąź master
- Wykonujemy łączenie gałęzi git merge BranchB
- Sprawdzamy historie rewizji oraz sprawdzamy stan fizyczyny plików. Mamy trzy komity a, b, c oraz pliki a.txt, b.txt i c.txt
Wycofanie operacji merge
Nasz ostatni komit możemy wycofać i wrócić do stanu sprzed merga
$ git reset --hard HEAD~
Przykład
Obecnie nasze rewizje wyglądają następująco:
/c/git (master) $ git log --oneline 17e5912 (HEAD -> master) Merge branch 'BranchB' ca9e054 c b337cf7 (BranchB) b b394c29 a
Jeżeli nasza ostatnia rewizja jest mergem i chcemy wycofać nasz merge, ponieważ uznaliśmy, że coś poszło nie tak, to musimy wykonać następujące kroki (a raczej krok :))
/c/git (master) $ git reset --hard HEAD~ HEAD is now at ca9e054 c /c/git (master) $ git log --oneline ca9e054 (HEAD -> master) c b394c29 a
Po wykonaniu polecenia git reset ––hard HEAD~
będąc na gałęzi master, nasza historia rewizji przyjmuje postać sprzed merge’a.
Git rebase
Przykładowe repozytorium dla git rebase (rys 1)
Teraz będziemy chcieli połączyć dwie gałęzie, w wyniku czego uzyskamy obraz historii liniowej. Aby tego dokonać, należy:
- Przejść na gałąź BranchB używając polecenia git checkout BranchB
- Wydamy polecenie połączenia gałęzi git rebase BranchC
Po wydaniu polecenia git rebase BranchC naszym oczom ukaże się komunikat:
First, rewinding head to replay your work on top of it... Applying: b1 Applying: b2 Applying: b3
Zmiany, jakie nastąpiły po zastosowaniu git rebase używając polecenia git checkout BranchB, możemy interpretować jako zastosowanie zmian w rewizjach BranchB używając polecenia b1,b2,b3 do repozytorium będącego w stanie rewizji c2.
W wyniku uzyskamy repo tak jak na (rys 2)
Zwróć uwagę komity(z apostrofem pojedynczym) b1′,b2′,b3′ posiadają takie same pliki, modyfikacje, zmiany itp. Nie są to jednak te same rewizje w rozumieniu SHA-1 względem komitów(bez apostrofu) b1,b2,b3, mówiąc inaczej SHA-1 rewizji b1 i b1′ będą inne. Z punktu widzenia projektu, synchronizacji będą to nowe komity dodane do naszej historii. W przypadku git rebase należy pamiętać, że te operacje można stosować, wtedy i tylko wtedy, gdy rewizje b1,b2,b3 nie zostały wypchnięte do repo, 'pushniętę’, udostępnionę.
Jeżeli zaś mamy sytuację jak na (rys 3)
Gdzie gałąź bieżąca jest zawarta w gałęzi dołączonej, to uzyskamy taki sam efekt co przy użyciu git merge.
Podsumowanie
Mechanizm łączenia dwóch gałęzi to kwintesencja systemu kontroli wersji, jaką jest GIT. W tym wpisie pokazałem jedną z prostszych sytuacji, kiedy możemy stosować łączenie gałęzi.
Wspomniałem, że podczas łączenia gałęzi mogą powstać konflikty, czyli jeżeli dwie osoby dokonują zmiany w tym samym miejscu danego pliku, to wtedy musimy ręcznie ustalić, jak ma wyglądać połączenie danego elementu, ale o tym będę jeszcze pisać 🙂 Na obecną chwilę należy pamiętać, że możemy łączyć gałęzie w gicie, nie tracąc przy tym danych 🙂
Jeżeli masz jakieś problemy albo coś jest niezrozumiałe, to daj mi znać, zrobię wszystko, co w mojej mocy, aby rozwiać Twoje wątpliwości 🙂
Pozdro 🙂
bardzo pomocne, dzięki!
bardzą przydatnę informację, supęr!
Dziękuję : )