Git push odrzucony po zmianie gałęzi funkcji

916

OK, myślałem, że to prosty scenariusz git, czego mi brakuje?

Mam masteroddział i featureoddział. Trochę pracuję master, trochę dalej feature, a potem jeszcze trochę dalej master. Kończę z czymś takim (kolejność leksykograficzna oznacza kolejność zatwierdzeń):

A--B--C------F--G  (master)
       \    
        D--E  (feature)

Nie mam problemu z aktualizowaniem git push origin masterpilota masterani z git push origin feature(gdy jest on feature) utrzymywaniem zdalnej kopii zapasowej dla mojej featurepracy. Do tej pory jesteśmy dobrzy.

Ale teraz chcę bazować featurena F--Gzatwierdzeniach na master, więc ja git checkout featurei git rebase master. Nadal dobrze. Teraz mamy:

A--B--C------F--G  (master)
                 \
                  D'--E'  (feature)

Problem: w momencie, gdy chcę wykonać kopię zapasową nowego, opartego na featurerozgałęzieniu git push origin feature, push jest odrzucany, ponieważ drzewo zmieniło się z powodu zmiany. Można to rozwiązać tylko za pomocą git push --force origin feature.

Nienawidzę używania, --forcenie będąc pewnym, że go potrzebuję. Więc potrzebuję tego? Czy ponowne bazowanie niekoniecznie oznacza, że ​​następne pushpowinno być --forcepełne?

Ta gałąź funkcji nie jest dzielona z innymi programistami, więc nie mam problemu de facto z siłą nacisku, nie zamierzam stracić żadnych danych, pytanie jest bardziej koncepcyjne.

Yuval Adam
źródło

Odpowiedzi:

681

Problem polega na tym, że git pushzakłada się, że gałąź zdalna może być szybko przekazywana do gałęzi lokalnej, to znaczy, że cała różnica między gałęziami lokalnymi i zdalnymi polega na tym, że na lokalnych lokalach znajdują się nowe zatwierdzenia:

Z--X--R         <- origin/some-branch (can be fast-forwarded to Y commit)
       \        
        T--Y    <- some-branch

Podczas wykonywania git rebasezatwierdzeń D i E są stosowane do nowej bazy i tworzone są nowe zatwierdzenia. Oznacza to, że po rebase masz coś takiego:

A--B--C------F--G--D'--E'   <- feature-branch
       \  
        D--E                <- origin/feature-branch

W takiej sytuacji zdalnej gałęzi nie można szybko przekazać do lokalnego. Chociaż teoretycznie gałąź lokalna może zostać scalona w zdalną (oczywiście nie jest to potrzebne w tym przypadku), ale ponieważ git pushwykonuje tylko scalanie z przewijaniem do przodu, generuje błąd.

A --forceopcja polega na ignorowaniu stanu zdalnej gałęzi i ustawianiu zatwierdzenia, które do niej wpychasz. Więc git push --force origin feature-branchpo prostu zastępuje origin/feature-branchlokalny feature-branch.

Moim zdaniem ponowne włączanie gałęzi funkcji masteri wypychanie ich z powrotem do zdalnego repozytorium jest OK, o ile tylko ty pracujesz w tej gałęzi.

KL-7
źródło
67
Szczerze mówiąc, przeciągnięcie i połączenie oryginalnej wersji gałęzi funkcji w jeden z rebased eliminuje całą ideę rebasingu.
KL-7
24
Może nie zrozumiałem cię poprawnie, ale jeśli wyciągniesz gałąź funkcji, zmienisz jej bazę na nową gałąź główną, nie możesz odepchnąć jej bez użycia siły, ponieważ zdalnej wersji gałęzi funkcji nie można szybko przekazać do nowej (ponownie wydana) wersja gałęzi funkcji. Dokładnie tak OP opisał w swoim pytaniu. Jeśli po zmianie bazy, ale przed wypchnięciem, zrobisz git pull feature-branch, to ściągnięcie wygeneruje nowe zatwierdzenie scalania (poprzez połączenie zdalnych i lokalnych wersji gałęzi funkcji). Więc albo otrzymujesz niepotrzebne scalenie po zmianie bazy, albo naciskasz --force.
KL-7
6
Ach, chyba to rozumiem. Opisujesz to samo podejście, co w odpowiedzi Marka Longaira. Ale generuje zatwierdzenie scalania. Może to być przydatne w niektórych przypadkach, ale używam bazy głównie w moich własnych gałęziach funkcji (stąd push --forcenie stanowi problemu), aby zachować historię zatwierdzeń liniowo bez żadnych zatwierdzeń scalania.
KL-7
11
Problem z „force-push” polega na tym, że rzeczywiście można „stracić rzeczy” (wcześniejsze zatwierdzenia), coś, co normalnie NIGDY nie powinno być możliwe w żadnym systemie kontroli wersji ➪ Z tego powodu przynajmniej jedna gałąź „master-ish” powinna mieć ustawienia, które nie akceptują nacisków , aby ograniczyć potencjalne uszkodzenia. (Wymień jedno z następujących: zrzędliwi / zwolnieni pracownicy, własny idiotyzm, zmęczone i przepracowane „decyzje” ...).
Frank Nocke,
13
--force-with-leasejak sugeruje @hardev, jest świetną opcją
augustorsouza
464

