GitMergeRebase

Git – Łączenie gałęzi, merge & rebase

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)
ExampleTwoBrnachesFromOne

Teraz poznamy mechanizm pozwalający łączyć dwie rozbieżne gałęzie w jedną rewizję (rys 2)

MergeTwobranchesIntoOneSchema

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)
FastForwardExample
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):
schemaMergedTwoBranches
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):
disjointBranches
Przechodzimy na gałąź BranchA poleceniem git checkout branchA a następnie wykonujemy polecenie git merge BranchB. Powstanie nam taka struktura (rys 6):
MergeDiffrentBranches
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)
schemaRepoGitRebase

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)
gitRebaseLineHistory
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)
rebaseSimilarMerge
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 🙂
 

3 komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

*