Ответ 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
.