Nie mogę zrozumieć zachowania git rebase --onto


169

Zauważyłem, że dwa bloki następujących poleceń git mają różne zachowania i nie rozumiem dlaczego.

Mam Ai Bgałąź, która różni się od jednejcommit

---COMMIT--- (A)
\
 --- (B)

Chcę zmienić Bgałąź na najnowszą A(i mieć zatwierdzenie na Bgałęzi)

---COMMIT--- (A)
         \
          --- (B)

Nie ma problemu, jeśli to zrobię:

checkout B
rebase A

Ale jeśli to zrobię:

checkout B
rebase --onto B A

W ogóle nie działa, nic się nie dzieje. Nie rozumiem, dlaczego te dwa zachowania są różne.

Klient git Phpstorm używa drugiej składni, więc wydaje mi się, że jest całkowicie zepsuty, dlatego proszę o ten problem ze składnią.


Odpowiedzi:


412

tl; dr

Poprawna składnia do rebase Bna górze Aużywając git rebase --ontow Twoim przypadku jest:

git checkout B
git rebase --onto A B^

lub rebase Bna początku Aod zatwierdzenia, które jest rodzicem, do któregoB odwołuje się B^lub B~1.

Jeśli interesuje Cię różnica między git rebase <branch>i git rebase --onto <branch>czytaj dalej.

Szybki: git rebase

git rebase <branch>ma zamiar ponownie bazować gałąź, którą aktualnie sprawdzałeś, do której odwołuje HEADsię najnowszy zatwierdzenie, które jest dostępne z, <branch>ale nie z HEAD.
Jest to najczęstszy przypadek zmiany bazy i prawdopodobnie ten, który wymaga mniej planowania z góry.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

W tym przykładzie Fi Gsą zatwierdzeniami, które są dostępne z, branchale nie z HEAD. Powiedzenie git rebase branchweźmie D, to jest pierwszy zatwierdzenie po punkcie rozgałęzienia, i ponownie go oprze (tj. Zmieni jego rodzica ) na najnowszym zatwierdzeniu osiągalnym od, branchale nie od HEAD, to znaczy G.

Precyzja: git rebase --onto z 2 argumentami

git rebase --ontopozwala na zmianę bazy zaczynając od określonego zatwierdzenia . Daje ci dokładną kontrolę nad tym, co jest zmieniane i gdzie. Dotyczy to scenariuszy, w których musisz być precyzyjny.

Na przykład wyobraźmy sobie, że musimy HEADdokładnie zmienić bazę , Fzaczynając od E. Jesteśmy zainteresowani tylko wprowadzeniem Fdo naszej działającej gałęzi, a jednocześnie nie chcemy zachować, Dponieważ zawiera pewne niekompatybilne zmiany.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

W tym przypadku powiedzielibyśmy git rebase --onto F D. To znaczy:

Przywróć zatwierdzenie osiągalne, HEADktórego rodzic jest Dna wierzchu F.

Innymi słowy, zmień rodzica z Ez Dna F. Składnia git rebase --ontojest zatem git rebase --onto <newparent> <oldparent>.

Innym scenariuszem, w którym jest to przydatne, jest sytuacja, gdy chcesz szybko usunąć niektóre zatwierdzenia z bieżącej gałęzi bez konieczności wykonywania interaktywnej rebase :

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

W tym przykładzie, aby usunąć Ciz Esekwencji, możesz powiedzieć git rebase --onto B E, lub zmienić bazę HEADna miejscu, w Bktórym był stary rodzic E.

The Surgeon: git rebase --onto z 3 argumentami

git rebase --ontojeśli chodzi o precyzję, może pójść o krok dalej. W rzeczywistości pozwala na przeniesienie dowolnego zakresu zatwierdzeń na inny.

Oto przykład:

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

W tym przypadku chcemy zmienić dokładny zakres E---Hna górze F, ignorując miejsce, na które HEADaktualnie wskazuje. Możemy to zrobić, mówiąc git rebase --onto F D H, co oznacza:

Rebase zakresu zatwierdzeń, których rodzic jest Dmaksymalnie Hna górze F.

Następnie składnia git rebase --ontoz zakresem zatwierdzeń staje się git rebase --onto <newparent> <oldparent> <until>. Sztuczka polega na tym, że należy pamiętać, że zatwierdzenie, do którego odwołuje się, <until>jest zawarte w zakresie i stanie się nowym HEADpo zakończeniu rebase.


