В каких случаях "w20> pull" может быть вредным?

У меня есть коллега, который утверждает, что git pull вреден, и расстраивается всякий раз, когда кто-то его использует.

Команда git pull представляется каноническим способом обновления локального репозитория. Использует ли git pull проблемы? Какие проблемы он создает? Есть ли лучший способ обновить репозиторий git?

384
задан Richard Hansen 10 марта '13 в 1:23
источник поделиться
4 ответов

Резюме

По умолчанию git pull создает комманды слияния, которые добавляют шум и сложность в историю кода. Кроме того, pull позволяет не думать о том, как изменения могут повлиять на входящие изменения.

Команда git pull безопасна до тех пор, пока она выполняет только быстрые слияния. Если git pull настроен только для слияния быстрой перемотки вперед, и слияние с быстрой перестройкой невозможно, то Git выйдет с ошибкой. Это даст вам возможность изучить входящие коммиты, подумать о том, как они могут повлиять на ваши локальные коммиты, и решить наилучший курс действий (слияние, rebase, reset и т.д.).

С Git 2.0 и новее вы можете запустить:

git config --global pull.ff only

чтобы изменить поведение по умолчанию только для быстрой перемотки вперед. С версиями Git между 1.6.6 и 1.9.x вам придется привыкнуть печатать:

git pull --ff-only

Однако, со всеми версиями Git, я рекомендую настроить псевдоним git up следующим образом:

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

