W artykule o podmianie gałęzi rodzica w git-cie pokazałam Ci jedno z zastosowań komendy git rebase --onto
. Dziś skupimy się na zgłębieniu tematu, bo jest o czym mówić. Im lepiej zrozumiesz to polecenie tym łatwiej będzie Ci go w przyszłości użyć. Zapraszam!
Istnieją dwa przypadki, w których git rebase --onto
może się przydać:
- Masz gałąź (branch), na której chciałabyś zmienić gałąź rodzica.
- Chcesz szybko usunąć zmiany ze swojej gałęzi.
Oczywiście możesz te dwa powody ze sobą połączyć i podmienić gałąź rodzica w tym samym momencie, gdy usuwasz zmiany. Dojdziemy do tego. Najpierw jednak ważne jest zrozumienie różnicy w wywołaniu git rebase --onto
z dwoma lub trzeba argumentami.
Zacznijmy jednak od małej powtórki. Omówimy sobie pokrótce czym jest git rebase
. Jeśli będziesz zainteresowana dodatkowymi informacjami o git rebase, to odsyłam Cię do osobnego artykułu na ten temat.
Git rebase
Komenda git rebase <newparent> <branch>
pozwala nam na dostęp do ostatnich zmian jakie istnieją na gałęzi <newparent>
i na przesunięcie naszych zmian z gałęzi <branch>
ponad zmiany z gałęzi <newparent>
.
Używając komendy:
git rebase master next-feature
otrzymamy:
Before After A---B---C---F---G (HEAD master) A---B---C---F---G (master) \ \ D---E (next-feature) D'---E' (HEAD next-feature)
Jak widać powyżej po wykonaniu komendy git rebase
nasz HEAD
wędruje zawsze na gałąź, która była zdefiniowana jako ostatni argument. W naszym przypadku jest to gałąź next-feature
. Możemy powiedzieć, że przełączyłyśmy się na gałąź next-feature
. Po wykonaniu git rebase
dalej mamy dostęp do wszystkich zmian jakie były na gałęzi next-feature
przed jej wykonaniem, jednak nie są to dokładnie te same zmiany. Kod, który się tam znajduje jest identyczny. Zmieniły się natomiast identyfikatory zmian wygenerowane przez kryptograficzną funkcję skrótu SHA-1 (dce79fd
), które często w skrócie określamy jako SHA. To dlatego na schemacie powyżej zmiany te zostały zaznaczone jako D'
i E'
.
Gdy jesteśmy na gałęzi, na której chcemy wykonać git rebase
możemy pominąć drugi argument komendy, a efekt końcowy będzie taki sam. Komenda:
git rebase master
daje następujący rezultat:
Before After A---B---C---F---G (master) A---B---C---F---G (master) \ \ D---E (HEAD next-feature) D'---E' (HEAD next-feature)
W obu przypadkach na gałęzi master
były dwie dodatkowe zmiany F
i G
, które nie były dostępne z poziomu gałęzi next-feature
. Wykonując polecenie git rebase
bierzemy zmianę D
, która jest pierwszą zmianą na gałęzi next-feature
wraz z resztą zmian na tej gałęzi i przenosimy/przesuwamy je ponad/powyżej ostatnią zmianę na gałęzi master
, czyli ponad zmianę G
. W przypadku schematów pokazywanych w tym artykule lepiej będzie użyć sformułowania przesuwamy zmiany na koniec gałęzi master
. Warto jednak pamiętać, że korzystając z narzędzi takich jak git log
nasze zmiany będziemy widzieć nad zmianami z gałęzi master
. Mówiąc jeszcze inaczej zmieniamy rodzica naszej gałęzi next-feature
ze zmiany C
na zmianę G
.
Git rebase –onto
Bardziej precyzyjne podmienianie gałęzi rodzicielskiej
W przypadku polecenia git rebase --onto
możemy przesunąć się w dowolne miejsce gałęzi rodzicielskiej, nie tylko na jej koniec. Dokładniej mówiąc możemy przesunąć punkt rozgałęzienia miedzy naszą gałęzią a gałęzią rodzicielską w dowolne miejsce drzewa zmian, czyli punkt rozgałęzienia może znaleźć się na dowolnej istniejącej zmianie. Dodatkowo możemy również wybrać zmianę, która będzie ostatnią zmianą widoczną na naszej gałęzi. Możemy powiedzieć, że git rebase --onto
jest bardzo precyzyjny i elastyczny. Daje nam dostęp do tego gdzie chcemy zacząć, a gdzie skończyć.
Przykładowo, chciałabyś przesunąć rozpoczęcie swojej gałęzi my-branch
z C
na F
i usunąć zmianę D
. By móc tego dokonać potrzebujesz:
git rebase --onto F D
A tak wygląda to na schemacie:
Before After A---B---C---F---G (branch) A---B---C---F---G (branch) \ \ D---E---H---I (HEAD my-branch) E'---H'---I' (HEAD my-branch)
Przesuwamy zmiany dostępne z poziomu HEAD
, czyli naszego my-branch
, gdzie starą zmianą rodzicielską było D
, na nową zmianę rodzicielską F
. Możemy też powiedzieć, że zmieniamy rodzica zmiany E
z D
na F
.
Taki sam efekt uzyskamy korzystając z polecenia:
git rebase --onto F D my-branch
Sytuacja wygląda inaczej, gdy zamiast HEAD
, jako trzeci argument, podamy ostatnią zmianę. W naszym przypadku I
. Wywołanie będzie wyglądać następująco:
git rebase --onto F D I
A efekt jaki otrzymamy widoczny jest na schemacie:
Before After A---B---C---F---G (branch) A---B---C---F---G (branch) \ | \ D---E---H---I (HEAD my-branch) | E'---H'---I' (HEAD) \ D---E---H---I (my-branch)
Podobnie jak w przypadku git rebase
bez dodatkowych parametrów, tu też przełączamy HEAD
na ostatni argument wywołania komendy git rebase --onto
. Widzimy, że nasza gałąź my-branch
pozostała nienaruszona, natomiast HEAD
znajduje się na nowej wersji zmiany I
. Nie jest to jeszcze nazwana gałąź, ale jeżeli chcemy możemy ją nazwać. Zastanówmy się teraz, co tu się stało. Nasze polecenie git rebase --onto F D I
sprawia, że zmieniamy rodzica zmiany E
. Można tu się zastanowić dlaczego właściwie zmiany E
? Przecież zmiana ta nie występuje nigdzie w poleceniu. Jest to pewnego rodzaju podmiot domyślny. Skoro bieżącym rodzicem jest zmiana D
, to jest ona rodzicem dla zmiany E
. W skrócie możemy powiedzieć, że git rebase --onto F D I
zmieni rodzica zmiany E
ze zmiany D
na F
. Dodatkowo przełączy nasz HEAD
na zmianę I
.
Taki sam efekt uzyskamy korzystając z polecenia:
git rebase --onto F D HEAD
Podobna sytuacja ma miejsce, gdy chcemy na przykład przełączyć HEAD
na zmianę H
. Tak będzie wtedy wyglądać komenda:
git rebase --onto F D H
Natomiast nasze drzewo gałęzi będzie wyglądać następująco:
Before After A---B---C---F---G (branch) A---B---C---F---G (branch) \ | \ D---E---H---I (HEAD my-branch) | E'---H' (HEAD) \ D---E---H---I (my-branch)
Jedyna rzecz jaka zmieniła się od ostatniego przykładu, to że HEAD
nie znajduje się teraz na I
ale na zmianie H
. Wcześniej, przed wykonaniem komendy git rebase --onto
, HEAD
znajdował się na gałęzi my-branch
. Po wywołaniu polecenia git rebase --onto F D H
przenosimy HEAD
na zmianę H
ignorując przy tym całkowicie zmianę I
. Analogiczne zachowanie zaobserwujemy w naszym drzewie po wykonaniu jednego z poniższych poleceń:
git rebase --onto F D H git rebase --onto F D HEAD^ git rebase --onto F D HEAD~ git rebase --onto F D HEAD~1
Usuwanie zmian z bieżącej gałęzi
To szybkie rozwiązanie, które pozwala nam usunąć niektóre zmiany z naszej gałęzi bez konieczności używania interaktywnego polecenia rebase. Jeżeli mamy gałąź, na której chcemy usunąć zmiany C
i D
, możemy to zrobić za pomocą polecenia:
git rebase --onto B D
Co daje nam:
Before After A---B---C---D---E---F (HEAD branch) A---B---E'---F' (HEAD branch)
W tym przypadku mówimy przenieś HEAD
nad zmianę B
, gdzie starą zmianą rodzicielską była zmiana D
. Taki sam efekt uzyskamy za pomocą:
git rebase --onto B D my-branch
W przypadku gdy użyjemy git rebase --onto
z trzema argumentami, gdzie ostatnim argumentem będzie identyfikator zmiany, sytuacja będzie wyglądać trochę inaczej. Powiemy wtedy: przenieś HEAD
nad zmianę B
, gdzie starą zmianą rodzicielską była zmiana D
, ale tylko do zmiany E
. By to osiągnąć użyjemy następującego polecenia:
git rebase --onto B D E
Dostaniemy wtedy nową gałąź tylko ze zmianą E
wychodzącą od zmiany B
:
Before After A---B---C---D---E---F (HEAD branch) A---B---C---D---E---F (branch) \ E' (HEAD)
Podsumowanie git rebase –onto
Podsumujmy teraz jak działa git rebase --onto
. Polecenia tego możemy używać z dwoma lub trzema argumentami. Gdy używamy git rebase --onto
z dwoma argumentami to tak wygląda składnia tego polecenia:
git rebase --onto <newparent> <oldparent>
To pozwala nam zmienić bieżącą gałąź rodzicielską <oldparent>
na nową <newparent>
. Ze względu na to, że nie określamy tutaj trzeciego argumentu zostajemy na bieżącej gałęzi. W przypadku git rebase --onto
z trzema argumentami składnia polecenia wygląda następująco:
git rebase --onto <newparent> <oldparent> <until>
W tym przypadku nie tylko możemy zmienić gałąź rodzicielską <oldparent>
na nową <newparent>
, ale możemy też określić do jakiej zmiany chcemy to zrobić <until>
(na jakiej zmianie chcemy zakończyć). Watro tu pamiętać, że <until>
stanie się naszym nowym HEAD
po zakończeniu git rebase --onto
.
Potrzebujesz pomocy?
Jeśli szukasz doświadczonej programistki Ruby z ponad dziesięcioletnim stażem, śmiało skontaktuj się ze mną.
Mam doświadczenie w różnych domenach, a szczególną wagę przykładam do szybkiej reakcji na opinie użytkowników i pracy zespołowej. Pomogę Ci stworzyć świetny produkt.