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

409

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

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

Ричард Хансен
источник
53
@ j.karlsson: meta.stackexchange.com/questions/17463/…
Ричард Хансен,
8
Или вы можете просто git pull --rebaseустановить эту стратегию по умолчанию для новых веток git config branch.autosetuprebase
knoopx
4
У knoopx все правильно, добавив --rebaseфлаг, чтобы git pullсинхронизировать локальный с удаленным, а затем воспроизвести ваши локальные изменения поверх обновленного локального. Затем, когда вы нажимаете, все, что вы делаете, это добавление новых коммитов в конец удаленного. Довольно просто
Хит Лилли
4
Спасибо @BenMcCormick. Я уже сделал это, но дискуссия относительно обоснованности вопроса, кажется, проходит в этих комментариях под вопросом. И я думаю, что вопрос о создании платформы для представления вашего личного мнения как факта - это не то, для чего действительно нужна структура вопросов и ответов SO.
MCV
4
@RichardHansen, это просто способ обмануть систему баллов, особенно если у вашего ответа такая резкая разница в тоне и такой короткий промежуток времени. Используя вашу модель вопросов и ответов, мы все могли бы просто задавать вопросы и сами отвечать на них, используя наши предыдущие знания. На этом этапе вам следует рассмотреть возможность написания поста в блоге, так как это во много раз более уместно Q & A специально ищет знания других людей. Сообщение в блоге показывает ваши собственные.
Джош Браун

Ответы:

546

Резюме

По умолчанию 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 не очень хорошо передает эту многомерную информацию. Напротив, ряд нормальных коммитов легко просмотреть.
  • Урегулирование конфликта слиянием сложно, и ошибки часто остаются незамеченными в течение длительного времени, потому что коммиты слияния трудно рассмотреть.
  • Слияния могут спокойно заменить эффекты регулярных коммитов. Код больше не является суммой добавочных коммитов, что приводит к недопониманию того, что на самом деле изменилось.
  • Коммиты слияния могут нарушить некоторые схемы непрерывной интеграции (например, автоматически построить только путь первого родителя в соответствии с принятым соглашением, что вторые родители указывают на незавершенные работы в процессе).

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

Обратите внимание, что цель 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Операция слияния (или перебазирования) изменяет рабочий каталог и индекс, что означает, что ваш рабочий каталог и индекс должны быть чистыми.

