Fetch, pull и push: обмен без потери чужой работы
Общая ветка ушла вперёд
Вы начали работу утром, а другой автор уже опубликовал исправление. Ваш локальный main остаётся прежним, пока вы не выполните обмен. Чтобы решить, как объединяться, сначала получите новую историю и посмотрите её. Git не должен угадывать, готовы ли вы интегрировать чужие изменения прямо сейчас.
fetch получает объекты и обновляет ссылки наблюдения, например origin/main. Ваш рабочий main от этого сам не становится новым. pull выполняет получение и интеграцию по выбранной политике. Для первого курса мы явно используем pull --ff-only: он разрешает только быстрое продвижение и отказывается, если линии разошлись. push отправляет ваши объекты и предлагает обновить удалённую ветку. Fetch, pull и push имеют разные границы действия.
Отказ non-fast-forward означает, что предложенная вершина не продолжает текущую удалённую линию. Это повод получить историю и выбрать интеграцию, а не разрешение применить force push. Мы не переписываем общую историю в обязательных лабораториях. Даже в маленькой команде можно случайно затереть работу второго автора.
Наблюдайте две независимые линии
lab=$(mktemp -d "${TMPDIR:-/tmp}/git-07.XXXXXX")
git init --bare -b main "$lab/shared.git"
git init -b main "$lab/a"
cd "$lab/a"
git config user.name "Автор A"
git config user.email "a@example.invalid"
git config commit.gpgsign false
printf 'base\n' > README.txt
git add README.txt
git commit -m "Add base"
git remote add origin "$lab/shared.git"
git push -u origin main
git clone "$lab/shared.git" "$lab/b"
cd "$lab/b"
git config user.name "Автор B"
git config user.email "b@example.invalid"
git config commit.gpgsign false
printf 'workers=2\n' > limits.txt
git add limits.txt
git commit -m "Add worker limit"
git push origin main
cd "$lab/a"
git fetch origin
git log --oneline main..origin/main
cat README.txt
git status --shortПосле fetch main автора A ещё не содержит limits.txt. Log диапазона показывает коммит, доступный из origin/main, но не из main. Две точки в log означают выбор множества коммитов; это не тот же смысл, что два снимка у diff. Проверяйте команду, а не знакомые символы сами по себе.
Теперь интегрируйте полученную вершину:
git merge --ff-only origin/main
cat limits.txt
printf 'queue=16\n' > queue.txt
git add queue.txt
git commit -m "Add queue limit"
git push origin main
cd "$lab/b"
git pull --ff-only origin main
cat queue.txtA быстро продвинулся до коммита B, добавил своё изменение и опубликовал. B получил его без нового merge-коммита: собственная ветка не успела разойтись. Обе рабочие папки теперь содержат два файла лимитов и прежний README.
Если обе стороны уже сделали коммит
Представьте, что B сохранил свою новую запись до получения queue.txt. Тогда pull --ff-only остановится. Посмотрите graph, прочитайте git diff main...origin/main для изменения удалённой линии от общей базы и выберите обычное слияние. Если нужно, разрешите конфликт по прошлой главе, проверьте результат и только затем push. Fetch сам по себе не выполняет эту проверку за вас.
В лаборатории не меняем глобальную настройку pull, чтобы не скрывать политику. pull --rebase имеет другой результат истории и будет разобран отдельно. Не запускайте незнакомый вариант только ради исчезновения сообщения об отказе.
Разберите гонку публикаций без принуждения
A выполнил fetch, прочитал новую вершину и подготовил свой commit. Между этим и push B успел опубликовать ещё одну запись. A снова получает отказ. Это не значит, что предыдущий fetch не работал: он показал состояние на момент обмена, а не зарезервировал общую ветку. Повторите получение, прочитайте новое изменение и интегрируйте его по договору команды. До этого не обещайте, что публикация обязательно пройдёт.
Сформулируйте отличие «моя версия содержит последнюю известную историю» от «общая история больше не изменится». Первое можно проверить локальными ссылками после fetch, второе нельзя обеспечить только этими командами. В лаборатории роли играете вы сами, поэтому остановите публикации на время чтения; в команде такой остановки может не быть.
Если сообщение push указывает другую ветку, проверьте аргументы и upstream. Отказ не нужно интерпретировать по памяти одной фразы. Назовите полный путь общей точки, выбранный локальный main и удалённое имя, которое собираетесь обновлять. Эта короткая последовательная проверка помогает надёжно отличить новое расхождение от отправки не той линии.
Самостоятельная лаборатория
Практика — 40–60 минут. В двух клонах из одной базы сделайте разные коммиты, меняющие разные файлы. Сначала опубликуйте A. В B попробуйте push и сохраните сообщение отказа. Выполните fetch, нарисуйте три вершины, затем покажите отказ ff-only интеграции.
Объедините линии через обычный merge, проверьте оба файла и опубликуйте результат. Обновите A быстрым продвижением. Сверьте хеши main в обоих клонах и bare. Во время лаборатории не используйте force, reset общей ветки или удаление чужой записи. Они не нужны, чтобы закончить задачу.
Критерии готовности
Вы показываете, что fetch изменил origin/main, но не рабочий main. После расходящихся коммитов ff-only отказал, обычный merge сохранил обе работы. Все три вершины после завершения совпали. Можете объяснить, почему между fetch и push другой автор всё ещё способен опубликовать новую запись и снова вызвать отказ.
Разбор
В расходящемся опыте возникает merge-коммит с двумя родителями; он продолжает опубликованную линию, поэтому обычный push допустим. Если ff-only прошёл, собственный коммит B, вероятно, создавался уже поверх новой базы. Если файлы исчезли, проверьте, не подменили ли вы объединение принудительным обновлением.
Следующая глава посвящена проверке чужой ветки и договору ревью. При обмене держите четыре вопроса: откуда получаю, какая локальная ветка выбрана, какую общую ветку обновляю и что уже посмотрел. Этот порядок переносится на сетевой хостинг без изменения модели Git.