Jak mogę odzyskać / ponownie zsynchronizować po tym, jak ktoś wypchnie rebase lub reset do opublikowanej gałęzi?

88

Wszyscy słyszeliśmy, że nigdy nie powinno się zmieniać bazy opublikowanych prac, że jest to niebezpieczne itp. Jednak nie widziałem żadnych opublikowanych przepisów na to, jak radzić sobie w sytuacji, gdy zostanie opublikowana rebase .

Teraz zwróć uwagę, że jest to naprawdę wykonalne tylko wtedy, gdy repozytorium jest sklonowane tylko przez znaną (i najlepiej małą) grupę ludzi, tak aby każdy, kto popycha rebase lub reset, mógł powiadomić wszystkich innych, że będzie musiał zwrócić uwagę następnym razem sprowadzać(!).

Jedno oczywiste rozwiązanie, które widziałem, zadziała, jeśli nie masz lokalnych zatwierdzeń fooi zostanie zmienione:

git fetch
git checkout foo
git reset --hard origin/foo

Spowoduje to po prostu odrzucenie lokalnego stanu foona korzyść jego historii zgodnie ze zdalnym repozytorium.

Ale jak sobie radzić z sytuacją, jeśli w tej branży nastąpiły istotne zmiany lokalne?

Arystoteles Pagaltzis
źródło
+1 za prosty przepis na skrzynkę. Jest idealny do osobistej synchronizacji między komputerami, zwłaszcza jeśli mają różne systemy operacyjne. To coś, o czym należy wspomnieć w instrukcji.
Philip Oakley,
Idealny przepis na osobistą synchronizację to git pull --rebase && git push. Jeśli będziesz pracować mastertylko nad tym, to prawie niezawodnie zrobi to, co trzeba, nawet jeśli zmienisz bazę i popchniesz na drugim końcu.
Arystoteles Pagaltzis
Ponieważ synchronizuję i programuję między komputerem PC a maszynami z systemem Linux, uważam, że używanie nowej gałęzi do każdej rebase / aktualizacji działa dobrze. Używam również tego wariantu git reset --hard @{upstream}teraz, gdy wiem, że magiczne zaklęcie refspec dla „zapomnij, co mam / miałem, użyj tego, co pobrałem z pilota”. Zobacz mój ostatni komentarz do stackoverflow.com/a/15284176/717355
Philip Oakley
Dzięki Git2.0 będziesz mógł znaleźć stare pochodzenie swojej gałęzi (zanim gałąź upstream została przepisana za pomocą a push -f): zobacz moją odpowiedź poniżej
VonC

Odpowiedzi:

75

W większości przypadków przywrócenie synchronizacji po wymuszonej rebase nie jest tak skomplikowane.

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

To znaczy. najpierw ustawiasz zakładkę dla miejsca, w którym pierwotnie znajdowała się zdalna gałąź, a następnie używasz jej do odtwarzania lokalnych zatwierdzeń od tego momentu do zdalnej gałęzi ponownie bazującej.

Rebasing jest jak przemoc: jeśli nie rozwiąże twojego problemu, potrzebujesz go więcej. ☺

Możesz to zrobić oczywiście bez zakładki, jeśli odszukasz origin/fooidentyfikator zatwierdzenia przed rebase i użyjesz go.

W ten sposób radzisz sobie również z sytuacją, w której zapomniałeś utworzyć zakładkę przed pobraniem. Nic nie jest stracone - wystarczy sprawdzić reflog dla zdalnego oddziału:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Spowoduje to wydrukowanie identyfikatora zatwierdzenia, który origin/foowskazywał przed ostatnim pobieraniem, które zmieniło jego historię.

Możesz wtedy po prostu

git rebase --onto origin/foo $commit foo
Arystoteles Pagaltzis
źródło
11
Szybka uwaga: myślę, że jest to całkiem intuicyjne, ale jeśli nie znasz dobrze awk ... ten git reflog show origin/foojednolinijkowy po prostu przegląda dane wyjściowe polecenia dla pierwszej linii i mówi „pobierz: wymuszona aktualizacja”; to właśnie rejestruje git, gdy pobieranie powoduje, że zdalna gałąź robi cokolwiek innego niż przewijanie do przodu. (Możesz też zrobić to ręcznie - wymuszona aktualizacja jest prawdopodobnie najnowszą rzeczą).
Cascabel
2
To nie jest przemoc. Przemoc jest czasami zabawna
Iolo,
5
@iolo Prawda, zmiana bazy jest zawsze fajna.
Dan Bechard,
1
Podobnie jak przemoc, prawie zawsze unikaj zmiany bazy. Ale miej wskazówkę, jak to zrobić.
Bob Stein
2
Cóż, unikaj popychania rebase, na które wpłynie to na innych.
Arystoteles Pagaltzis
11

