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

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

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

Ответ 1

Резюме

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

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

С 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,
  • он выбирает все ветки вверх по течению (не только ветку, над которой вы сейчас работаете), и
  • он очищает старые ветки origin/* которые больше не существуют в апстриме.

Проблемы с git pull

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

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

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

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

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

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

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

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

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

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

Реинтродукция перебазированных комитетов

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

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

Изменения в рабочем каталоге Surprise

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

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 выполнить перебазировку вместо слияния (см branch.<branch>.rebase, branch.autosetuprebase и pull.rebase).

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

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

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, но он по-прежнему не извлекает все восходящие коммиты и не git pull --ff-only старые ветки origin/* поэтому я все же предпочитаю git up.

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

Операция 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 (и почему?)

Ответ 5

Если вы зайдете в старый репозиторий git git up, предлагаемый ими псевдоним будет другим. https://github.com/aanand/git-up

git config --global alias.up 'pull --rebase --autostash'

Это прекрасно работает для меня.