gitMergeVsRebase

Git – merge vs rebase różnice

Niniejszy post pokaże różnice merge vs rebase poparte praktycznymi przykładami. Zademonstruje łączenia gałęzi za pomocą tych dwóch poleceń. W poprzednim wpisie tej serii opisuje działanie merge i rebase zapraszam 🙂

Przykladowe Repo

Repozytorium dla tego wpisu, na nim będziemy bazować każdy przykład:
ExampleRepo
A tutaj kod tworzący powyższy obraz repozytorium

/c/git
$ git init

/c/git (master)
$ git commit --allow-empty -m "a1.txt"
$ git commit --allow-empty -m "a2.txt"
$ git commit --allow-empty -m "a3.txt"
$ git commit --allow-empty -m "a4.txt"
$ git commit --allow-empty -m "a5.txt"

$ git checkout -b branchB
$ git commit --allow-empty -m "b1.txt"
$ git commit --allow-empty -m "b2.txt"
$ git commit --allow-empty -m "b3.txt"
$ git commit --allow-empty -m "b4.txt"
$ git commit --allow-empty -m "b5.txt"

$ git checkout master
$ git commit --allow-empty -m "c1.txt"
$ git commit --allow-empty -m "c2.txt"
$ git commit --allow-empty -m "c3.txt"
$ git commit --allow-empty -m "c4.txt"
$ git commit --allow-empty -m "c5.txt"

$ git log --oneline --graph --all
* fc7ad22 (HEAD -> master) c5.txt
* 1dacbd5 c4.txt
* 31fcae8 c3.txt
* c751c48 c2.txt
* c2fa54b c1.txt
| * e5a6a64 (branchB) b5.txt
| * 459d356 b4.txt
| * c75851f b3.txt
| * 0a6ac92 b2.txt
| * be93057 b1.txt
|/
* 0f0ff1e a5.txt
* 80323e9 a4.txt
* 1f9e10e a3.txt
* eb6f108 a2.txt
* e65e22e a1.txt

No fast forward

Słowo wstępu, przy każdym poleceniu merge dodaję przełącznik no-ff 'no fast forward’. Jest to merge, który dodaje nowy komit z informacją 'Merge branch feature into master’. W historii zobaczymy 'wybrzuszenie’ tak jak na rys.1. Bez przełącznika uzyskamy historię liniową.

NoFastForward

Merge branchB into master

Pracując na naszym repo, wykonamy łączenie gałęzi master z branchB. Zawartość gałęzi branchB zostanie ustawiona (dołączona) na samym szczycie gałęzi master w postaci jednego komita, sclającego rozwidlenie.

/c/git (master)
$ git merge branchB --no-ff 
(Pojawi się info, które poprosi nas, abyśmy podali message komita. Zapisujemy wiadomość)

Merge made by the 'recursive' strategy.
 b1.txt | 0
 b2.txt | 0
 b3.txt | 0
 b4.txt | 0
 b5.txt | 0
 5 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 b1.txt
 create mode 100644 b2.txt
 create mode 100644 b3.txt
 create mode 100644 b4.txt
 create mode 100644 b5.txt

$ git log --oneline --graph --all
*   0a2644c (HEAD -> master) Merge branch 'branchB'
|\
| * e5a6a64 (branchB) b5.txt
| * 459d356 b4.txt
| * c75851f b3.txt
| * 0a6ac92 b2.txt
| * be93057 b1.txt
* | fc7ad22 c5.txt
* | 1dacbd5 c4.txt
* | 31fcae8 c3.txt
* | c751c48 c2.txt
* | c2fa54b c1.txt
|/
* 0f0ff1e a5.txt
* 80323e9 a4.txt
* 1f9e10e a3.txt
* eb6f108 a2.txt
* e65e22e a1.txt

Obraz z poziomu gitk all
GitMergeBranchB

Rebase branchB

Jeżeli wklejasz komendy, aby zobaczyć w praktyce działanie, to za każdym poleceniem rebase lub merge przywracaj repo do stanu z Przykładowe repo.
Wykonamy polecenie rebase na gałęzi branchB, przejdziemy na gałąź master i spróbujemy z mergować branchB do master

/c/git (master)
$ git rebase branchB 
First, rewinding head to replay your work on top of it...
Applying: c1.txt
Applying: c2.txt
Applying: c3.txt
Applying: c4.txt
Applying: c5.txt

$ git log --oneline --graph --all
* e6d59de (HEAD -> master) c5.txt
* f4de59f c4.txt
* d0aef22 c3.txt
* d1867e3 c2.txt
* 5784083 c1.txt
* e5a6a64 (branchB) b5.txt
* 459d356 b4.txt
* c75851f b3.txt
* 0a6ac92 b2.txt
* be93057 b1.txt
* 0f0ff1e a5.txt
* 80323e9 a4.txt
* 1f9e10e a3.txt
* eb6f108 a2.txt
* e65e22e a1.txt

$ git merge branchB --no-ff
Already up to date.

gitRebaseBranchB2

Wykonanie opcji merge dla powższego przykładu nie jest potrzebne, ponieważ wykonując operację rebase na gałęzi branchB w tym konkretnym przypadku, gałąź ta zostanie 'wpasowana’ w historie komitów gałęzi master.

Rebase master & merge branchB

/c/git (branchB)
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: b1.txt
Applying: b2.txt
Applying: b3.txt
Applying: b4.txt
Applying: b5.txt

