Przepływ pracy Git i rebase vs scalanie pytań


970

Korzystam z Git już od kilku miesięcy przy projekcie z innym deweloperem. Mam kilkuletnie doświadczenie z SVN , więc chyba przynoszę ze sobą dużo bagażu.

Słyszałem, że Git doskonale nadaje się do rozgałęziania i łączenia, a jak dotąd nie widzę tego. Jasne, rozgałęzienie jest bardzo proste, ale kiedy próbuję się połączyć, wszystko idzie do piekła. Teraz jestem przyzwyczajony do tego z SVN, ale wydaje mi się, że właśnie wymieniłem jeden sub-par system wersjonowania na inny.

Mój partner mówi mi, że moje problemy wynikają z chęci połączenia się nie chcąc, i że w wielu sytuacjach powinienem używać bazy zamiast scalania. Oto przykładowy schemat pracy:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

Zasadniczo utwórz gałąź funkcji, ZAWSZE dokonuj zmiany bazy od głównej do gałęzi i łącz ją z gałęzi z powrotem do głównej. Należy pamiętać, że oddział zawsze pozostaje lokalny.

Oto przepływ pracy, z którym zacząłem

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

Są dwie zasadnicze różnice (tak myślę): zawsze używam scalania zamiast zmiany bazy i wypycham swoją gałąź funkcji (a moja gałąź funkcji zatwierdza) do zdalnego repozytorium.

Moje rozumowanie dla zdalnej gałęzi jest takie, że chcę, aby moja praca była tworzona podczas tworzenia kopii zapasowej. Kopie zapasowe naszego repozytorium są automatycznie tworzone i można je przywrócić, jeśli coś pójdzie nie tak. Mój laptop nie jest lub nie jest tak dokładnie. Dlatego nienawidzę mieć kodu na moim laptopie, który nie jest dublowany gdzie indziej.

Moje rozumowanie dla scalania zamiast rebase jest takie, że scalanie wydaje się standardowe, a rebase wydaje się być funkcją zaawansowaną. Mam przeczucie, że to, co próbuję zrobić, nie jest zaawansowaną konfiguracją, więc zmiana bazy powinna być niepotrzebna. Przeczytałem nawet nową książkę Pragmatic Programming na Git, która obszernie omawia scalanie i prawie nie wspomina o rebase.

W każdym razie śledziłem przebieg pracy w niedawnym oddziale, a kiedy próbowałem połączyć go z powrotem w master, wszystko poszło do piekła. Było mnóstwo konfliktów z rzeczami, które nie powinny mieć znaczenia. Konflikty nie miały dla mnie sensu. Zajęło mi dzień, aby wszystko załatwić, a ostatecznie kulminacją było zmuszenie do zdalnego mistrza, ponieważ mój lokalny mistrz rozwiązał wszystkie konflikty, ale zdalny nadal nie był szczęśliwy.

Jaki jest „prawidłowy” przepływ pracy dla czegoś takiego? Git ma sprawić, że rozgałęzianie i łączenie będzie super łatwe, a ja po prostu tego nie widzę.

Aktualizacja 2011-04-15

To wydaje się być bardzo popularnym pytaniem, więc pomyślałem, że zaktualizuję swoje dwuletnie doświadczenie, odkąd po raz pierwszy zadałem.

Okazuje się, że oryginalny przepływ pracy jest poprawny, przynajmniej w naszym przypadku. Innymi słowy, robimy to i działa:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

W rzeczywistości nasz przepływ pracy jest nieco inny, ponieważ mamy tendencję do wykonywania połączeń squashowych zamiast połączeń surowych. ( Uwaga: jest to kontrowersyjne, patrz poniżej. ) To pozwala nam przekształcić całą gałąź funkcji w pojedyncze zatwierdzenie na master. Następnie usuwamy naszą gałąź funkcji. To pozwala nam logicznie konstruować nasze commity na master, nawet jeśli są trochę niechlujne na naszych oddziałach. Oto co robimy:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