Powiedziałbym, że sekcja odzyskiwania z górnej części strony podręcznika git-rebase obejmuje prawie wszystko.

Naprawdę nie różni się to od odzyskiwania z własnej bazy - przenosisz jedną gałąź i ponownie bazujesz wszystkie gałęzie, które miały ją w swojej historii, na nową pozycję.

Cascabel
źródło
4
Ach, więc to robi. Ale chociaż teraz rozumiem, co mówi, nie miałbym tego wcześniej, zanim sam to zrozumiałem. I nie ma przepisu na książkę kucharską (być może słusznie w takiej dokumentacji). Wytłumaczę również, że nazywanie „twardej sprawy” trudnym jest FUD. Uważam, że przepisana historia jest trywialnie zarządzalna w skali większości wewnętrznych prac. Irytuje mnie przesądny sposób, w jaki ten temat jest zawsze traktowany.
Arystoteles Pagaltzis
4
@Aristotle: Masz rację, że jest bardzo łatwy w zarządzaniu, biorąc pod uwagę, że wszyscy programiści wiedzą, jak używać git i że możesz skutecznie komunikować się ze wszystkimi programistami. W idealnym świecie to byłby koniec historii. Ale wiele projektów jest na tyle dużych, że rebase z zewnątrz jest naprawdę przerażający. (Są też miejsca, takie jak moje miejsce pracy, gdzie większość deweloperów nigdy nawet nie słyszała o rebase.) Myślę, że „przesądy” to tylko sposób na zapewnienie najbezpieczniejszej, najbardziej ogólnej porady. Nikt nie chce być tym, który powoduje katastrofę w czyimś repozytorium.
Cascabel
2
Tak, rozumiem motyw. I w pełni się z tym zgadzam. Ale jest ogromna różnica między „nie próbuj tego, jeśli nie rozumiesz konsekwencji”, a „nigdy nie powinieneś tego robić, ponieważ jest to złe”, i tylko z tym nie zgadzam się. Zawsze lepiej jest pouczać niż wzbudzać strach.
Arystoteles Pagaltzis
@Aristotle: Zgoda. Staram się dążyć do końca „upewnij się, że wiesz, co robisz”, ale szczególnie w Internecie staram się nadać mu odpowiednią wagę, aby zwykły gość z Google to zauważył. Masz rację, wiele z nich powinno być prawdopodobnie stonowanych.
Cascabel
11

Począwszy od git 1,9 / 2,0 Q1 2014, nie będzie musiał zaznaczyć swoją poprzednią pochodzenie oddział przed przebazowania go na przepisany upstream oddziału, jak opisano w Arystoteles Pagaltzis „s odpowiedź :
See popełnić 07d406b i popełnić d96855f :

Po pracy nad topicgałęzią utworzoną w programie git checkout -b topic origin/master, historia gałęzi ze zdalnym śledzeniem origin/mastermogła zostać przewinięta i przebudowana, co prowadzi do historii o takim kształcie:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

gdzie origin/masterwykorzystywane do punktu, w zatwierdzeń B3, B2, B1a teraz wskazuje na to B, a topicoddział został uruchomiony na nim z powrotem, gdy origin/masterbył na B3.

Ten tryb wykorzystuje reflog of origin/masterdo znalezienia B3jako punktu rozwidlenia, dzięki czemu topicmożna zmienić bazę na zaktualizowanąorigin/master przez:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

Dlatego git merge-basepolecenie ma nową opcję:

--fork-point::

Znajdź punkt, w którym gałąź (lub jakakolwiek historia, do której prowadzi <commit>) rozwidliła się z innej gałęzi (lub dowolnego odniesienia) <ref>.
To nie tylko wyszukuje wspólnego przodka dwóch zatwierdzeń, ale także bierze pod uwagę refleksję, <ref>aby zobaczyć, czy historia prowadząca do <commit>rozwidlenia od wcześniejszego wcielenia gałęzi<ref> .