$ git log --oneline --graph --all
* d065c1b (HEAD -> branchB) b5.txt
* c91cec8 b4.txt
* 9b4b65a b3.txt
* fa426d0 b2.txt
* b2af0cc b1.txt
* fc7ad22 (master) c5.txt
* 1dacbd5 c4.txt
* 31fcae8 c3.txt
* c751c48 c2.txt
* c2fa54b c1.txt
* 0f0ff1e a5.txt
* 80323e9 a4.txt
* 1f9e10e a3.txt
* eb6f108 a2.txt
* e65e22e a1.txt

rebaseMaster
Wykonując rebase na gałęzi master, git uporządkowuje naszą historię w taki sposób, że komity z master są jeden po drugim, a komity z branchB są na szczycie gałęzi master.

Wrócimy na gałąź master i z mergujemy gałąź branchB do master

/c/git (branchB)
$ git merge branchB --no-ff
Merge made by the 'recursive' strategy.
 b1.txt | 0
 b2.txt | 0
 b3.txt | 0
 b4.txt | 0
 b5.txt | 0
 5 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 b1.txt
 create mode 100644 b2.txt
 create mode 100644 b3.txt
 create mode 100644 b4.txt
 create mode 100644 b5.txt

$ git log --oneline --graph --all
*   9ddd24e (HEAD -> master) Merge branch 'branchB' into master
|\
| * c5a7917 (branchB) b5.txt
| * 4742033 b4.txt
| * b015845 b3.txt
| * c4da213 b2.txt
| * 20b4bd5 b1.txt
|/
* fc7ad22 c5.txt
* 1dacbd5 c4.txt
* 31fcae8 c3.txt
* c751c48 c2.txt
* c2fa54b c1.txt
* 0f0ff1e a5.txt
* 80323e9 a4.txt
* 1f9e10e a3.txt
* eb6f108 a2.txt
* e65e22e a1.txt

gitRebaseMasterGitMergeMaster

Wykonując operację git merge branch no-ff dodajemy na szczcie naszej historii komit z informacją, że w tym konkretnym komicie jest merge innej gałęzi. Przez dodanie opcji no-ff nasza historia nie jest w 100% liniowa, tylko zawiera 'wybrzuszenie’ dla komitów zawierających się w branchB. Ten zabieg widać bardzo dobrze na obrazku. Takie postępowania pozwala nam zachować czytelną historię, dzięki której widzimy, na jakim etapie były dodawane nowe elementy.

Merge vs rebase

Pierwszą rzeczą, jaką należy sobie uświadomić to, że git rebase i git merge, rozwiązują ten sam problem. Oba polecenia służą do łączenie gałęzi.

Przy używaniu polecenie rebase, należy wykonywać tę komendę zawsze na 'branchach’ niewypchniętych do repozytorium. Rebase powinien być wykonywany na naszych prywatnych gałęziach (czyli takich, których, żaden z członków nie widzi).

Komenda git rebase 'przepisuje’ historię projektu poprzez utworzenie nowych SHA-1 dla każdego komita w oryginalnej gałęzi. Przykład rozjaśni, o co mi chodzi, cały czas korzystamy z przykładowego repo:

/c/git (master)
$ git log --oneline --all --graph
* fc7ad22 (HEAD -> master) c5.txt
* 1dacbd5 c4.txt
* 31fcae8 c3.txt
* c751c48 c2.txt
* c2fa54b c1.txtPrzy
| * e5a6a64 (branchB) b5.txt
| * 459d356 b4.txt
| * c75851f b3.txt
| * 0a6ac92 b2.txt
| * be93057 b1.txt
|/
* 0f0ff1e a5.txt
* 80323e9 a4.txt
* 1f9e10e a3.txt
* eb6f108 a2.txt
* e65e22e a1.txt

$ git checkout branchB

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: b1.txt
Applying: b2.txt
Applying: b3.txt
Applying: b4.txt
Applying: b5.txt

/c/git (branchB)
$ git log --oneline --all --graph
* 002ac7f (HEAD -> branchB) b5.txt
* 2436d4c b4.txt
* 90c39a6 b3.txt
* 2306bbd b2.txt
* d2e01f2 b1.txt
* fc7ad22 (master) c5.txt
* 1dacbd5 c4.txt
* 31fcae8 c3.txt
* c751c48 c2.txt
* c2fa54b c1.txt
* 0f0ff1e a5.txt
* 80323e9 a4.txt
* 1f9e10e a3.txt
* eb6f108 a2.txt
* e65e22e a1.txt
Przed rebase  Po rebase
* e5a6a64 (branchB) b5.txt * 002ac7f (HEAD -> branchB) b5.txt
* 459d356 b4.txt * 2436d4c b4.txt

Zwróć uwagę na SHA-1 komitów z komentarzem od 'b1.txt’ do 'b5.txt.’ przed rebasem i po rebase, są różne 🙂

 

Podsumowanie

Tym krótkim w treść, ale obszernym w kod i obrazy, przedstawiłem działanie merge i rebase. Sam jestem zwolennikiem merge, ponieważ lubię widzieć w historii jeden komit informujący, że w tym miejscu został wykonany merge

Jak wygląda u was praktyka stosowania łączenia gałęzi? 🙂

 

 

Pozdro 🙂

Dodaj komentarz

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

*