W jakich przypadkach `git pull " może być szkodliwy?

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

Polecenie git pull wydaje się być kanonicznym sposobem aktualizacji lokalnego repozytorium. Czy używanie git pull stwarza problemy? Jakie problemy stwarza? Czy istnieje lepszy sposób na aktualizację repozytorium git?

 393
Author: Max, 2013-03-10

4 answers

Podsumowanie

Domyślnie, git pull tworzy commity scalające, które dodają szum i złożoność do historii kodu. Ponadto pull ułatwia Nie zastanawianie się, w jaki sposób nadchodzące zmiany mogą wpłynąć na twoje zmiany.

Polecenie git pull jest bezpieczne tak długo, jak wykonuje tylko fast-forward merges. Jeśli git pull jest skonfigurowane tak, aby wykonywać fast-forward merges, a fast-forward merge nie jest możliwe, to Git zakończy działanie z błędem. To daje możliwość studiowania przychodzące commity, zastanów się, jak mogą one wpłynąć na Twoje lokalne commity i zdecyduj, jak najlepiej postąpić (merge, rebase, reset, itp.).

Z Git 2.0 i nowszymi, możesz uruchomić:

git config --global pull.ff only

Aby zmienić domyślne zachowanie na tylko przewijanie do przodu. Z wersjami Git od 1.6.6 do 1.9.x będziesz musiał nabrać nawyku pisania:

git pull --ff-only

Jednak, ze wszystkimi wersjami Gita, polecam skonfigurowanie git up aliasu w taki sposób:

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

I używając git up zamiast z dnia git pull. Wolę ten alias niż git pull --ff-only ponieważ:

    Działa ze wszystkimi (Nie-starożytnymi) wersjami Gita,]}
  • pobiera wszystkie gałęzie (nie tylko gałąź, nad którą aktualnie pracujesz) i
  • Czyści stare gałęzie, które już nie istnieją.

Problemy z git pull

git pull nie jest źle, jeśli jest używany prawidłowo. Kilka ostatnich zmian w Git ułatwiło prawidłowe użycie git pull, ale niestety domyślne zachowanie zwykłego git pull ma kilka problemów:

  • wprowadza niepotrzebne nieliniowości w historii
  • ułatwia to przypadkowe ponowne wprowadzenie commitów, które zostały celowo usunięte]}
  • modyfikuje Twój katalog roboczy w nieprzewidywalny sposób
  • pauzowanie tego, co robisz, aby przejrzeć czyjąś pracę, jest irytujące git pull
  • To sprawia, że trudno jest poprawnie przełączyć się na zdalną gałąź]}
  • nie czyści up gałęzie, które zostały usunięte w zdalnym repo

Problemy te zostały opisane bardziej szczegółowo poniżej.

Nieliniowa Historia

Domyślnie polecenie {[6] } jest równoważne z uruchomieniem git fetch, po którym następuje git merge @{u}. Jeśli w repozytorium lokalnym są nieużywane commity, część scalająca git pull tworzy commit scalający.

Nie ma nic złego w commitach merge, ale mogą one być niebezpieczne i powinny być traktowane z respect:

  • commity scalające są z natury trudne do zbadania. Aby zrozumieć, co robi połączenie, musisz zrozumieć różnice między wszystkimi rodzicami. Konwencjonalny diff nie przekazuje tej wielowymiarowej informacji dobrze. W przeciwieństwie do tego, seria normalnych commitów jest łatwa do przejrzenia.
  • rozwiązywanie konfliktów scalania jest trudne, a błędy często pozostają niezauważone przez długi czas, ponieważ commity scalania są trudne do przejrzenia.
  • połączenia mogą spokojnie zastąpić skutki regularnych commitów. Kod nie jest już sumą przyrostowych commitów, co prowadzi do nieporozumień co do tego, co faktycznie się zmieniło.
  • commity scalające mogą zakłócać niektóre schematy ciągłej integracji (np. automatycznie budują tylko ścieżkę pierwszego rodzica zgodnie z założoną konwencją, że drugi rodzic wskazuje na niekompletne prace w toku).

Oczywiście istnieje czas i miejsce na połączenia, ale zrozumienie, kiedy połączenia powinny, a nie powinny być używane, może poprawić przydatność twojego repozytorium.

Zauważ, że celem Git jest ułatwienie udostępniania i spożywania ewolucji bazy kodowej, a nie dokładne zapisywanie historii dokładnie tak, jak się rozwinęła. (Jeśli się nie zgadzasz, rozważ polecenie rebase i dlaczego zostało utworzone.) Commity scalające utworzone przez git pull nie przekazują użytecznej semantyki innym-po prostu mówią, że ktoś inny przypadkiem wypchnął się do repozytorium, zanim skończyłeś z Twoimi zmianami. Po co te commity scalania, jeśli nie są znaczące dla innych i może być niebezpieczne?

Możliwe jest skonfigurowanie git pull do rebase zamiast merge, ale to również ma problemy (omówione później). Zamiast tego, git pull powinno być skonfigurowane tak, aby wykonywać tylko fast-forward merges.

Ponowne wprowadzenie zmian do zmian

Przypuśćmy, że ktoś rebasuje gałąź i siła ją popycha. Zazwyczaj nie powinno to się zdarzyć, ale czasami jest to konieczne(np. usunięcie pliku dziennika 50GiB, który został przypadkowo dodany i wypchnięty). Merge done by git pull Scali nową wersję gałęzi upstream ze starą wersją, która nadal istnieje w Twoim lokalnym repozytorium. Jeśli naciśniesz wynik, widły i pochodnie zaczną przychodzić w Twoją stronę.

Niektórzy mogą twierdzić, że prawdziwym problemem są aktualizacje siły. Tak, ogólnie wskazane jest unikanie popychania siłą, gdy tylko jest to możliwe, ale czasami są one nieuniknione. Deweloperzy muszą być przygotowani do radzenia sobie z aktualizacjami wymuszonymi, ponieważ czasami będą się one zdarzać. Oznacza to brak ślepego scalania w starych commitach poprzez zwykłe git pull.

Niespodziewane Modyfikacje Katalogu Roboczego

Nie da się przewidzieć, jak będzie wyglądał katalog roboczy lub indeks do czasu zakończenia git pull. Mogą wystąpić konflikty scalania, które musisz rozwiązać, zanim będziesz mógł zrobić cokolwiek innego, może to wprowadzić plik dziennika 50GiB do twojego katalogu roboczego, ponieważ ktoś przypadkowo go popchnął, może zmienić nazwę katalogu, w którym pracujesz, itp.

git remote update -p (lub git fetch --all -p) pozwala na przeglądanie commitów innych osób przed podjęciem decyzji o scaleniu lub rebase, co pozwala na stworzenie planu przed podjęciem działania.

Trudności w przeglądaniu cudzych commitów

Załóżmy, że jesteś w trakcie wprowadzania pewnych zmian i ktoś inny chce, abyś przejrzał niektóre commity, które właśnie wprowadził. git pull operacja merge (lub rebase) modyfikuje katalog roboczy i indeks, co oznacza, że katalog roboczy i indeks muszą być czyste.

You could użyj git stash, a następnie git pull, ale co zrobić, gdy skończysz recenzować? Aby wrócić do miejsca, w którym byłeś, musisz cofnąć połączenie utworzone przez git pull i zastosować skrytkę.

git remote update -p (lub git fetch --all -p) nie modyfikuje katalogu roboczego ani indeksu, więc można go bezpiecznie uruchomić w dowolnym momencie-nawet jeśli zmiany zostały wprowadzone i / lub nie. Możesz wstrzymać to, co robisz i przejrzeć commit innej osoby, nie martwiąc się o przechowywanie lub kończenie commitu, nad którym pracujesz. git pull nie daje ta elastyczność.

[[121]} Rebasing na zdalną gałąź

Powszechnym wzorcem użycia Gita jest wykonanie git pull, aby wprowadzić najnowsze zmiany, a następnie git rebase @{u}, aby wyeliminować commit merge wprowadzony przez git pull. Jest na tyle powszechne, że Git ma kilka opcji konfiguracyjnych, aby zredukować te dwa kroki do jednego kroku, mówiąc git pull, aby wykonać rebase zamiast scalać (zobacz branch.<branch>.rebase, branch.autosetuprebase, i pull.rebase opcje).

Niestety, jeśli masz nieużywany commit merge nie będzie działać rebase-pull (git pull z branch.<branch>.rebase ustawionym na true) ani merge-pull (domyślne zachowanie git pull), po którym następuje rebase. Jest tak dlatego, że git rebase eliminuje merge (linearyzuje DAG) bez opcji --preserve-merges. Operacja rebase-pull nie może być skonfigurowana tak, aby zachowywała połączenia, a merge-pull, po którym następuje git rebase -p @{u}, nie wyeliminuje połączenia spowodowanego przez merge-pull. aktualizacja: Git v1.8. 5 dodał git pull --rebase=preserve i git config pull.rebase preserve. Powoduje to, że git pull wykonuje git rebase --preserve-merges Po pobraniu commitów. (Podziękowania dla funkaster dla heads-up!)

Czyszczenie Usuniętych Gałęzi

git pull nie usuwa zdalnych gałęzi śledzenia odpowiadających gałęziom usuniętym ze zdalnego repozytorium. Na przykład, jeśli ktoś usunie gałąź foo ze zdalnego repo, nadal zobaczysz origin/foo.

To prowadzi do przypadkowego wskrzeszenia zabitych gałęzie, bo myślą, że nadal są aktywne.

Lepsza alternatywa: Użyj git up zamiast git pull

Zamiast git pull, zalecam utworzenie i użycie następującego aliasugit up:

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

Ten alias pobiera wszystkie najnowsze commity ze wszystkich gałęzi (przycinanie martwych gałęzi) i próbuje przeskoczyć lokalną gałąź do najnowszego commita z gałęzi. Jeśli się powiedzie, wtedy nie było żadnych lokalnych commitów, więc nie było ryzyka połączenia konflikt. Przewijanie do przodu nie powiedzie się, jeśli istnieją lokalne (nieużywane) commity, co daje możliwość przejrzenia wcześniejszych commitów przed podjęciem działań.

To nadal modyfikuje Twój katalog roboczy w nieprzewidywalny sposób, ale tylko jeśli nie masz żadnych lokalnych zmian. W przeciwieństwie do git pull, git up Nigdy nie wyświetli monitu oczekującego, że naprawisz konflikt scalający.

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

Poniżej znajduje się alternatywa dla powyższego git up alias:

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

Ta wersja git up ma takie samo zachowanie jak poprzedni git up alias, z wyjątkiem:

  • komunikat o błędzie jest nieco bardziej tajemniczy, jeśli lokalna gałąź nie jest skonfigurowana z odgałęzieniem upstream
  • opiera się na nieudokumentowanej funkcji (argument -p, który jest przekazywany do fetch), która może ulec zmianie w przyszłych wersjach Git

Jeśli używasz Git 2.0 lub nowszego

Z Git 2.0 i nowszymi możesz skonfigurować git pull tylko do fast-forward merges domyślnie:

git config --global pull.ff only

Powoduje to, że git pull zachowuje się jak git pull --ff-only, ale nadal nie pobiera wszystkich commitów lub nie usuwa starych gałęzi origin/*, więc nadal wolę git up.

 530
Author: Richard Hansen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-07 15:04:20

Moja odpowiedź, wyciągnięta z dyskusji, która powstała Na HackerNews:

Czuję się kuszony, aby odpowiedzieć na pytanie, korzystając z lepszego prawa nagłówków: dlaczego git pull uważa się za szkodliwe? Nie jest.

  • Nieliniowość nie jest z natury zła. Jeśli reprezentują rzeczywistą historię, są ok.
  • przypadkowe ponowne wprowadzenie commitów rebased upstream jest wynikiem błędnego przepisania historii upstream. Nie można przepisać historii, gdy historia jest replikowane wzdłuż kilku repo.
  • modyfikacja katalogu roboczego jest oczekiwanym wynikiem; dyskusyjnej użyteczności, mianowicie w obliczu zachowania hg / monotone / darcs / other_dvcs_predating_git, ale znowu nie jest z natury zła.
  • pauzowanie w celu sprawdzenia pracy innych osób jest potrzebne do połączenia i ponownie jest oczekiwanym zachowaniem na git pull. Jeśli nie chcesz scalać, powinieneś użyć git fetch. Ponownie, jest to idiosynkrazja git w porównaniu z poprzednimi popularnymi DVC, ale jest oczekiwane zachowanie i nie jest z natury złe.
  • utrudnianie rebase przeciwko zdalnej gałęzi jest dobre. Nie przepisuj historii, chyba że absolutnie musisz. Nie mogę za życia zrozumieć tego dążenia do (fałszywej) liniowej historii
  • Nie sprzątanie gałęzi jest dobre. Każdy repo wie, co chce trzymać. Git nie ma pojęcia o związkach mistrz-niewolnik.
 194
Author: Sérgio Carvalho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-12 16:11:53

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

 26
Author: Hunt Burdick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-14 16:11:45

The accepted answer claims

Operacja rebase-pull nie może być skonfigurowana do zachowania połączeń

Ale od Git 1.8.5 , który postdates tej odpowiedzi, można zrobić

git pull --rebase=preserve

Lub

git config --global pull.rebase preserve

Lub

git config branch.<name>.rebase preserve

Thedocs say

Kiedy preserve, przechodzi również --preserve-merges do 'git rebase' tak, że lokalnie zatwierdzone zmiany scalające nie zostaną spłaszczone przez uruchomienie 'git pull'.

Ta poprzednia dyskusja ma więcej szczegółowe informacje i schematy: git pull --rebase --preserve-merges . Wyjaśnia również, dlaczego git pull --rebase=preserve nie jest tym samym co git pull --rebase --preserve-merges, co nie robi dobrze.

Ta inna poprzednia dyskusja wyjaśnia, co tak naprawdę robi wariant preserve-merges rebase i jak jest o wiele bardziej złożony niż zwykły rebase: co dokładnie robi "rebase --preserve-merges" Gita (i dlaczego?)

 17
Author: Marc Liyanage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:15