1
Niezła odpowiedź. Tylko mały dodatek do przypadku ogólnego: <oldparent>nazwa nie działa, jeśli dwie części zakresu znajdują się w różnych gałęziach. Generalnie jest to: „Uwzględnij wszystkie zatwierdzenie, które jest osiągalne z, <until>ale wyklucz każde zatwierdzenie, które jest osiągalne z <oldparent>”.
musiKk

50
git rebase --onto <newparent> <oldparent>jest najlepszym wyjaśnieniem zachowania, jakie widziałem!
ronkot

4
Dzięki! Trochę walczyłem z tą --ontoopcją, ale to sprawiło, że stało się jasne! Nawet nie rozumiem, jak nie mogłem tego wcześniej zrozumieć: D Dzięki za doskonały "tutorial" :-)
grongor

3
Chociaż ta odpowiedź jest doskonała, myślę, że nie obejmuje wszystkich możliwych przypadków. Ostatnia forma składni może również służyć do wyrażenia bardziej subtelnego typu rebase. Wychodząc z przykładu w Pro Git (wyd. 2), D niekoniecznie musi być przodkiem H. Zamiast tego D i H mogą być również zatwierdzeniami wspólnego przodka - w tym przypadku Git znajdzie ich wspólnego przodka i powtórka od tego przodka do H na F.
Pastafarian

1
To było pomocne. Strona podręcznika w ogóle nie wyjaśnia argumentów.
a544jh

61

To wszystko, co musisz wiedzieć, aby zrozumieć --onto:

git rebase --onto <newparent> <oldparent>

Przełączasz rodzica na zatwierdzenie, ale nie podajesz sha tego zatwierdzenia, tylko sha jego obecnego (starego) rodzica.


4
Krótko i łatwo. Właściwie uświadomienie sobie, że muszę zapewnić zobowiązanie rodzica tego, którego chcę zmienić, zamiast tego zobowiązania, zajęło mi najwięcej czasu.
Antoniossss

1
Ważnym szczegółem jest to, że wybierasz dziecko starego rodzica z bieżącej gałęzi, ponieważ jedno zatwierdzenie może być rodzicem dla wielu zatwierdzeń, ale kiedy ograniczysz się do bieżącej gałęzi, zatwierdzenie może być rodzicem tylko dla jednego zatwierdzenia. Innymi słowy, statek relacji nadrzędnej jest unikalny w gałęzi, ale nie musi być, jeśli nie określisz oddziału.
Trismegistos

2
Uwaga: musisz być w oddziale lub dodać nazwę oddziału jako trzeci parametr git rebase --onto <newparent> <oldparent> <feature-branch>
Jason Portnoy

1
Ta odpowiedź jest niesamowita, prosto do celu w tym wątku
John Culviner,

13

Krótko mówiąc, biorąc pod uwagę:

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

Który jest taki sam jak (ponieważ --ontoprzyjmuje jeden argument):

git rebase D H --onto F

Środki rebase zobowiązuje się w zakresie (D, H] na górze F. Zawiadomienie zakres jest lewa wykluczają. Jest to ekskluzywny, ponieważ łatwiej jest określić 1-ci popełnić wpisując np branchpozwolić gitznaleźć 1st odbiegał popełnić od branchznaczy Dco prowadzi do H.

Sprawa OP

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

Można zmienić na pojedyncze polecenie:

git rebase --onto B A B

To, co wygląda na błąd, to umieszczenie, Bktóre oznacza „przenieś niektóre zatwierdzenia, które prowadzą do gałęzi Bna wierzch B”. Pytanie brzmi, czym są „niektóre zatwierdzenia”. Jeśli dodasz -iflagę, zobaczysz, że jest to pojedyncze zatwierdzenie, na które wskazuje HEAD. Zatwierdzenie jest pomijane, ponieważ zostało już zastosowane do --ontocelu Bi nic się nie dzieje.

To polecenie jest bezsensowne w każdym przypadku, gdy nazwa gałęzi jest powtarzana w ten sposób. Dzieje się tak, ponieważ zakres zatwierdzeń będzie obejmował niektóre zatwierdzenia, które są już w tej gałęzi, a podczas rebase wszystkie zostaną pominięte.

Dalsze wyjaśnienia i zastosowanie git rebase <upstream> <branch> --onto <newbase>.

git rebase domyślne.

git rebase master

Rozwija się do:

git rebase --onto master master HEAD
git rebase --onto master master current_branch

Automatyczne kasy po rebase.

W przypadku standardowego użycia, na przykład:

git checkout branch
git rebase master

