W jakich przypadkach „git pull” może być szkodliwy?


409

Mam kolegę, który tak twierdzi git pull jest szkodliwy i denerwuje się, gdy ktoś go używa.

git pullPolecenia wydaje się być kanoniczny sposób zaktualizować lokalne repozytorium. Czy używanie git pullstwarza problemy? Jakie to stwarza problemy? Czy istnieje lepszy sposób aktualizacji repozytorium git?



8
Możesz też git pull --rebaseustawić tę strategię jako domyślną dla nowych oddziałów git config branch.autosetuprebase
znopx

4
znaopx ma rację, dodając --rebaseflagę do git pullsynchronizacji lokalnej ze zdalną, a następnie odtwarza zmiany lokalne na podstawie zaktualizowanego lokalnego. Następnie, kiedy naciskasz, jedyne, co robisz, to dołączanie nowych zatwierdzeń na końcu pilota. Dość proste.
Heath Lilley,

4
Dzięki @BenMcCormick. Już to zrobiłem, ale wydaje się, że dyskusja na temat ważności pytania odbywa się w komentarzach pod pytaniem. I wydaje mi się, że zadawanie pytań w celu stworzenia platformy do przedstawienia swojej osobistej opinii jako faktu nie jest tak naprawdę po to, do czego służy struktura pytań i odpowiedzi SO.
mcv,

4
@RichardHansen, to po prostu sposób na oszukanie systemu punktowego, szczególnie gdy twoja odpowiedź ma tak drastyczną różnicę w tonie i tak krótką przerwę czasową. Korzystając z twojego modelu pytań i odpowiedzi, wszyscy moglibyśmy po prostu zadawać pytania i samodzielnie na nie odpowiadać, korzystając z naszych wcześniejszych wiedzy. W tym momencie powinieneś po prostu rozważyć napisanie posta na blogu, ponieważ jest to wielokrotnie bardziej odpowiednie. Pytania i odpowiedzi w szczególności poszukują wiedzy innych ludzi. Wpis na blogu zawiera własne.
Josh Brown

Odpowiedzi:


546

Podsumowanie

Domyślnie git pulltworzy zatwierdzenia scalania, które zwiększają szum i złożoność historii kodu. Ponadto pullułatwia nie myśleć o tym, w jaki sposób zmiany mogą wpływać na nadchodzące zmiany.

git pullKomenda jest bezpieczny tak długo, jak to tylko wykonuje scala fast-forward. Jeśli git pulljest skonfigurowany tylko do wykonywania połączeń szybkiego przewijania do przodu, a gdy scalanie szybkiego przewijania nie jest możliwe, Git zakończy działanie z błędem. To da ci możliwość przestudiowania nadchodzących zatwierdzeń, zastanowienia się, w jaki sposób mogą one wpłynąć na twoje lokalne zatwierdzenia, i wybrania najlepszego sposobu działania (scalenie, zmiana bazy, resetowanie itp.).

Dzięki Git 2.0 i nowszym możesz uruchomić:

git config --global pull.ff only

aby zmienić domyślne zachowanie na szybkie przewijanie do przodu. W wersjach Git od 1.6.6 do 1.9.x musisz przyzwyczaić się do pisania:

git pull --ff-only

Jednak w przypadku wszystkich wersji Git zalecam skonfigurowanie git up aliasu:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