Zamiast używać -f lub --force programiści powinni używać

--force-with-lease

Dlaczego? Ponieważ sprawdza zdalną gałąź pod kątem zmian, co jest absolutnie dobrym pomysłem. Wyobraźmy sobie, że James i Lisa pracują nad tym samym działem funkcji, a Lisa wprowadziła zatwierdzenie. James zmienia teraz swój oddział lokalny i jest odrzucany, gdy próbuje pchać. Oczywiście James uważa, że ​​dzieje się tak z powodu zmiany bazy i użycia - force i przepisałby wszystkie zmiany Lisy. Gdyby James użył opcji - force-with-leasing, otrzymałby ostrzeżenie, że ktoś dopuścił się popełnienia przestępstwa. Nie rozumiem, dlaczego ktokolwiek używałby --force zamiast --force-with-lease podczas wypychania po rebase.

Hardev
źródło
33
Świetne wyjaśnienie. git push --force-with-leaseuratował mi sporo.
ckib16
5
To przydatny komentarz, ale tak naprawdę nie jest odpowiedzią na pytanie.
Dallin,
4
To jest odpowiedź, zmiana bazy na master / develop tworzy problem, właśnie dlatego istnieje --force-with-leasing.
Tamir Daniely,
3
To powinna być zaakceptowana odpowiedź. Rozwiązuje dokładnie opisany problem - wymusza pchanie bez zmuszania, jeśli ktoś w międzyczasie popełnił błąd.
Luzian
3
Myślę, że zarówno zaakceptowana odpowiedź, jak i ta, stanowią odpowiedź na pytanie. Akceptowana odpowiedź wyjaśnia, dlaczego musisz wymusić. I to wyjaśnia, dlaczego --force-with-leaserozwiązuje problem używania--force
Jeff Appareti
47

Zamiast tego użyłbym „checkout -b” i łatwiej to zrozumieć.

git checkout myFeature
git rebase master
git push origin --delete myFeature
git push origin myFeature

po usunięciu uniemożliwisz wypchnięcie zamykającej gałęzi, która zawiera inny identyfikator SHA. W tym przypadku usuwam tylko gałąź zdalną.

Eddy Hernandez
źródło
6
Działa to świetnie, szczególnie jeśli twój zespół ma haczyk do git, który odrzuca wszystkie polecenia git push - force.
Ryan Thames,
1
dziękuję za to, że działało dobrze. Oto więcej szczegółów na ten temat, które czytam, aby lepiej zrozumieć. Jest to bardzo przydatne, gdy nie chcesz lub nie możesz użyć siły. Usuwanie zdalnych gałęzi i przywracanie
RajKon
5
Ma to ten sam wynik, co push --force, więc jest to tylko sposób na obejście repozytorium git --force. W związku z tym nie sądzę, aby był to zawsze dobry pomysł - albo repo na to pozwala push --force, albo z dobrego powodu go wyłącza. Odpowiedź Nabi jest bardziej odpowiednia, jeśli --forcejest wyłączona na zdalnym repozytorium, ponieważ nie ma ryzyka utraty zatwierdzeń od innych programistów lub spowodowania innych problemów.
Logan Pickup
19

Jednym z rozwiązań jest zrobienie tego, co robi skrypt scalania rebasingu msysGit - po rebase połącz się ze starą głową featurez -s ours. Skończysz z wykresem zatwierdzenia:

A--B--C------F--G (master)
       \         \
        \         D'--E' (feature)
         \           /
          \       --
           \    /
            D--E (old-feature)

... a twoje pchnięcie featurebędzie szybkie.

Innymi słowy, możesz wykonać:

git checkout feature
git branch old-feature
git rebase master
git merge -s ours old-feature
git push origin feature

(Nie testowano, ale myślę, że to prawda ...)