Nie zauważysz, że po rebase gitprzechodzi branchdo ostatniego zmiany bazy danych i robi git checkout branch(zobacz git refloghistorię). Co jest interesujące, gdy drugim argumentem jest wartość skrótu commita, zamiast tego nazwa gałęzi rebase nadal działa, ale nie ma gałęzi do przeniesienia, więc kończysz w "odłączonym HEAD" zamiast wyrejestrowywania do przeniesionej gałęzi.

Pomiń pierwotne rozbieżne zatwierdzenia.

masterW --ontopochodzi z 1 git rebaseargument.

                   git rebase master
                              /    \
         git rebase --onto master master

Tak praktyczne, że może to być dowolne inne zatwierdzenie lub gałąź. W ten sposób możesz ograniczyć liczbę zatwierdzeń rebase, biorąc najnowsze i pozostawiając pierwotne rozbieżne zatwierdzenia.

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

Przebuduje pojedyncze zatwierdzenie wskazywane przez HEADto masteri zakończy się w „odłączonym HEAD”.

Unikaj jawnych kasów.

Wartość domyślna HEADlub current_branchargument jest pobierana kontekstowo z miejsca, w którym się znajdujesz. Dlatego większość ludzi zameldowuje się w oddziale, w którym chce zmienić bazę. Ale kiedy drugi argument rebase zostanie podany jawnie, nie musisz wyewidencjonowywać go przed rebase, aby przekazać go w sposób niejawny.

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

Oznacza to, że możesz zmienić bazę zatwierdzeń i rozgałęzień z dowolnego miejsca. A więc razem z automatycznym kasowaniem po ponownymbase. nie musisz osobno płacić oddziału z rebase'em przed lub po rebase.

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.

8

Mówiąc najprościej, git rebase --ontowybiera zakres zatwierdzeń i opiera je na zatwierdzeniu podanym jako parametr.

Przeczytaj strony podręcznika man git rebase, wyszukaj „na”. Przykłady są bardzo dobre:

example of --onto option is to rebase part of a branch. If we have the following situation:

                                   H---I---J topicB
                                  /
                         E---F---G  topicA
                        /
           A---B---C---D  master

   then the command

       git rebase --onto master topicA topicB

   would result in:

                        H'--I'--J'  topicB
                       /
                       | E---F---G  topicA
                       |/
           A---B---C---D  master

W tym przypadku mówisz gitowi, aby zmienił bazę zatwierdzeń od topicAdo topicBna górze master.


8

Aby lepiej zrozumieć różnicę między git rebasei git rebase --ontodobrze jest wiedzieć, jakie są możliwe zachowania obu poleceń. git rebasepozwalają nam przenieść nasze zatwierdzenia na wybraną gałąź. Jak tutaj:

git rebase master

a wynik to:

Before                              After
A---B---C---F---G (master)          A---B---C---F---G (master)
         \                                           \
          D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)

git rebase --ontojest bardziej precyzyjny. Pozwala nam wybrać konkretne zatwierdzenie, od którego chcemy zacząć, a także gdzie chcemy zakończyć. Jak tutaj:

git rebase --onto F D

a wynik to:

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)

Aby uzyskać więcej informacji, polecam zapoznać się z moim własnym artykułem na temat przeglądu git rebase --onto


@Makyen Jasne, będę o tym pamiętać w przyszłości :)
womanonrails

Więc możemy odczytać git rebase --onto F Djako ustawione dziecko rodzica D jako F , prawda?
Prihex

2

Dla ontopotrzebne są dwa dodatkowe oddziały. Za pomocą tego polecenia możesz zastosować zmiany z branchBbazujące branchAna innej gałęzi, np master. W poniższym przykładzie branchBjest oparty na branchAi chcesz zastosować zmiany branchBon masterbez stosowania zmian branchA.

o---o (master)
     \
      o---o---o---o (branchA)
                   \
                    o---o (branchB)

za pomocą poleceń:

checkout branchB
rebase --onto master branchA 

otrzymasz następującą hierarchię zatwierdzeń.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)

1
Czy możesz wyjaśnić trochę więcej, jeśli chcemy zmienić bazę na master, dlaczego staje się ona aktualną gałęzią? Jeśli to zrobisz, czy rebase --onto branchA branchBoznaczałoby to postawienie całej gałęzi głównej na czele gałęzi A?
Polymerase

8
nie powinno tak być checkout branchB: rebase --onto master branchA?
goldenratio