Polecenie " git pull --rebase" oblicza punkt rozwidlenia gałęzi podlegającej zmianie przy użyciu wpisów reflog gałęzi " base" (zazwyczaj gałęzi zdalnego śledzenia), na której była oparta praca gałęzi, aby poradzić sobie z przypadkiem, w którym "baza" oddział został przewinięty i odbudowany.

Na przykład, jeśli historia wyglądała tak, gdzie:

  • aktualny wierzchołek „ base” oddział jest B, ale wcześniej pobieraniu zauważył, że jego końcówka kiedyś B3, a potem B2, a potem B1 , zanim się do prądu popełnić, i
  • gałąź, która jest przebudowywana na najnowszą "bazę", jest oparta na zatwierdzeniu B3,

próbuje znaleźć B3przechodząc przez wyjście „ git rev-list --reflog base” (czyli B, B1, B2, B3), aż znajdzie zobowiązują się, że jest przodkiem obecnego końcówką „ Derived (topic)”.

Wewnętrznie możemy get_merge_bases_many()obliczyć to za jednym razem.
Chcielibyśmy stworzyć bazę scalającą pomiędzy Derivedi fikcyjne zatwierdzenie scalające, które wynikałoby z połączenia wszystkich historycznych wskazówek " base (origin/master)".
Gdy takie zatwierdzenie istnieje, powinniśmy otrzymać pojedynczy wynik, który dokładnie pasuje do jednego z wpisów reflog w „ base”.


Git 2.1 (Q3 2014) doda, że ​​ta funkcja będzie bardziej niezawodna: zobacz commit 1e0dacd autorstwa Johna Keeping ( johnkeeping)

poprawnie obsłużmy scenariusz, w którym mamy następującą topologię:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

gdzie:

  • B'jest poprawioną wersją programu, Bktóra nie jest łatka z B;
  • C*i D*są identyczne z poprawkami Ci Dodpowiednio i powodują konflikt tekstowy, jeśli zostaną zastosowane w złej kolejności;
  • Ezależy od tekstu D.

Prawidłowe wynikiem git rebase master devjest to, że Bjest określona jako widełek punktu devi mastertak, aby C, D, Eto dopuszcza, które muszą być odtwarzane na master; ale Ci Dsą patch-identyczne z C*i D*i tak mogą być usunięte, tak że efekt końcowy to:

o --- B' --- C* --- D* --- E  <- dev

Jeśli punkt rozwidlenia nie zostanie zidentyfikowany, to wybranie Bgałęzi zawierającej B'powoduje konflikt, a jeśli zatwierdzenia identyczne z łatą nie są poprawnie zidentyfikowane, wybranie Cdo gałęzi zawierającej D(lub równoważnie D*) powoduje konflikt.


" --fork-point" Tryb " git rebase" cofnął się, gdy polecenie zostało przepisane w C w 2.20 era, co zostało poprawione w Git 2.27 (Q2 2020).

Zobacz commit f08132f (09 grudnia 2019) autorstwa Junio ​​C Hamano ( gitster) .
(Scalone przez Junio ​​C Hamano - gitster- w zobowiązaniu fb4175b , 27 marca 2020 r.)

rebase: --fork-pointnaprawa regresji

Podpisał: Alex Torok
[jc: odnowił poprawkę i użył testów Alexa]
Podpisał: Junio ​​C Hamano

git rebase --fork-point masterKiedyś „ ” działało OK, jak to wewnętrznie nazywało się „ git merge-base --fork-point”, które wiedziało, jak obsługiwać krótką nazwę referencyjną i skracać ją do pełnej nazwy referencyjnej przed wywołaniem podstawowej get_fork_point()funkcji.

Nie jest to już prawdą po przepisaniu polecenia w C, ponieważ jego wewnętrzne wywołanie bezpośrednio do get_fork_point()nie ogranicza krótkiego odwołania.

Przenieś argument „dwim the refname” do logiki „full refname”, która jest używana w „git merge-base”, do podstawowej get_fork_point()funkcji, tak aby inny wywołujący funkcję w implementacji „git rebase” zachowywał się w ten sam sposób, aby naprawić ten regres.

VonC
źródło
1
Zauważ, że git push --force można teraz (git 1.8.5) wykonać ostrożniej: stackoverflow.com/a/18505634/6309
VonC