Mark Longair
źródło
26
Uważam, że najczęstszym powodem używania git rebase(zamiast ponownego łączenia masterz gałęzią funkcji) jest tworzenie historii czystych liniowych zatwierdzeń. Z twoim podejściem historia staje się jeszcze gorsza. A ponieważ rebasing tworzy nowe zatwierdzenia bez odniesienia do ich poprzednich wersji, nie jestem nawet pewien, czy wynik tego scalenia będzie odpowiedni.
KL-7
6
@ KL-7: Chodzi o merge -s oursto, że sztucznie dodaje odwołanie nadrzędne do poprzedniej wersji. Jasne, historia nie wygląda na czystą, ale wydaje się, że pytający jest szczególnie zaniepokojony tym, że musi wymusić nacisk featuregałęzi, i to się omija. Jeśli chcesz dokonać zmiany bazy, to mniej więcej jedno lub drugie. :) Ogólnie rzecz biorąc, myślę, że interesujące jest to, że projekt
msysgit
@ KL-7: Nawiasem mówiąc, dałem +1 twojej odpowiedzi, która jest oczywiście właściwa - pomyślałem, że to też może być interesujące.
Mark Longair,
To zdecydowanie interesujące, przynajmniej dla mnie. Dziękuję Ci. oursWcześniej widziałem strategię, ale pomyślałem, że ma ona zastosowanie tylko do sytuacji konfliktowych, automatycznie rozwiązując je za pomocą zmian w naszym oddziale. Okazało się, że działa inaczej. I działanie w ten sposób jest bardzo przydatne, jeśli potrzebujesz wersji bazowej (np. Do opiekuna repozytoriów, aby zastosować ją czysto master), ale chcesz uniknąć wymuszania wypychania (jeśli wiele innych ppl z jakiegoś powodu używa twojej gałęzi funkcji).
KL-7
15

Może się zdarzyć, że w tej gałęzi jest tylko jeden programista, który teraz (po zmianie bazy) nie jest zgodny z początkiem / funkcją.

Jako taki sugerowałbym użycie następującej sekwencji:

git rebase master
git checkout -b feature_branch_2
git push origin feature_branch_2

Tak, nowy oddział, to powinno rozwiązać ten problem bez użycia siły, co, jak sądzę, ogólnie jest poważną wadą git.

JAR.JAR. beans
źródło
3
Przykro mi to mówić, ale: „Ciągłe generowanie gałęzi”, aby uniknąć wymuszenia zastąpienia istniejących, nie pomaga „samotnym programistom funkcji” (którzy mogą przesłonić) ani wielu osobom pracującym nad gałęzią funkcji (trzeba przekazać ten przyrost „gałęzi” i powiedzieć przejść, ludzie). - To bardziej jak ręczne wersjonowanie („thesis_00.doc, thesis_01.doc, ...”) w ramach systemu wersjonowania ...
Frank Nocke
2
Dodatkowo, to nie pomaga, gdy masz github PR otwarty dla jednej nazwy oddziału, musisz utworzyć nowy PR dla nowej nazwy oddziału, którą wypchnąłeś.
gprasant
1
@frankee Połowa prawdy z mojego doświadczenia. dla samotnego programisty, tak, tylko forsowanie jest dość łatwe, ale jest to nawyk, który może cię ugryźć później. + właśnie dołączył nowy deweloper? a może jakiś system CI, który nie korzysta z --hard reset? w przypadku zespołu współpracującego, myślę, że podanie nowej nazwy oddziału jest dość łatwe, można to również łatwo napisać w skrypcie + dla zespołu, sugerowałbym bazować lokalnie lub gdy oddział jest gotowy do połączenia, a nie w codziennej pracy , dodatkowe zatwierdzenie jest mniej kłopotliwe niż rozwiązywanie konfliktów rebase / merge.
JAR.JAR. beans
@ gprasant dla PR, znowu, myślę, że to byłoby niewłaściwe, aby rebase, naprawdę chciałbym zobaczyć pojedyncze zatwierdzenia z poprawkami PR. Rebase (squash) powinien nastąpić dopiero później jako część scalenia do opanowania i kiedy PR jest już gotowy i gotowy (więc nie trzeba otwierać nowego PR).
JAR.JAR. beans
13

Moim sposobem na uniknięcie wymuszenia siły jest utworzenie nowej gałęzi i kontynuowanie pracy nad tą nową gałęzią, a po pewnej stabilności usunąć starą gałąź, która została ponownie bazowana:

  • Lokalne ponowne uruchomienie pobranej gałęzi
  • Rozgałęzienie z oddziału opartego na nowej strukturze do nowego oddziału
  • Przekazywanie tej gałęzi jako nowej gałęzi do zdalnego. i usuwanie starego oddziału na pilocie