4
Dlaczego głosowano za tym? to nie robi tego, co mówi, że robi.
De Novo

Zredagowałem i poprawiłem odpowiedź, więc ludzie nie muszą najpierw łamać swoich gałęzi repozytorium, a zaraz potem przychodzą i czytają komentarze ... 🙄
Kamafeather

0

Jest jeszcze jeden przypadek, w którym git rebase --ontojest trudny do zrozumienia: kiedy ponownie bazujesz na zatwierdzeniu wynikającym z selektora symetrycznej różnicy (trzy kropki ' ...')

Git 2.24 (Q4 2019) radzi sobie lepiej z tym przypadkiem:

Zobacz commit 414d924 , commit 4effc5b , commit c0efb4c , commit 2b318aa (27 sierpnia 2019) i commit 793ac7e , commit 359eceb (25 sierpnia 2019) autorstwa Denton Liu ( Denton-L) .
Z pomocą: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) i Johannes Schindelin ( dscho) .
Zobacz commit 6330209 , commit c9efc21 (27 sierpnia 2019) i commit 4336d36 (25 sierpnia 2019) autorstwa Ævar Arnfjörð Bjarmason ( avar).
Z pomocą: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) i Johannes Schindelin ( dscho) .
(Scalone przez Junio ​​C Hamano - gitster- w zatwierdzeniu 640f9cd , 30 września 2019)

rebase: przewijanie do przodu --ontow większej liczbie przypadków

Wcześniej, gdy mieliśmy następujący wykres,

A---B---C (master)
     \
      D (side)

running ' git rebase --onto master... master side' skutkowałoby Dzmianą bazy, bez względu na wszystko.

W tym miejscu przeczytaj " Jakie są różnice między podwójną kropką .." a potrójną kropką " ..." w zakresach zatwierdzania Git diff? "

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

Tutaj: „ master...” odnosi się do master...HEAD, czyli B: HEAD to strona HEAD (aktualnie wyrejestrowana): zmieniasz bazę B.
Co podważasz? Dowolne zatwierdzenie, które nie jest w master i jest dostępne z sidegałęzi: jest tylko jeden zatwierdzenie pasujące do tego opisu: D... który już jest na wierzchu B!

Ponownie, przed Git 2.24, takie rebase --ontorozwiązanie Dzawsze powodowałoby ponowne bazowanie, bez względu na wszystko.

Jednak pożądanym zachowaniem jest to, że rebase powinien zauważyć, że można to szybko przewinąć, i zamiast tego zrobić to.

To jest podobne do rebase --onto B APO, który nic nie zrobił.

Dodaj wykrywanie do can_fast_forward, aby ten przypadek mógł zostać wykryty i zostanie przeprowadzone szybkie przewijanie do przodu.
Przede wszystkim należy przepisać funkcję, aby używała gotos, co upraszcza logikę.
Następnie, ponieważ

options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)

warunki zostały usunięte w cmd_rebase, ponownie wprowadzamy zamiennik can_fast_forward.
W szczególności sprawdzanie podstaw scalania upstreami headnaprawianie nieprawidłowego przypadku w t3416.

Skrócony wykres dla t3416 przedstawia się następująco:

        F---G topic
       /
  A---B---C---D---E master

a polecenie upadło

git rebase --onto master...topic F topic

Wcześniej Git widział, że istnieje jedna podstawa scalania ( Cwynik master...topic), a scalanie i do były takie same, więc niepoprawnie zwracał 1, wskazując, że możemy przewinąć do przodu. Spowodowałoby to, że zmieniony wykres byłby ABCFG„kiedy oczekiwaliśmy ABCG”.

A rebase --onto C F topicoznacza każde zatwierdzenie po F , osiągalne przez topicHEAD: to jest Gtylko, a nie Fsiebie.
W tym przypadku Fprzewijanie do przodu obejmowałoby gałąź ponownie bazującą, co jest błędne.

Dzięki dodatkowej logice wykrywamy, że podstawa scalania w górę i w głowę jest F. Ponieważ on nie jest F, oznacza to, że nie zmieniamy podstawy pełnego zestawu zatwierdzeń z master..topic.
Ponieważ wykluczamy niektóre zatwierdzenia, nie można wykonać szybkiego przewijania do przodu, więc poprawnie zwracamy 0.

Dodaj „ -f” do przypadków testowych, które zakończyły się niepowodzeniem w wyniku tej zmiany, ponieważ nie spodziewały się szybkiego przewinięcia do przodu, więc wymuszona jest rebase.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.