Kontrowersje związane ze scalaniem squash - jak zauważyło kilku komentatorów, scalanie squash wyrzuci całą historię w gałęzi funkcji. Jak sama nazwa wskazuje, zgniata wszystkie zatwierdzenia w jeden. W przypadku małych funkcji ma to sens, ponieważ skraca je w jednym pakiecie. W przypadku większych funkcji prawdopodobnie nie jest to świetny pomysł, zwłaszcza jeśli twoje indywidualne zatwierdzenia są już atomowe. To naprawdę sprowadza się do osobistych preferencji.

Żądania ściągania Github i Bitbucket (inni?) - jeśli zastanawiasz się, w jaki sposób scalanie / rebase odnosi się do żądań ściągania , zalecam wykonanie wszystkich powyższych kroków, dopóki nie będziesz gotowy, aby ponownie połączyć się z mistrzem. Zamiast ręcznie łączyć się z gitem, akceptujesz PR. Zauważ, że nie spowoduje to scalenia squasha (przynajmniej domyślnie), ale non-squash, non-fast forward jest przyjętą konwencją scalania w społeczności Pull Request (o ile mi wiadomo). W szczególności działa to tak:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

Pokochałem Gita i nigdy nie chcę wracać do SVN. Jeśli walczysz, po prostu trzymaj się go, a ostatecznie zobaczysz światło na końcu tunelu.


31
Niestety, nowa książka o Pragmstic Programming jest napisana głównie z używania Gita, gdy nadal myśli się w SVN, i w tym przypadku wprowadziła cię w błąd. W Git rebase sprawia, że ​​wszystko jest proste, kiedy tylko mogą. Twoje doświadczenie może ci powiedzieć, że Twój przepływ pracy nie działa w Git, nie że Git nie działa.
Paul

18
W tym przypadku nie polecałbym scalania squasha, ponieważ nie zapisuje on żadnych informacji o tym, co jest scalone (podobnie jak svn, ale tutaj nie ma mergeinfo).
Marius K

7
Uwielbiam notatkę na dole, miałem podobne doświadczenie w walce z Gitem, ale teraz trudno mi sobie wyobrazić, że go nie używam. Dziękuję również za ostateczne wyjaśnienie, bardzo pomogło w rebasezrozumieniu
Jon Phenow

6
Czy po zakończeniu tej funkcji nie powinieneś bazować ponownie po raz ostatni przed scaleniem nowej_funkcji w celu opanowania?
softarn