и используя git up вместо git pull. Я предпочитаю этот псевдоним над git pull --ff-only, потому что:

  • он работает со всеми (не древними) версиями Git,
  • он извлекает все ветки upstream (а не только ветку, в которой вы сейчас работаете) и
  • он очищает старые ветки origin/*, которые больше не существуют вверх по течению.

Проблемы с git pull

git pull не плохо, если он используется правильно. Несколько недавних изменений в Git облегчили правильное использование git pull, но, к сожалению, поведение по умолчанию для обычного git pull по умолчанию имеет несколько проблем:

  • он вводит ненужные нелинейности в истории
  • это позволяет случайно повторно ввести коммиты, которые были намеренно переустановлены вверх по течению.
  • он изменяет ваш рабочий каталог непредсказуемыми способами.
  • приостанавливая то, что вы делаете, чтобы пересмотреть работу другого, раздражает git pull
  • это затрудняет правильную переадресацию на удаленную ветвь
  • он не очищает ветки, удаленные в удаленном репо

Эти проблемы описаны более подробно ниже.

Нелинейная история

По умолчанию команда git pull эквивалентна запуску git fetch, за которым следует git merge @{u}. Если в локальном репозитории есть разблокированные коммиты, часть слияния git pull создает фиксацию слияния.

В отношении слияний не существует ничего плохого, но они могут быть опасными и к ним следует относиться с уважением:

  • Слияния коммитов по своей сути трудно исследовать. Чтобы понять, что делает слияние, вы должны понимать различия со всеми родителями. Обычный diff не передает эту многомерную информацию. Напротив, ряд нормальных коммитов легко проверить.
  • Объединить разрешение конфликтов сложно, и ошибки часто остаются необнаруженными в течение длительного времени, потому что компромиссы слияния трудно пересмотреть.
  • Слияния могут спокойно вытеснять эффекты регулярных коммитов. Код больше не является суммой дополнительных ошибок, что приводит к недоразумениям относительно того, что фактически изменилось.
  • Объединяющие коммиты могут нарушить некоторые схемы непрерывной интеграции (например, автоматически строить только путь первого родителя в соответствии с предполагаемым соглашением, что второй родитель указывает на незавершенные незавершенные работы).

Конечно, есть время и место для слияний, но понимание того, когда слияния должны и не должны использоваться, может улучшить полезность вашего репозитория.

Обратите внимание, что цель Git заключается в том, чтобы упростить разделение и потребление эволюции кодовой базы, а не точно записывать историю точно так, как она была развернута. (Если вы не согласны, рассмотрите команду rebase и почему она была создана.) Завершение слияния, созданное с помощью git pull, не передает полезную семантику другим - они просто говорят, что кто-то еще случайно нажал на репозиторий, прежде чем вы закончите с вашими изменениями. Почему эти слияния совершаются, если они не имеют смысла для других и могут быть опасными?

Можно настроить git pull на rebase вместо merge, но это также имеет проблемы (обсуждается ниже). Вместо этого git pull должен быть настроен только для переходов с быстрой перемоткой вперед.

Реинтродукция завершенных обязательств

Предположим, что кто-то переустанавливает ветку, и сила толкает ее. Это обычно не должно происходить, но иногда необходимо (например, удалить файл журнала 50GiB, который случайно был запущен и нажат). Слияние, выполненное с помощью git pull, объединит новую версию ветки восходящего потока в старую версию, которая все еще существует в вашем локальном репозитории. Если вы нажмете результат, начнут появляться вилки и факелы.

Некоторые могут утверждать, что настоящая проблема - это силовые обновления. Да, обычно рекомендуется избегать принудительного толчка, когда это возможно, но иногда это неизбежно. Разработчики должны быть готовы реагировать на обновления, потому что они будут происходить иногда. Это означает не слепое слияние в старых коммитах с помощью обычного git pull.

Модификации рабочего стола с сюрпризом

Невозможно предсказать, как будет выглядеть рабочий каталог или индекс, пока не будет выполнено git pull. Могут возникнуть конфликты слияния, которые необходимо решить, прежде чем вы сможете сделать что-либо еще, он может ввести файл журнала 50GiB в вашем рабочем каталоге, потому что кто-то случайно его нажал, он может переименовать каталог, в котором вы работаете, и т.д.

git remote update -p (или git fetch --all -p) позволяет вам наблюдать за совершением других людей, прежде чем вы решите объединить или переустановить, что позволит вам составить план, прежде чем предпринимать действия.

Сложность Просмотр других людей Заблокированные

Предположим, вы находитесь в середине внесения некоторых изменений, и кто-то еще хочет, чтобы вы просмотрели некоторые коммиты, которые они просто нажали. Операция git pull merge (или rebase) изменяет рабочий каталог и индекс, что означает, что ваш рабочий каталог и индекс должны быть чистыми.

Вы можете использовать git stash, а затем git pull, но что вы будете делать, когда закончите просмотр? Чтобы вернуться к тому, где вы были, вам нужно отменить слияние, созданное с помощью git pull, и применить кэш.

git remote update -p (или git fetch --all -p) не изменяет рабочий каталог или индекс, поэтому он безопасен для запуска в любое время, даже если у вас есть поэтапные и/или неустановленные изменения. Вы можете приостановить то, что вы делаете, и проверить, что кто-то еще совершает, не беспокоясь о том, чтобы скрыть или завершить коммит, над которым вы работаете. git pull не дает вам такой гибкости.

Возврат в удаленную ветвь

Обычный шаблон использования Git - это сделать git pull, чтобы вносить последние изменения, за которыми следует git rebase @{u}, чтобы исключить коммит слияния, введенный git pull. Достаточно распространено, что Git имеет некоторые параметры конфигурации, чтобы уменьшить эти два шага до одного шага, сообщив git pull выполнить rebase вместо слияния (см. Параметры branch.<branch>.rebase, branch.autosetuprebase и pull.rebase)).

К сожалению, если у вас есть непроверенная фиксация слияния, которую вы хотите сохранить (например, фиксация слияния вложенной ветки функции в master), ни перетаскивание (git pull с branch.<branch>.rebase) не установлено на true), а также слияние (по умолчанию поведение git pull), за которым следует rebase. Это связано с тем, что git rebase устраняет слияния (линеаризует DAG) без опции --preserve-merges. Операция rebase-pull не может быть сконфигурирована для сохранения слияний, а слияние-выталкивание, за которым следует git rebase -p @{u}, не устранит слияние, вызванное слиянием. Обновление: Git v1.8.5 добавлено git pull --rebase=preserve и git config pull.rebase preserve. Это приводит к тому, что git pull выполняет git rebase --preserve-merges после того, как выберет восходящий поток. (Спасибо funkaster за хедз-ап!)

Очистка удаленных ветвей

git pull не обрезает ветки удаленного отслеживания, соответствующие ветвям, которые были удалены из удаленного репозитория. Например, если кто-то удаляет ветвь foo из удаленного репо, вы все равно увидите origin/foo.

Это приводит к тому, что пользователи случайно воскрешают убитые ветки, потому что считают, что они все еще активны.

Лучшая альтернатива: используйте git up вместо git pull

Вместо git pull, я рекомендую создать и использовать следующий псевдоним git up:

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

Этот псевдоним загружает все последние коммиты из всех восходящих ветвей (обрезка мертвых ветвей) и пытается ускорить переадресацию локальной ветки до последней фиксации в ветке вверх по течению. В случае успеха, тогда не было никаких локальных коммитов, поэтому не было риска конфликта слияния. Быстрая перемотка не сработает, если есть локальные (непроверенные) коммиты, дающие вам возможность просмотреть восходящие коммиты, прежде чем предпринимать действия.

Это по-прежнему изменяет рабочий каталог непредсказуемым образом, но только если у вас нет локальных изменений. В отличие от git pull, git up никогда не приведет вас к приглашению, ожидающему исправить конфликт слиянием.

Другой вариант: git pull --ff-only --all -p

Ниже приведена альтернатива вышеупомянутому алиасу git up:

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

Эта версия git up имеет такое же поведение, как предыдущий псевдоним git up, за исключением:

  • сообщение об ошибке является более загадочным, если ваша локальная ветвь не настроена с восходящей веткой
  • он полагается на недокументированную функцию (аргумент -p, который передается на fetch), который может измениться в будущих версиях Git

Если вы используете Git 2.0 или новее

С Git 2.0 и новее вы можете настроить git pull только для слияния быстрой пересылки по умолчанию:

git config --global pull.ff only

Это приводит к тому, что git pull действует как git pull --ff-only, но он все еще не извлекает все восходящие коммиты или очищает ветки origin/*, поэтому я по-прежнему предпочитаю git up.

519
ответ дан Richard Hansen 10 марта '13 в 1:23
источник поделиться

Мой ответ, вытащил из обсуждения, что возникло на HackerNews:

У меня возникает соблазн просто ответить на вопрос, используя закон заголовков Betteridge: почему git pull считается вредным? Это не так.

  • Нелинейности не являются внутренне плохими. Если они представляют фактическую историю, они в порядке.
  • Случайная реинтродукция коммитов rebased вверх по течению является результатом неправильной перезаписи истории вверх. Вы не можете переписать историю, когда история реплицируется по нескольким репозиториям.
  • Изменение рабочего каталога - ожидаемый результат; дискуссионных полезности, а именно в условиях поведения Hg/монотонном/Darcs/other_dvcs_predating_git, но опять же не по своей природе плохо.
  • Для слияния требуется приостановка рассмотрения работы других пользователей, а также ожидаемое поведение при растягивании git. Если вы не хотите сливаться, вы должны использовать git fetch. Опять же, это идиосинкразия git по сравнению с предыдущими популярными dvcs, но это ожидаемое поведение, а не внутренне плохое.
  • Сделать это сложно для переустановки на удаленную ветку. Не переписывайте историю, если вам это абсолютно не нужно. Я не могу, чтобы жизнь меня понимала это стремление к (поддельной) линейной истории.
  • Не очистка ветвей - это хорошо. Каждый репо знает, что хочет. git не имеет понятия отношений "ведущий-ведомый".
196
ответ дан Sérgio Carvalho 12 марта '14 в 18:15
источник поделиться

Это не считается вредным, если вы правильно используете Git. Я вижу, как это негативно влияет на ваш случай использования, но вы можете избежать проблем, просто не изменяя общую историю.

26
ответ дан Websitescenes 12 марта '14 в 18:43
источник поделиться

Принятые требования к ответам

Операция rebase-pull не может быть сконфигурирована для сохранения слияния

но с Git 1.8.5, который публикует этот ответ, вы можете сделать

git pull --rebase=preserve

или

git config --global pull.rebase preserve

или

git config branch.<name>.rebase preserve

docs сказать

Когда preserve, также проходит --preserve-merges до 'git rebase', так что локально зафиксированные коммиты слияния не будут сглажены, запустив 'git pull'.

В этом предыдущем обсуждении есть более подробная информация и диаграммы: Git pull --rebase --preserve-merges. Это также объясняет, почему git pull --rebase=preserve не совпадает с git pull --rebase --preserve-merges, что не так.

В этом другом предыдущем обсуждении объясняется, что на самом деле реализует вариант rebase сбережения-слияния, и как он намного сложнее, чем обычная rebase: Что именно делает git "rebase -preserve-merges" do (и почему?)

17
ответ дан Marc Liyanage 12 марта '14 в 19:38
источник поделиться

Другие вопросы по меткам