Вы можете использовать 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), ни rebase-pull ( git pullс branch.<branch>.rebaseустановленным значением true), ни merge-pull ( git pullповедение по умолчанию ), за которым следует ребаз будет работать. Это потому, что git rebaseисключает слияния (линеаризует DAG) без --preserve-mergesопции. Операция rebase-pull не может быть сконфигурирована для сохранения слияний, и pull-merge, сопровождаемый git rebase -p @{u}not, не устранит слияние, вызванное merge-pull. Обновление: добавлен 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, но он все еще не извлечь все добывающие фиксации или очистить старые origin/*ветви , так что я до сих пор предпочитаю git up.

Ричард Хансен
источник
6
@brianz: git remote update -pэквивалентно git fetch --all -p. Я имею привычку печатать, git remote update -pпотому что когда-то fetchне было -pвозможности. По поводу ведущих !смотрите описание alias.*в git help config. В нем говорится: «Если расширение псевдонима начинается с восклицательного знака, оно будет рассматриваться как команда оболочки».
Ричард Хансен
13
Git 2.0 добавляет pull.ffконфигурацию, которая, кажется, достигает того же самого, без псевдонимов.
Дэнни Томас
51
Некоторые из причин звучат как «тянуть может вызвать проблемы, когда другие делают сумасшедшие вещи». Нет, это такие сумасшедшие вещи, как перебазирование коммита из репозитория, который вызывает проблемы. Восстановление IMO безопасно только тогда, когда вы делаете это локально на коммите, который еще не был передан. Как, например, когда вы вытягиваете, прежде чем нажать, перебазировка локальных коммитов помогает сохранить вашу историю линейной (хотя линейная история не такая уж большая проблема). Тем не менее, git upзвучит как интересная альтернатива.
mcv
16
Большинство ваших соображений заключается в том, что вы делаете что-то не так: вы пытаетесь просмотреть код в своей рабочей ветке . Это не очень хорошая идея, просто создайте новую ветку, потяните --rebase = preserve и затем бросьте эту ветку (или объедините, если хотите).
funkaster
5
Смысл @ funkaster здесь имеет большой смысл, особенно в отношении: «Трудности в рассмотрении комитетов других людей». Это не тот обзор, который используют большинство пользователей Git, это то, что я никогда не видел рекомендуемым, и это причина всей ненужной дополнительной работы, описанной под заголовком git pull.
Бен Регенспан,
195

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

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

  • Нелинейности не являются плохими по своей природе. Если они представляют реальную историю, они в порядке.
  • Случайное повторное введение коммитов, перебазированных вверх по течению, является результатом ошибочного переписывания истории вверх по течению. Вы не можете переписать историю, когда история реплицируется по нескольким репозиториям.
  • Изменение рабочего каталога является ожидаемым результатом; дискуссионных полезности, а именно в условиях поведения Герберт / монотонно / Darcs / other_dvcs_predating_git, но опять же не по своей природе плохо.
  • Для слияния необходима пауза для просмотра чужой работы, и это опять-таки ожидаемое поведение при git pull. Если вы не хотите объединяться, вы должны использовать git fetch. Опять же, это отличительная черта git по сравнению с предыдущими популярными dvcs, но это ожидаемое поведение, а не по сути плохое.
  • Хорошо сделать перебазирование против удаленной ветки. Не переписывайте историю, если вам это абсолютно не нужно. Я не могу на всю жизнь понять это стремление к (фальшивой) линейной истории
  • Не убирать ветки это хорошо. Каждый репо знает, что он хочет держать. Git не имеет понятия отношения хозяин-раб.
Сержиу Карвалью
источник
13
Согласен. В этом нет ничего вредного git pull. Тем не менее, это может противоречить некоторым вредным практикам, таким как желание переписать историю больше, чем это строго необходимо. Но git гибок, поэтому, если вы хотите использовать его по-другому, сделайте это любым способом. Но это потому, что вы (ну, @Richard Hansen) хотите сделать что-то необычное в git, а не потому, что git pullэто вредно.
mcv
28
Не могу согласиться больше. Люди защищают git rebaseи считают git pullвредным? В самом деле?
Виктор Мороз
10
Было бы неплохо увидеть, как кто-то создает граф с моралью в качестве вашей оси и классифицирует команды git как хорошие, плохие или где-то между ними. Эта диаграмма будет отличаться между разработчиками, хотя она будет много говорить об использовании Git.
michaelt
5
Моя проблема с git pullбез --rebaseопции - это направление слияния, которое оно создает. Когда вы смотрите на различия, все изменения в этом слиянии теперь принадлежат человеку, который потянул, а не тому, кто внес изменения. Мне нравится рабочий процесс, в котором объединение зарезервировано для двух отдельных ветвей (A -> B), поэтому коммит слияния понятен, что было введено, а перебазирование зарезервировано для получения обновлений в одной и той же ветви (удаленный A -> локальный A). )
Крейг Кочис
4
Итак, что же вы знаете, если кто-то сделал тягу всего за несколько секунд до кого-то другого или наоборот? Я думаю, что это просто шум и просто запутывает действительно актуальную историю. Это даже уменьшает ценность истории. Хорошая история должна быть а) чистой и б) на самом деле иметь важную историю.
Дэвид Онгаро
26

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

Охота Бурдик
источник
10
Чтобы уточнить это: если каждый работает над собственной веткой (что, на мой взгляд, является правильным способом использования git), git pullэто не проблема. Ветвление в git это дешево.
AlexQueue
18

Принятые ответы на претензии

Операция rebase-pull не может быть настроена для сохранения слияний

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

git pull --rebase=preserve

или

git config --global pull.rebase preserve

или

git config branch.<name>.rebase preserve

В документах говорят

Когда 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» (и почему?)

Марк Лиянэйдж
источник
0

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

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

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

Натан Редблур
источник