Nabi
źródło
1
Dlaczego nie ma miłości do tej opcji? Jest to zdecydowanie najczystszy, najprostszy, najbezpieczniejszy.
cdmo
Ponieważ mam około 200 systemów śledzących nazwę gałęzi i musi to być konkretna nazwa dla zadania, a jeśli zacznę zmieniać nazwy gałęzi za każdym razem, stracę rozum.
Tamir Daniely,
@TamirDaniely Nie próbowałem, ale czy usunięcie starej gałęzi (zdalnie) przed wypychaniem i wypychaniem nowej gałęzi o tej samej starej nazwie rozwiązuje problem?
Nabi,
2
@Nabi To właśnie robi --force-with-lease, z tym wyjątkiem, że weryfikuje również, czy nie ma żadnych nowych zatwierdzeń, które nie byłyby twoje.
Tamir Daniely,
12

Inni odpowiedzieli na twoje pytanie. Jeśli zmienisz gałąź, będziesz musiał zmusić ją do popchnięcia.

Rebase i wspólne repozytorium na ogół się nie dogadują. To jest przepisywanie historii. Jeśli inni używają tej gałęzi lub rozgałęziają się z tej gałęzi, zmiana bazy będzie dość nieprzyjemna.

Ogólnie rzecz biorąc, rebase działa dobrze w przypadku zarządzania oddziałami lokalnymi. Zdalne zarządzanie oddziałami działa najlepiej w przypadku jawnych połączeń (--no-ff).

Unikamy również scalania wzorca w gałąź funkcji. Zamiast tego bazujemy na master, ale z nową nazwą oddziału (np. Dodając sufiks wersji). Pozwala to uniknąć problemu zmiany bazy w repozytorium współdzielonym.

Bill Door
źródło
5
Czy możesz dodać przykład?
Thermech,
8

Co jest złego git merge masterw featuregałęzi? Pozwoli to zachować pracę, którą wykonałeś, jednocześnie oddzielając ją od gałęzi głównej.

A--B--C------F--G
       \         \
        D--E------H

Edycja: Przepraszam, nie przeczytałem zgłoszenia problemu. Będziesz potrzebował siły podczas wykonywania rebase. Wszystkie polecenia modyfikujące historię będą wymagały --forceargumentu. Jest to bezpieczne, aby zapobiec utracie pracy (stare Di Ezostałyby utracone).

Więc wykonałeś polecenie, git rebasektóre sprawiło, że drzewo wyglądało (chociaż częściowo ukryte jako Di Enie ma już w nazwanej gałęzi):

A--B--C------F--G
       \         \
        D--E      D'--E'

Tak więc, próbując wcisnąć nowy featureoddział (z nim D'i E'w nim), stracisz Di E.

Bouke
źródło
3
Nie ma w tym nic złego i wiem, że to zadziała. Po prostu nie tego potrzebuję. Jak powiedziałem, pytanie jest bardziej koncepcyjne niż praktyczne.
Yuval Adam
4

Dla mnie działają następujące proste kroki:

1. git checkout myFeature
2. git rebase master
3. git push --force-with-lease
4. git branch -f master HEAD
5. git checkout master
6. git pull

Po wykonaniu powyższych czynności możemy również usunąć gałąź myFeature, wykonując następujące polecenie:

git push origin --delete myFeature
Neeraj Khede
źródło
3

Poniższe działa dla mnie:

git push -f origin branch_name

i nie usuwa żadnego mojego kodu.

Ale jeśli chcesz tego uniknąć, możesz wykonać następujące czynności:

git checkout master
git pull --rebase
git checkout -b new_branch_name

wtedy możesz wybrać wszystkie swoje zobowiązania do nowego oddziału. git cherry-pick COMMIT ID a następnie pchnij nowy oddział.

Sohair Ahmad
źródło
5
-fjest pseudonimem --force, dlatego w miarę możliwości pytanie próbuje tego uniknąć.
epochengine
1

Ponieważ PO rozumie problem, po prostu szuka ładniejszego rozwiązania ...

Co powiesz na to jako praktykę?

  • Posiadaj w branży rozwijającej funkcje (w której nigdy nie bazujesz na bazach i nie naciskasz na siłę, więc inni twórcy funkcji nie nienawidzą cię). Tutaj regularnie pobieraj te zmiany z głównego za pomocą scalenia. Messier history , tak, ale życie jest łatwe i nikt nie przeszkadza w jego pracy.

  • Mają drugą gałąź opracowywania funkcji, w której jeden członek zespołu ds. Funkcji normalnie popycha wszystkie zmiany funkcji, a nawet przekształca je, a nawet wymusza. Więc prawie czysto w oparciu o dość niedawne zatwierdzenie główne. Po zakończeniu funkcji przesuń gałąź na master.

Może już istnieć nazwa wzorca dla tej metody.

Frank Nocke
źródło