i używanie git upzamiast git pull. Wolę ten alias niżgit pull --ff-only ponieważ:

  • działa ze wszystkimi (nie starożytnymi) wersjami Gita,
  • pobiera wszystkie odgałęzienia (nie tylko odgałęzienie, nad którym aktualnie pracujesz) i
  • oczyszcza stare origin/*gałęzie, które już nie istnieją pod prąd.

Problemy z git pull

git pullnie jest złe, jeśli jest właściwie używane. Kilka ostatnich zmian w Git ułatwiło git pullprawidłowe korzystanie , ale niestety domyślne zachowanie zwykłego git pullma kilka problemów:

  • wprowadza niepotrzebne nieliniowości w historii
  • ułatwia przypadkowe przywrócenie zatwierdzeń, które zostały celowo wyparto pod prąd
  • modyfikuje katalog roboczy w nieprzewidywalny sposób
  • wstrzymywanie tego, co robisz, aby przejrzeć czyjąś pracę, jest denerwujące git pull
  • utrudnia to prawidłowe ustawienie bazy na zdalnej gałęzi
  • nie usuwa gałęzi usuniętych w zdalnym repozytorium

Problemy te opisano bardziej szczegółowo poniżej.

Historia nieliniowa

Domyślnie git pullpolecenie jest równoważne z uruchomieniem, git fetchpo którym następuje git merge @{u}. Jeśli w lokalnym repozytorium znajdują się nieprzypisane zatwierdzenia, część scalania git pulltworzy zatwierdzenie scalania.

Nie ma nic złego w zatwierdzeniach scalania, ale mogą być niebezpieczne i powinny być traktowane z szacunkiem:

  • Zatwierdzenia scalania są z natury trudne do zbadania. Aby zrozumieć, co robi scalanie, musisz zrozumieć różnice między wszystkimi rodzicami. Konwencjonalny plik różnicowy nie oddaje dobrze tej wielowymiarowej informacji. Natomiast seria normalnych zatwierdzeń jest łatwa do przejrzenia.
  • Rozwiązywanie konfliktów scalania jest trudne, a błędy często pozostają niewykryte przez długi czas, ponieważ zatwierdzenia scalania są trudne do sprawdzenia.
  • Połączenia mogą po cichu zastąpić efekty zwykłych zatwierdzeń. Kod nie jest już sumą przyrostowych zatwierdzeń, co prowadzi do nieporozumień na temat tego, co faktycznie się zmieniło.
  • Scalanie zatwierdzeń może zakłócać niektóre schematy ciągłej integracji (np. Auto-kompilacja tylko ścieżki pierwszego rodzica zgodnie z przyjętą konwencją, że drugi rodzic wskazuje niekompletne prace w toku).

Oczywiście jest czas i miejsce na fuzje, ale zrozumienie, kiedy fuzje powinny i nie powinny być używane, może poprawić użyteczność repozytorium.

Zauważ, że celem Git jest ułatwienie dzielenia się i korzystania z ewolucji bazy kodu, a nie dokładne zapisywanie historii dokładnie w miarę jej rozwoju. (Jeśli się nie zgadzasz, rozważ rebasepolecenie i powód, dla którego zostało utworzone). Zatwierdzenia scalania utworzone przez git pullnie przekazują użytecznej semantyki innym użytkownikom - po prostu mówią, że ktoś inny przepchnął się do repozytorium, zanim skończysz wprowadzać zmiany. Dlaczego owe zatwierdzenia scalania są, jeśli nie mają znaczenia dla innych i mogą być niebezpieczne?

Możliwe jest skonfigurowanie git pullbazowania zamiast scalania, ale ma to również problemy (omówione później). Zamiast tego git pullpowinien być skonfigurowany tak, aby wykonywał tylko szybkie przewijanie do przodu.

Ponowne wprowadzenie przywróconych zobowiązań

Załóżmy, że ktoś zmienia gałąź i popycha ją siła. Zasadniczo nie powinno tak się zdarzyć, ale czasami jest konieczne (np. Aby usunąć plik dziennika 50GiB, który został przypadkowo zatwierdzony i wypchnięty). Scalenie wykonane przez git pullspowoduje scalenie nowej wersji gałęzi upstream ze starą wersją, która nadal istnieje w lokalnym repozytorium. Jeśli popchniesz wynik, widelce i pochodnie zaczną nadciągać.

Niektórzy mogą twierdzić, że prawdziwym problemem jest wymuszanie aktualizacji. Tak, generalnie wskazane jest unikanie wypychania siły, gdy tylko jest to możliwe, ale czasami są one nieuniknione. Programiści muszą być przygotowani na radzenie sobie z aktualizacjami wymuszonymi, ponieważ czasem będą się one zdarzały. Oznacza to, że nie ślepo łączy się stare zobowiązania za pomocą zwykłego git pull.

Zaskocz modyfikacje katalogu roboczego

Nie ma możliwości przewidzenia, jak będzie wyglądał katalog roboczy lub indeks, dopóki nie git pullzostanie wykonane. Mogą istnieć konflikty scalania, które musisz rozwiązać, zanim będziesz mógł zrobić cokolwiek innego, może wprowadzić plik dziennika 50GiB do katalogu roboczego, ponieważ ktoś przypadkowo go wypchnął, może zmienić nazwę katalogu, w którym pracujesz itp.

git remote update -p(lub git fetch --all -p) pozwala spojrzeć na zobowiązania innych osób, zanim zdecydujesz się na scalenie lub zmianę bazy, co pozwoli ci stworzyć plan przed podjęciem działań.

Trudność w sprawdzaniu zobowiązań innych osób

Załóżmy, że jesteś w trakcie wprowadzania zmian i ktoś inny chce, abyś sprawdził niektóre zatwierdzone przez siebie zmiany. git pullOperacja scalania (lub rebase) modyfikuje katalog roboczy i indeks, co oznacza, że ​​katalog roboczy i indeks muszą być czyste.

Możesz użyć, git stasha następnie git pull, ale co robisz, gdy skończysz sprawdzanie? Aby wrócić do miejsca, w którym byłeś, musisz cofnąć scalenie utworzone przez git pulli zastosować skrytkę.

git remote update -p(lub git fetch --all -p) nie modyfikuje katalogu roboczego lub indeksu, więc można go bezpiecznie uruchomić w dowolnym momencie - nawet jeśli wprowadziłeś zmiany etapowe i / lub niestabilne. Możesz wstrzymać to, co robisz i sprawdzić czyjeś zatwierdzenie, nie martwiąc się o ukrywanie lub dokończenie zatwierdzenia, nad którym pracujesz.git pullnie daje ci takiej elastyczności.

Powrót do zdalnego oddziału

Typowym wzorcem użycia Gita jest git pullwprowadzenie najnowszych zmian, a następnie git rebase @{u}wyeliminowanie wprowadzonego zatwierdzenia scalania git pull. To wspólne wystarczy, że Git ma kilka opcji konfiguracyjnych, aby zmniejszyć te dwa kroki do jednego kroku mówiąc git pullwykonać rebase zamiast seryjnej (patrz branch.<branch>.rebase, branch.autosetuprebasei pull.rebaseopcje).

Niestety, jeśli masz nieprzypisane zatwierdzenie scalania, które chcesz zachować (np. Zatwierdzenie scalające wypchniętą gałąź funkcji do master), ani rebase-pull ( git pullz branch.<branch>.rebaseustawionym na true), ani merge-pull ( git pullzachowanie domyślne ), po którym następuje rebase będzie działać. Jest tak, ponieważ git rebaseeliminuje scalenia (linearyzuje DAG) bez --preserve-mergesopcji. Operacji rebase-pull nie można skonfigurować w celu zachowania scalania, a połączenie-pull, po którym następuje git rebase -p @{u}, nie eliminuje scalenia spowodowanego przez pull-pull. Aktualizacja: Dodano Git v1.8.5 git pull --rebase=preservei git config pull.rebase preserve. Powodują git pullto git rebase --preserve-mergespo pobraniu zatwierdzeń poprzedzających. (Dzięki funkaster za heads-up!)

Czyszczenie usuniętych gałęzi

git pullnie przycina gałęzi zdalnego śledzenia odpowiadających gałęziom, które zostały usunięte ze zdalnego repozytorium. Na przykład, jeśli ktoś usunie gałąź fooze zdalnego repozytorium, nadal zobaczysz origin/foo.

Prowadzi to do przypadkowego wskrzeszenia zabitych gałęzi przez użytkowników, którzy uważają, że nadal są aktywni.

Lepsza alternatywa: użyj git upzamiastgit pull

Zamiast tego git pullzalecam utworzenie i używanie następującego git upaliasu:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

Ten alias pobiera wszystkie najnowsze zatwierdzenia ze wszystkich gałęzi upstream (przycinanie martwych gałęzi) i próbuje przewinąć gałąź lokalną do najnowszego zatwierdzenia w gałęzi upstream. Jeśli się powiedzie, nie będzie żadnych lokalnych zatwierdzeń, więc nie będzie ryzyka konfliktu scalania. Przewijanie do przodu nie powiedzie się, jeśli istnieją lokalne (niepodawane) zatwierdzenia, co daje możliwość przejrzenia zatwierdzeń przed podjęciem działania.

To wciąż modyfikuje katalog roboczy w nieprzewidywalny sposób, ale tylko wtedy, gdy nie masz żadnych lokalnych zmian. W przeciwieństwie do git pull, git upnigdy nie spowoduje wyświetlenia monitu oczekującego rozwiązania konfliktu scalania.

Inna opcja: git pull --ff-only --all -p

Poniższa wersja stanowi alternatywę dla powyższego git upaliasu:

git config --global alias.up 'pull --ff-only --all -p'

Ta wersja git upma takie samo zachowanie jak poprzedni git upalias, z wyjątkiem:

  • komunikat o błędzie jest nieco bardziej tajemniczy, jeśli lokalny oddział nie jest skonfigurowany z odgałęzieniem wyższym
  • opiera się na nieudokumentowanej funkcji ( -pargument przekazywany fetch), która może ulec zmianie w przyszłych wersjach Gita

Jeśli korzystasz z Git 2.0 lub nowszej wersji

W wersji Git 2.0 i nowszych możesz skonfigurować git pulldomyślną funkcję tylko szybkiego przewijania do przodu:

git config --global pull.ff only

To powoduje, git pullże tak się zachowuje git pull --ff-only, ale nadal nie pobiera wszystkich zatwierdzeń wcześniejszych ani nie usuwa starych origin/*gałęzi, więc nadal wolę git up.


6
@brianz: git remote update -pjest równoważne z git fetch --all -p. Mam w zwyczaju pisać, git remote update -pbo kiedyś fetchnie miałem takiej -pmożliwości. Jeśli chodzi o prowadzenie !, zobacz opis alias.*w git help config. Mówi: „Jeśli rozszerzenie aliasu jest poprzedzone wykrzyknikiem, będzie traktowane jak polecenie powłoki”.
Richard Hansen

13
Git 2.0 dodaje pull.ffkonfigurację, która wydaje się osiągać to samo, bez aliasów.
Danny Thomas

51
Niektóre z powodów brzmią jak „pull” może powodować problemy, kiedy inni robią szalone rzeczy ”. Nie, to szalone rzeczy, takie jak wycofanie zatwierdzenia z repozytorium poprzedzającego, które powoduje problemy. Baza IMO jest bezpieczna tylko wtedy, gdy robisz to lokalnie na zatwierdzeniu, które nie zostało jeszcze wypchnięte. Na przykład, kiedy wyciągasz przed wypchnięciem, zmiana lokalnych zatwierdzeń pomaga zachować twoją historię liniową (choć historia liniowa nie jest aż tak wielka). Mimo to git upbrzmi jak interesująca alternatywa.
mcv

16
Większość twoich punktów wynika z tego, że robisz coś złego: próbujesz przejrzeć kod we własnej działającej gałęzi . To nie jest dobry pomysł, po prostu utwórz nową gałąź, pociągnij --rebase = zachowaj, a następnie podrzuć tę gałąź (lub scal ją, jeśli chcesz).
funkaster

5
Punkt @ funkastera ma tutaj wiele sensu, zwłaszcza w odniesieniu do: „Trudności w sprawdzaniu cudzych zobowiązań”. To nie jest przegląd opinii, z którego korzysta większość użytkowników Gita, jest to coś, czego nigdzie nie widziałem, i jest to przyczyną wszystkich niepotrzebnych dodatkowych prac opisanych poniżej git pull.
Ben Regenspan,

195

Moja odpowiedź pochodzi z dyskusji, która się pojawiła na HackerNews:

Kusi mnie, aby po prostu odpowiedzieć na pytanie, stosując Prawo Betteridge z Nagłówków: Dlaczego jest git pulluważane za szkodliwe? To nie jest

  • Nieliniowości nie są z natury złe. Jeśli reprezentują faktyczną historię, są w porządku.
  • Przypadkowe przywrócenie popełnionych zobowiązań ponownie wcześniejszych etapach jest wynikiem niewłaściwego przepisywania historii na wcześniejszych etapach. Nie można przepisać historii, gdy historia jest replikowana podczas kilku repozytoriów.
  • Modyfikacja katalogu roboczego jest oczekiwanym rezultatem; dyskusyjnej użyteczności, mianowicie w obliczu zachowania hg / monotone / darcs / other_dvcs_predating_git, ale znowu nie jest wewnętrznie zły.
  • Wstrzymanie się od przejrzenia pracy innych osób jest potrzebne do scalenia i ponownie jest oczekiwanym zachowaniem podczas ściągania git. Jeśli nie chcesz scalać, powinieneś użyć git fetch. Ponownie, jest to cecha git w porównaniu z poprzednimi popularnymi programami dvcs, ale jest to oczekiwane zachowanie, które nie jest wewnętrznie złe.
  • Utrudnianie bazowania na odległej gałęzi jest dobre. Nie przepisuj historii, chyba że jest to absolutnie konieczne. Nie mogę za życia zrozumieć tego dążenia do (fałszywej) historii liniowej
  • Nie sprzątanie gałęzi jest dobre. Każde repozytorium wie, co chce utrzymać. Git nie ma pojęcia o relacjach pan-niewolnik.

13
Zgadzam się. Nie ma w tym nic z natury szkodliwego git pull. Może to jednak kolidować z niektórymi szkodliwymi praktykami, takimi jak chęć przepisania historii bardziej niż jest to absolutnie konieczne. Ale git jest elastyczny, więc jeśli chcesz go używać w inny sposób, zrób to. Ale to dlatego, że (cóż, @Richard Hansen) chcesz zrobić coś niezwykłego w git, a nie dlatego, że git pulljest szkodliwy.
mcv

28
Nie można zgodzić się więcej. Ludzie są za git rebasei uważają za git pullszkodliwe? Naprawdę?
Victor Moroz

10
Byłoby miło widzieć, jak ktoś tworzy wykres, którego moralnością jest twoja oś, i klasyfikuje polecenia git jako dobre, złe lub gdzieś pośrodku. Ten wykres różni się między programistami, choć wiele mówi o jednym z git.
Michael

5
Moim problemem git pullbez --rebaseopcji jest kierunek scalania, który tworzy. Kiedy spojrzysz na różnicę, wszystkie zmiany w tym scaleniu należą teraz do osoby, która pociągnęła, a nie do osoby, która dokonała zmian. Podoba mi się przepływ pracy, w którym scalanie jest zarezerwowane dla dwóch oddzielnych gałęzi (A -> B), więc zatwierdzenie scalania jest jasne, co zostało wprowadzone, a zmiana bazy jest zarezerwowana na aktualizację w tym samym oddziale (zdalne A -> lokalne A )
Craig Kochis,

4
Co zyskujesz, wiedząc, że ktoś wykonał pociąg zaledwie kilka sekund przed kimś innym lub na odwrót? Myślę, że to tylko hałas i zaciemnia naprawdę istotną historię. To nawet obniża wartość historii. Dobra historia powinna być a) czysta i b) faktycznie mieć ważną historię.
David Ongaro

26

Nie jest uważane za szkodliwe, jeśli używasz Git poprawnie. Widzę, jak wpływa to na ciebie negatywnie, biorąc pod uwagę twój przypadek użycia, ale możesz uniknąć problemów po prostu nie modyfikując wspólnej historii.


10
Aby rozwinąć tę git pullkwestię : jeśli każdy pracuje we własnym oddziale (co moim zdaniem jest właściwym sposobem korzystania z git), nie stanowi to żadnego problemu. Rozgałęzienie w git jest tanie.
AlexQueue

18

Zaakceptowana odpowiedź twierdzi

Operacji rebase-pull nie można skonfigurować w celu zachowania scalania

ale od wersji 1.8.5 Git , która postdatuje tę odpowiedź, możesz to zrobić

git pull --rebase=preserve

lub

git config --global pull.rebase preserve

lub

git config branch.<name>.rebase preserve

W docs powiedzieć

Kiedy preserve,również przekażemy --preserve-mergesdo „git rebase”, aby zatwierdzone lokalnie zatwierdzenia scalania nie zostały spłaszczone przez uruchomienie „git pull”.

Ta poprzednia dyskusja zawiera bardziej szczegółowe informacje i diagramy: git pull --rebase --preserve-scales . Wyjaśnia również, dlaczego git pull --rebase=preserveto nie to samo git pull --rebase --preserve-merges, co nie robi właściwej rzeczy.

Ta inna poprzednia dyskusja wyjaśnia, co faktycznie robi wariant rebase z zachowaniem-scalaniem i jak jest on o wiele bardziej złożony niż zwykły rebase: Co dokładnie robi „rebase - zachowaj-scala” gita (i dlaczego?)


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.