17
Twój przepływ pracy traci całą historię zatwierdzeń z usuniętego oddziału :(
Max Nanasy,

Odpowiedzi:


371

„Konflikty” oznaczają „równoległe ewolucje tej samej treści”. Więc jeśli pójdzie „do piekła” podczas scalania, oznacza to, że masz ogromne zmiany w tym samym zestawie plików.

Powodem, dla którego rebase jest lepszy niż scalanie, jest to, że:

  • przepisujesz swoją lokalną historię zatwierdzeń za pomocą głównego (a następnie ponownie zastosujesz swoją pracę, rozwiązując wszelkie konflikty)
  • ostateczne scalenie z pewnością będzie „szybkie przewijanie do przodu”, ponieważ będzie miało całą historię zatwierdzeń nadrzędnych, a także tylko zmiany do ponownego zastosowania.

Potwierdzam, że poprawny przepływ pracy w tym przypadku (ewolucja wspólnego zestawu plików) najpierw opiera się na bazie, a następnie łączy .

Oznacza to jednak, że jeśli wypchniesz oddział lokalny (ze względu na kopię zapasową), gałąź ta nie powinna być pobierana (ani przynajmniej używana) przez nikogo innego (ponieważ historia zatwierdzeń zostanie przepisana przez kolejne zmiany bazy).


Na ten temat (rebase, a następnie scalanie), barraponto wspomina w komentarzach dwa interesujące posty, oba z randyfay.com :

Korzystając z tej techniki, twoja praca zawsze trafia na oddział publiczny jak łatka, która jest na bieżąco z aktualnymi HEAD.

(podobna technika istnieje dla bazaru )


27
Aby zapoznać się z techniką, która umożliwia zmianę bazy danych i udostępnianie, zobacz softwareswirl.blogspot.com/2009/04/…
mhagger

2
randyfay.com/node/91 i randyfay.com/node/89 to wspaniałe lektury . artykuły te pozwoliły mi zrozumieć, co było nie tak z moim przepływem pracy i jaki byłby idealny przepływ pracy.
Capi Etheriel,

po prostu, prosto: przejście z gałęzi master na lokalną oznacza aktualizację historii, którą lokalny mógł przegapić, o której kapitan wie po jakimkolwiek fuzji?
hellatan

@dtan to, co tu opisuję, polega na lokowaniu na szczycie mistrza. Nie do końca aktualizujesz historię lokalną, ale raczej ponownie stosujesz historię lokalną nad masterem, aby rozwiązać wszelkie konflikty w lokalnym oddziale.
VCC

385

TL; DR

Przepływ pracy git rebase nie chroni cię przed ludźmi, którzy źle radzą sobie z rozwiązywaniem konfliktów lub osobami przyzwyczajonymi do przepływu pracy SVN, jak zasugerowano w Unikanie katastrof Git: krwawa historia . Sprawia to, że rozwiązywanie konfliktów jest dla nich bardziej nużące i trudniejsze do odzyskania po złym rozwiązaniu konfliktu. Zamiast tego użyj diff3, aby nie było tak trudne.


Przepływ pracy Rebase nie jest lepszy do rozwiązywania konfliktów!

Jestem bardzo pro-rebase do czyszczenia historii. Jeśli jednak kiedykolwiek dojdę do konfliktu, natychmiast przerywam rebase i zamiast tego dokonuję scalenia! Naprawdę mnie to zabija, że ​​ludzie zalecają przepływ pracy w bazie danych jako lepszą alternatywę dla przepływu pracy łączenia w celu rozwiązania konfliktu (dokładnie o to chodziło w tym pytaniu).

Jeśli pójdzie „wszystko do piekła” podczas scalania, pójdzie „wszystko do piekła” podczas ponownego uruchomienia i potencjalnie o wiele więcej piekła! Dlatego:

Powód 1: Rozwiązywanie konfliktów raz, zamiast raz dla każdego zatwierdzenia

Kiedy dokonujesz zmiany bazy zamiast scalania, będziesz musiał wykonać rozwiązywanie konfliktów maksymalnie tyle razy, ile będziesz musiał zobowiązać się do zmiany bazy, dla tego samego konfliktu!

Prawdziwy scenariusz

Odgałęziam kapitana, aby refaktoryzować skomplikowaną metodę w oddziale. Moja praca refaktoryzacyjna składa się z 15 zatwierdzeń łącznie, gdy pracuję nad refaktoryzacją i przeglądaniem kodu. Część mojego refaktoryzacji polega na naprawianiu mieszanych zakładek i spacji, które były wcześniej w master. Jest to konieczne, ale niestety spowoduje konflikt z każdą zmianą wprowadzoną później do tej metody w trybie master. Rzeczywiście, kiedy pracuję nad tą metodą, ktoś dokonuje prostej, legalnej zmiany tej samej metody w gałęzi master, którą należy połączyć z moimi zmianami.

Kiedy przychodzi czas na połączenie mojej gałęzi z master, mam dwie opcje:

git merge: Mam konflikt. Widzę zmianę, którą wprowadzili, aby opanować i połączyć ją z (końcowym produktem) mojej gałęzi. Gotowy.

git rebase: Mam konflikt z pierwszym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z drugim zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z trzecim zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim czwartym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim piątym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim szóstym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z siódmympopełnić. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim ósmym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z dziewiątym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim dziesiątym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim jedenastym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim dwunastym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim trzynastym popełnieniem. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moją czternastkąpopełnić. Rozwiązuję konflikt i kontynuuję rebase. Mam konflikt z moim piętnastym zatwierdzeniem. Rozwiązuję konflikt i kontynuuję rebase.

Żartujesz sobie ze mnie, jeśli to twój ulubiony przepływ pracy. Wystarczy tylko wstawić spację, która powoduje konflikt z jedną zmianą dokonaną na wzorcu, a każde zatwierdzenie spowoduje konflikt i musi zostać rozwiązane. Jest to prosty scenariusz z konfliktem tylko białych znaków. Niebiosa zabraniają ci prawdziwego konfliktu związanego z poważnymi zmianami kodu w plikach i musisz rozwiązać to wiele razy.

Przy wszystkich dodatkowych rozwiązywaniu konfliktów, które musisz zrobić, zwiększa to tylko możliwość popełnienia błędu . Ale błędy są w porządku, ponieważ można cofnąć, prawda? Z wyjątkiem oczywiście ...

Powód # 2: Z rebase nie można cofnąć!

Myślę, że wszyscy możemy się zgodzić, że rozwiązanie konfliktu może być trudne, a także że niektórzy ludzie są w tym bardzo źli. Może być bardzo podatny na błędy, dlatego jest tak świetny, że git ułatwia cofanie!

Podczas scalania gałęzi git tworzy zatwierdzenie scalania, które można odrzucić lub zmienić, jeśli rozwiązanie konfliktu pójdzie źle. Nawet jeśli już wysłałeś niepoprawne zatwierdzenie scalania do publicznego / autorytatywnego repozytorium, możesz użyć go git revertdo cofnięcia zmian wprowadzonych przez scalanie i powtórzenia scalenia poprawnie w nowym zatwierdzeniu scalania.

Kiedy zmieniasz gałąź, w prawdopodobnym przypadku, gdy rozwiązanie konfliktu zostanie wykonane nieprawidłowo, jesteś zepsuty. Każde zatwierdzenie zawiera teraz błędne scalenie i nie można po prostu powtórzyć rebase *. W najlepszym wypadku musisz cofnąć się i zmienić każde z zatwierdzonych zobowiązań. Nie śmieszne.

Po zmianie bazy niemożliwe jest ustalenie, co pierwotnie stanowiło część zatwierdzeń, a co zostało wprowadzone w wyniku złego rozwiązania konfliktu.

* Można cofnąć rebase, jeśli możesz wykopać stare referencje z wewnętrznych dzienników git lub jeśli utworzysz trzecią gałąź, która wskazuje na ostatnie zatwierdzenie przed zmianą bazy.

Wyjdź do diabła z rozwiązywania konfliktów: użyj diff3

Weźmy na przykład ten konflikt:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Patrząc na konflikt, nie można powiedzieć, co zmieniła każda gałąź ani jakie było jej zamierzenie. Jest to moim zdaniem największy powód, dla którego rozwiązywanie konfliktów jest mylące i trudne.

diff3 na ratunek!

git config --global merge.conflictstyle diff3

Kiedy użyjesz diff3, każdy nowy konflikt będzie miał trzecią sekcję, połączonego wspólnego przodka.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Najpierw sprawdź połączonego wspólnego przodka. Następnie porównaj każdą stronę, aby określić zamiar każdej gałęzi. Możesz zobaczyć, że HEAD zmienił EmailMessage na TextMessage. Jego celem jest zmiana klasy używanej na TextMessage, przekazując te same parametry. Możesz również zobaczyć, że intencją gałęzi funkcji jest przekazanie wartości false zamiast wartości true dla opcji: include_timestamp. Aby scalić te zmiany, połącz zamiar obu:

TextMessage.send(:include_timestamp => false)

Ogólnie:

  1. Porównaj wspólnego przodka z każdą gałęzią i ustal, która gałąź ma najprostszą zmianę
  2. Zastosuj tę prostą zmianę do wersji kodu drugiej gałęzi, aby zawierała zarówno prostszą, jak i bardziej złożoną zmianę
  3. Usuń wszystkie sekcje kodu konfliktu inne niż ten, w którym właśnie scaliłeś zmiany

Alternatywnie: rozwiązuj ręcznie, stosując zmiany oddziału

Wreszcie, niektóre konflikty są trudne do zrozumienia, nawet z diff3. Dzieje się tak zwłaszcza wtedy, gdy diff znajduje wspólne linie, które nie są semantycznie wspólne (np. Obie gałęzie miały pustą linię w tym samym miejscu!). Na przykład jedna gałąź zmienia wcięcie ciała klasy lub zmienia kolejność podobnych metod. W takich przypadkach lepszą strategią rozdzielczości może być zbadanie zmiany z dowolnej strony scalania i ręczne zastosowanie różnicy do drugiego pliku.

Spójrzmy, jak możemy rozwiązać konflikt w scenariuszu, w którym łączymy się tam, origin/feature1gdzie występują lib/message.rbkonflikty.

  1. Zdecyduj, czy nasza aktualnie wyewidencjonowana gałąź ( HEADlub --ours), czy gałąź, którą łączymy ( origin/feature1, lub --theirs), jest prostszą zmianą do zastosowania. Użycie diff z potrójną kropką ( git diff a...b) pokazuje zmiany, które nastąpiły od bczasu jego ostatniej rozbieżności alub innymi słowy, porównuje wspólnego przodka aib z b.

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. Sprawdź bardziej skomplikowaną wersję pliku. Spowoduje to usunięcie wszystkich znaczników konfliktu i skorzystanie z wybranej strony.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
    
  3. Po sprawdzeniu skomplikowanej zmiany, wyciągnij różnicę prostszej zmiany (patrz krok 1). Zastosuj każdą zmianę z tego pliku różnicowego do pliku powodującego konflikt.


4
W jaki sposób scalenie wszystkich konfliktów za jednym razem działałoby lepiej niż indywidualne zatwierdzenia? Już mam problemy ze scalaniem pojedynczych zatwierdzeń (szczególnie od ludzi, którzy nie dzielą zatwierdzeń na logiczne części ORAZ zapewniają wystarczające testy do weryfikacji). Również rebase nie jest gorsze niż scalanie, jeśli chodzi o opcje tworzenia kopii zapasowych, inteligentne użycie interaktywnego rebase i narzędzi, takich jak tortoisegit (który pozwala wybrać, które zobowiązania należy uwzględnić) bardzo pomogą.
prusswan

8
Wydaje mi się, że podałem przyczynę w punkcie 1. Jeśli poszczególne zatwierdzenia nie są logicznie spójne, tym bardziej powód do połączenia logicznie spójnej gałęzi, aby można było naprawdę zrozumieć konflikt. Jeśli zatwierdzenie 1 jest błędne, a zatwierdzenie 2 naprawia to, scalenie zatwierdzenia 1 będzie mylące. Istnieją uzasadnione powody, dla których możesz dostać 15 konfliktów z rzędu, takich jak ten, który przedstawiłem powyżej. Również twój argument za tym, że rebase nie jest gorszy, jest nieco bezzasadny. Rebase łączy złe połączenia w oryginalne dobre zatwierdzenia i nie pozostawia dobrych zatwierdzeń w pobliżu, abyś mógł spróbować ponownie. Scalanie robi.
Edward Anderson

6
Całkowicie się z tobą zgadzam, nilbus. Wspaniały post; to usuwa niektóre rzeczy. Zastanawiam się, czy rerere przydałaby się tutaj. Ponadto, dzięki za sugestię użycia diff3, na pewno zamierzam teraz włączyć tę.
derick

45
+1 za mówienie o samym diff3 - jak często patrzył na niezrozumiały konflikt przeklinający, kto jest odpowiedzialny za to, że nie powiedział mi, co miał do powiedzenia wspólny przodek. Dziękuję Ci bardzo.
Jan

4
To powinna być zaakceptowana odpowiedź. Przepływ pracy w bazie danych jest również okropny, ponieważ ukrywa fakt, że w pewnym momencie w bazie danych istniała ogromna rozbieżność, co może być przydatne, jeśli chcesz zrozumieć, w jaki sposób napisany jest kod, który oglądasz. Tylko małe gałęzie, które nie powodują konfliktów, powinny zostać przeniesione na master.
Robert Rüger

32

W moim przepływie pracy bazuję na podstawach w jak największym stopniu (i staram się to robić często. Nie dopuszczenie do nagromadzenia się rozbieżności drastycznie zmniejsza ilość i nasilenie kolizji między oddziałami).

Jednak nawet w przepływie pracy opartym głównie na rebase istnieje miejsce na scalanie.

Przypomnijmy, że scalenie faktycznie tworzy węzeł, który ma dwoje rodziców. Rozważmy teraz następującą sytuację: Mam dwa niezależne branże funkcji A i B, a teraz chcę opracować rzeczy w gałęzi funkcji C, które zależą zarówno od A, jak i B, podczas gdy A i B są sprawdzane.

To, co robię, to:

  1. Utwórz (i kasy) gałąź C na górze A.
  2. Połącz to z B.

Teraz gałąź C zawiera zmiany zarówno z A, jak i B, i mogę nadal nad nią rozwijać. Jeśli dokonam zmiany w A, rekonstruuję wykres rozgałęzień w następujący sposób:

  1. utwórz gałąź T na nowym szczycie A
  2. połączyć T z B.
  3. bazować C na T.
  4. usuń gałąź T

W ten sposób mogę właściwie utrzymywać dowolne wykresy rozgałęzień, ale robienie czegoś bardziej złożonego niż opisana powyżej sytuacja jest już zbyt skomplikowane, biorąc pod uwagę, że nie ma automatycznego narzędzia do zmiany bazowania po zmianie rodzica.


1
Możesz osiągnąć to samo za pomocą tylko rebases. Scalanie w rzeczywistości nie jest tutaj konieczne (z wyjątkiem sytuacji, gdy nie chcesz powielać zatwierdzeń - ale nie uważam tego za argument).
odwl

1
Rzeczywiście nie chcę powielać zatwierdzeń. Chciałbym zachować jak najlepszą strukturę mojej pracy podczas lotu. Ale to kwestia osobistego gustu i niekoniecznie jest odpowiednie dla wszystkich.
Alex Gontmakher

W 100% zgadzam się z akapitem pierwszym. (Odpowiedź @ Edwarda działa tam, gdzie tak nie jest, ale wolałbym, żeby wszystkie projekty na świecie działały tak, jak sugerujesz). Reszta odpowiedzi wydaje się nieco przesadna w tym sensie, że praca nad C, gdy A i B są w toku, jest już dość ryzykowna (przynajmniej w takim stopniu, w jakim tak naprawdę zależy od A i B), a nawet w końcu prawdopodobnie nie zachowałby połączeń (C zostałby ponownie oparty na najnowszym i największym).
Alois Mahdal,

22

NIE używaj git push origin --mirror POD PRAWIE ŻADNĄ OKOLICZNOŚCIĄ.

Nie pyta, czy jesteś pewien, że chcesz to zrobić, i lepiej bądź pewien, ponieważ spowoduje to usunięcie wszystkich zdalnych gałęzi, które nie znajdują się w lokalnej skrzynce.

http://twitter.com/dysinger/status/1273652486


6
Czy nie robisz rzeczy, których nie jesteś pewien, jaki będzie wynik? Maszyna, którą administrowałem, miała Instructions to this machine may lead to unintended consequences, loss of work/data, or even death (at the hands of the sysad). Remember that you are solely responsible for the consequences of your actions w MOTD.
richo

użyj go, jeśli masz dublowane repozytorium (chociaż w moim przypadku jest ono teraz wykonywane przez specjalnego użytkownika w repozytorium źródłowym po przechwyceniu po odebraniu)
prusswan

14

Po przeczytaniu twojego wyjaśnienia mam jedno pytanie: czy to możliwe, że nigdy tego nie zrobiłeś

git checkout master
git pull origin
git checkout my_new_feature

przed wykonaniem „git rebase / merge master” w gałęzi funkcji?

Ponieważ twoja główna gałąź nie aktualizuje się automatycznie z repozytorium twojego znajomego. Musisz to zrobić za pomocą git pull origin. Tj. Może zawsze bazowałbyś na niezmiennym lokalnym oddziale głównym? A potem nadchodzi czas wypychania, wpychasz do repozytorium, które ma (lokalne) zobowiązania, których nigdy nie widziałeś, a zatem wypychanie kończy się niepowodzeniem.


13

W twojej sytuacji myślę, że twój partner ma rację. Zaletą zmiany bazy jest to, że dla osoby postronnej zmiany wyglądają tak, jakby same przebiegały w czystej kolejności. To znaczy

  • Twoje zmiany są bardzo łatwe do przejrzenia
  • możesz nadal tworzyć ładne, małe zatwierdzenia, a jednocześnie możesz ustawić ich zestawienia publicznie (łącząc się w master) naraz
  • kiedy spojrzysz na główny oddział publiczny, zobaczysz różną serię zatwierdzeń dla różnych funkcji przez różnych programistów, ale nie wszystkie zostaną zmieszane

Nadal możesz nadal wypychać swój prywatny oddział programistyczny do zdalnego repozytorium ze względu na tworzenie kopii zapasowych, ale inni nie powinni traktować tego jako gałęzi „publicznej”, ponieważ będziesz się opierać. BTW, łatwym poleceniem do tego jest git push --mirror origin.

Artykuł Oprogramowanie do pakowania korzystające z Git wykonuje całkiem niezłą robotę, wyjaśniając kompromisy w łączeniu i przekształcaniu. To trochę inny kontekst, ale zasady są takie same - w zasadzie sprowadza się to do tego, czy twoje gałęzie są publiczne, czy prywatne i jak planujesz zintegrować je z linią główną.


1
Link do oprogramowania do pakowania używającego git już nie działa. Nie mogłem znaleźć dobrego linku do edycji oryginalnej odpowiedzi.
Chetan

Nie powinieneś tworzyć originkopii lustrzanej , powinieneś wykonać kopię lustrzaną do trzeciego repozytorium kopii zapasowych.
Miral

12

W każdym razie śledziłem przebieg pracy w niedawnym oddziale, a kiedy próbowałem połączyć go z powrotem w master, wszystko poszło do piekła. Było mnóstwo konfliktów z rzeczami, które nie powinny mieć znaczenia. Konflikty nie miały dla mnie sensu. Zajęło mi dzień, aby wszystko załatwić, a ostatecznie kulminacją było zmuszenie do zdalnego mistrza, ponieważ mój lokalny mistrz rozwiązał wszystkie konflikty, ale zdalny nadal nie był szczęśliwy.

Ani w partnerach, ani w sugerowanych przepływach pracy nie powinieneś napotkać konfliktów, które nie miały sensu. Nawet jeśli tak, jeśli przestrzegasz sugerowanych przepływów pracy, po rozwiązaniu nie powinno być wymagane „wymuszone” wypychanie. Sugeruje to, że tak naprawdę nie scaliłeś gałęzi, do której naciskałeś, ale musiałeś pchnąć gałąź, która nie była potomkiem odległej końcówki.

Myślę, że musisz dokładnie przyjrzeć się temu, co się stało. Czy ktoś inny mógł (celowo lub nie) przewinąć zdalną gałąź główną między utworzeniem gałęzi lokalnej a punktem, w którym próbowałeś połączyć ją z powrotem z gałęzią lokalną?

W porównaniu do wielu innych systemów kontroli wersji odkryłem, że używanie Git wymaga mniejszej walki z narzędziem i pozwala zająć się problemami, które są fundamentalne dla twoich strumieni źródłowych. Git nie wykonuje magii, więc konfliktowe zmiany powodują konflikty, ale powinno ułatwić pisanie, śledząc pochodzenie popełnienia.


Sugerujesz, że OP ma jakiś nieodkryty zwrot lub błąd w swoim procesie, prawda?
krosenvold

7

Z tego, co zaobserwowałem, git merge ma tendencję do oddzielania gałęzi nawet po scaleniu, podczas gdy rebase następnie scalanie łączy je w jedną gałąź. Ten drugi wychodzi o wiele czystszy, podczas gdy w pierwszym łatwiej byłoby dowiedzieć się, które zatwierdzenia należą do której gałęzi, nawet po połączeniu.


7

„Nawet jeśli jesteś pojedynczym programistą z zaledwie kilkoma gałęziami, warto przyzwyczaić się do prawidłowego korzystania z bazy i łączenia. Podstawowy schemat pracy będzie wyglądał następująco:

  • Utwórz nową gałąź B z istniejącej gałęzi A

  • Dodaj / zatwierdz zmiany w oddziale B

  • Wycofaj aktualizacje z oddziału A

  • Scal zmiany z gałęzi B do gałęzi A ”

https://www.atlassian.com/git/tutorials/merging-vs-rebasing/


3

W Git nie ma „poprawnego” przepływu pracy. Używaj czegokolwiek, co płynie łodzią. Jeśli jednak ciągle pojawiają się konflikty podczas łączenia oddziałów, może powinieneś lepiej koordynować swoje wysiłki z innymi programistami? Wygląda na to, że oboje edytujecie te same pliki. Uważaj również na słowa kluczowe spacje i subversion (np. „$ Id $” i inne).


0

Używam tylko przepływu pracy rebase, ponieważ jest on wizualnie wyraźniejszy (nie tylko w GitKraken, ale także w Intellij i in gitk, ale najbardziej polecam ten pierwszy): masz gałąź, pochodzi ona od mistrza i wraca do mistrza . Kiedy schemat będzie czysty i piękny, będziesz wiedział, że nic nigdy nie pójdzie do piekła .

wprowadź opis zdjęcia tutaj

Mój obieg pracy jest prawie taki sam jak w twoim, ale z jedną tylko niewielką różnicą: squashzobowiązuję się do jednego w moim oddziale lokalnym, zanim rebasemój oddział wprowadzi najnowsze zmiany master, ponieważ:

rebasedziała na podstawie każdego zatwierdzenia

co oznacza, że ​​jeśli masz 15 zatwierdzeń zmieniających tę samą linię co masterrobi, musisz sprawdzić 15 razy, jeśli nie zgniatasz, ale co ma znaczenie, to wynik końcowy, prawda?

Cały przepływ pracy to:

  1. Do kasy masteri pociągnij, aby upewnić się, że masz najnowszą wersję

  2. Stamtąd utwórz nowy oddział

  3. Wykonuj tam swoją pracę, możesz dowolnie popełniać kilka razy i pchać się na odległość, bez obaw, bo to twoja gałąź.

  4. Jeśli ktoś powie ci „hej, mój PR / MR został zatwierdzony, teraz jest scalony z master”, możesz je pobrać / wyciągnąć. Możesz to zrobić w dowolnym momencie lub w kroku 6.

  5. Po wykonaniu całej pracy zatwierdź je, a jeśli masz kilka zatwierdzeń, zmiażdż je (wszystkie są twoją pracą i ile razy zmieniasz wiersz kodu, nie ma znaczenia; jedyną ważną rzeczą jest wersja ostateczna). Naciskaj czy nie, to nie ma znaczenia.

  6. Zamówienie na master, pullponownie, aby upewnić się, że masz najnowszą masterw lokalnym. Twój schemat powinien być podobny do tego:

wprowadź opis zdjęcia tutaj

Jak widać, jesteś w lokalnym oddziale, który wywodzi się z przestarzałego statusu master, podczas gdy master(zarówno lokalny, jak i zdalny) posuwa się naprzód ze zmianami twojego kolegi.

  1. Do kasy wracaj do oddziału i dokonuj zmian w bazie. Będziesz miał teraz tylko jedno zatwierdzenie, więc konflikty rozwiążesz tylko raz . (A w GitKraken musisz tylko przeciągnąć gałąź masteri wybrać „Rebase”; kolejny powód, dla którego mi się podoba). Następnie będziesz lubić:

wprowadź opis zdjęcia tutaj

  1. Teraz masz wszystkie zmiany w najnowszym masterpołączeniu ze zmianami w oddziale. Możesz teraz naciskać na swojego pilota, a jeśli naciskałeś wcześniej, będziesz zmuszony naciskać; Git powie ci, że nie możesz po prostu szybko przewinąć do przodu. To normalne, z powodu zmiany bazy zmieniłeś punkt początkowy swojej gałęzi. Ale nie powinieneś się bać: mądrze używaj siły . W końcu pilot jest również twoją gałęzią, więc nie ma to wpływumaster nawet, jeśli zrobisz coś złego.

  2. Utwórz PR / MR i poczekaj, aż zostanie zatwierdzony, więc masterotrzymasz swój wkład. Gratulacje! Możesz teraz dokonać zakupu master, wyciągnąć zmiany i usunąć lokalny oddział, aby wyczyścić schemat. Zdalna gałąź również powinna zostać usunięta, jeśli nie zostanie to zrobione po scaleniu jej w master.

Ostateczny schemat jest znów przejrzysty:

wprowadź opis zdjęcia tutaj

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.