Fetch и интеграция
git fetch только обновляет origin/*, не трогая твою работу. Затем выбирай: pull (merge) делает merge-пузыри, pull --rebase переигрывает коммиты ради линейной истории, pull --ff-only отказывается сливать при расхождении. Merge хранит правду, rebase — линейность.
git pull ощущается как одна безопасная кнопка, но он тихо принимает за тебя решение: как вложить коммиты удалёнки в твои собственные. Сделай pull двух коммитов, когда у тебя есть локальная работа, и получишь merge-коммит — маленький «пузырь» в истории — хотел ты его или нет. Команды, которым важен чистый лог, учатся перестать пуллить вслепую и снова разбивать операцию на две половины: fetch, затем интеграция осознанно.
К концу этого урока ты сможешь делать fetch, не трогая свою работу, и осознанно выбирать между merge, rebase и интеграцией только-fast-forward — зная, какую историю каждая из них производит.
После этого урока ты сможешь использовать git fetch, чтобы безопасно обновлять origin/*, затем интегрировать через merge, --rebase или --ff-only, объяснять форму истории, которую даёт каждый, и настроить pull.rebase/pull.ff, чтобы git pull по умолчанию делал то, что ты задумал.
git fetch обновляет твои удалённо-отслеживающие ветки и больше ничего. Он скачивает новые коммиты с сервера и продвигает origin/main, origin/feature и компанию. Он не трогает твои локальные ветки, твой индекс или рабочее дерево — поэтому он никогда не вызовет конфликт и не потеряет работу:
git fetch origin
git log --oneline main..origin/main # что есть на сервере, чего нет у меняПоскольку fetch неразрушающий, это команда, которую запускают, когда хочется просто увидеть, что изменилось на upstream, прежде чем решать, как реагировать.
Интеграция через merge — поведение git pull по умолчанию — сохраняет настоящую историю, но добавляет merge-пузыри. После fetch git merge origin/main (который голый pull запускает за тебя) соединяет две линии работы merge-коммитом с двумя родителями:
git fetch origin
git merge origin/main # или просто: git pullПлюс: история правдива — она фиксирует, что два человека действительно работали параллельно и где их работа соединилась. Минус: множество таких соединений создаёт клубок «merge-пузырей», от которого git log шумит, а git bisect читается труднее.
Интеграция через rebase переигрывает твои локальные коммиты поверх удалёнки, давая линейную историю. Вместо merge-коммита rebase берёт коммиты, уникальные для твоей ветки, откладывает их в сторону, fast-forward-ит твою ветку до вершины удалёнки, затем заново накладывает твои коммиты один за другим сверху:
git fetch origin
git rebase origin/main # или в один шаг: git pull --rebaseРезультат — единая прямая линия, как будто ты начал свою работу после всех коммитов удалёнки с самого начала. Никакого merge-пузыря. Цена: твои коммиты получают новые хеши (они переписываются), поэтому нельзя ребейзить коммиты, которые ты уже расшарил на ветке, поверх которой строятся другие.
▸Почему это работает
Merge против rebase — настоящий компромисс, а не война стилей. Merge сохраняет истинную топологию: всегда видно, что фича разрабатывалась рядом с main и была слита в конкретный день — ценно для аудита и долгоживущих веток. Rebase отбрасывает эту параллельность ради чистой, удобной для bisect прямой линии, читающейся как история. Широко используемое правило: ребейзь свою локальную, незапушенную работу, чтобы причесать её перед расшариванием; сливай (или используй merge платформы) для интеграции расшаренных веток и никогда не ребейзь коммиты, которые другие уже забрали.
git pull --ff-only отказывается интегрировать, когда ветки разошлись. Fast-forward возможен только когда у твоей ветки нет коммитов, которых нет у удалёнки, — тогда git может просто сдвинуть указатель твоей ветки вперёд без нового коммита. --ff-only разрешает ровно это и громко падает в противном случае:
git pull --ff-only
# fatal: Not possible to fast-forward, aborting.Это падение — фича: вместо тихого создания merge, о котором ты не просил, git останавливается и заставляет тебя решить — merge или rebase. Многие инженеры ставят это по умолчанию именно чтобы убить внезапные merge-коммиты.
Настрой умолчание, чтобы git pull перестал угадывать. Можно зафиксировать стратегию интеграции глобально или для одного репозитория:
git config --global pull.ff only # pull делает fast-forward или прерывается
git config --global pull.rebase true # ИЛИ: pull всегда ребейзитПоставь одно из этого, и git pull станет предсказуемым: либо он чисто fast-forward-ится / прерывается, либо ребейзит твою работу поверх удалёнки. Выбор политики на уровне команды убирает из истории целый класс шума из случайных merge-пузырей.
Одно и то же расхождение, тремя способами.
Ты ответвился от main на A, затем закоммитил X и Y локально. Команда запушила B и C в origin/main. Ты делаешь git fetch — теперь origin/main на C, твоя main на Y, и две линии разошлись от A.
git merge origin/mainсоздаёт merge-коммитM, родители которогоYиC. История держит обе линии видимыми и соединяет их вM.git log --graphпоказывает пузырь. Твои коммитыX,Yсохраняют исходные хеши.git rebase origin/mainпереходит наC, затем заново накладываетXиYсверху как новые коммитыX'иY'. История — прямая линияA-B-C-X'-Y'. Никакого merge-коммита, ноXиYисчезли, заменённые переписанными копиями.git pull --ff-onlyнемедленно прерывается с «Not possible to fast-forward» — потому что твойYне содержится вC. Ничего не меняется; git возвращает решение тебе.
Одна стартовая точка, три разные истории. Выбирать осознанно — а не позволять голому pull выбрать merge за тебя — и есть навык.
▸Частая ошибка
Дорогая ошибка — запустить git pull --rebase на ветке, чьи коммиты ты уже запушил, а коллега уже забрал. Rebase переписывает эти коммиты в новые хеши; клон коллеги всё ещё держит оригиналы, и твой следующий push либо конфликтует, либо, если форсировать, осиротит их работу. Безопасная граница: ребейзь только коммиты, живущие исключительно в твоём локальном репозитории. Как только коммит расшарен — интегрируй через merge, а не rebase.
Твоя ветка и origin/main разошлись. Ты хочешь избежать случайного merge-коммита и быть вынужденным решить осознанно. Какая команда это делает?
Разделение pull обратно на половины даёт тебе контроль. git fetch безопасно продвигает origin/* и никогда не трогает твою работу, так что можно сначала осмотреть расхождение. Затем интегрируешь осознанно: merge (умолчание pull) сохраняет настоящую параллельную историю, но добавляет merge-пузыри; --rebase переигрывает твои локальные коммиты ради чистой линейной истории ценой переписывания их хешей; --ff-only отказывается интегрировать разошедшуюся ветку, чтобы ты выбрал осознанно. Зафиксируй поведение через pull.ff = only или pull.rebase = true и никогда не ребейзь коммиты, которые уже есть у других. Дальше: когда переписывание истории и есть цель — как запушить его безопасно.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.