Олег Марков
Получение и слияние в Git - команда git pull
Введение
Команда git pull — одна из самых часто используемых в повседневной работе с Git. Она отвечает за получение изменений с удалённого репозитория и их автоматическое слияние с вашей текущей локальной веткой.
Смотрите, я покажу вам, как важно понимать, что именно делает git pull внутри. На первый взгляд команда кажется простой: обновить ветку с сервера. Но за этим скрываются разные стратегии слияния, возможные конфликты, настройка отслеживаемых веток и типичные ошибки, которыми легко сломать историю репозитория.
В этой статье вы разберётесь:
- что на самом деле выполняет git pull;
- чем git pull отличается от git fetch и git merge;
- какие режимы слияния бывают — merge, rebase, fast-forward;
- как правильно настраивать ветки и remotes;
- как решать конфликты после git pull;
- как сделать работу с git pull более безопасной и предсказуемой.
Я буду опираться на практические примеры и комментарии в коде, чтобы вы могли повторить действия у себя и увидеть, как это работает.
Что делает git pull на самом деле
Внутренний механизм: fetch + merge
Важно понять одну базовую вещь: git pull — это не отдельная магическая операция. По сути, это сокращение для двух команд:
- получение новых коммитов с сервера;
- слияние этих коммитов с вашей текущей веткой.
Если разложить git pull на шаги, получится такое:
# Шаг 1 - получить обновления из удалённого репозитория
git fetch origin
# Шаг 2 - слить изменения из удалённой ветки в текущую локальную
git merge origin/main
Комментарии к командам:
git fetch origin
// Забирает все новые коммиты и ссылки веток с удалённого репозитория origin
// При этом ваша текущая ветка и рабочие файлы не меняютсяgit merge origin/main
// Сливает в текущую ветку изменения из удалённой ветки origin/main
// Может создать новый merge-коммит
// Может вызвать конфликты, если изменения пересекаются
Команда git pull делает эти шаги за вас автоматически, на основе настроек отслеживаемой ветки.
Общая форма команды:
git pull <remote> <branch>
Например:
git pull origin main
// Получить изменения из ветки main на удалённом репозитории origin
// И слить их в текущую локальную ветку
Если вы не указываете ни remote, ни ветку, Git использует настройки текущей ветки: какой remote она отслеживает и какую удалённую ветку с ней связывает.
Отслеживаемые ветки (tracking branches)
Чтобы git pull знал, откуда и во что тянуть изменения, у ветки есть пара «отслеживаемая локальная ветка — отслеживаемая удалённая ветка».
Например:
- локальная ветка:
main - удалённая отслеживаемая ветка:
origin/main
Посмотреть настройки отслеживания можно так:
git branch -vv
// Показывает локальные ветки
// Для каждой ветки указывает, какую удалённую ветку она отслеживает
// И короткий хэш последнего коммита
Типичный вывод будет выглядеть так:
* main a1b2c3d [origin/main] Add logging to auth service
feature/login e4f5g6h [origin/feature/login: ahead 2] Implement login UI
Здесь вы видите, что:
- ветка main отслеживает origin/main;
- ветка feature/login отслеживает origin/feature/login и «опережает» её на 2 коммита.
Когда вы выполняете:
git pull
Git использует эту информацию по ветке main:
- remote:
origin - upstream branch:
origin/main
И поэтому выполняет эквивалент:
git pull origin main
Если отслеживание не настроено, git pull без аргументов либо не сработает, либо попросит указать remote и ветку.
Как создать отслеживаемую ветку
Есть несколько способов настроить отслеживание, но на практике используются два.
Создание новой ветки из удалённой
git checkout -b feature/login origin/feature/login
// Создаём новую локальную ветку feature/login
// Указываем, что её базой является origin/feature/login
// Git автоматически настроит отслеживание этой удалённой ветки
Теперь в ветке feature/login можно просто вызвать:
git pull
Привязка существующей локальной ветки к удалённой
Если ветка уже есть, но не привязана к remote:
git branch --set-upstream-to=origin/feature/login feature/login
// Говорим, что локальная ветка feature/login должна отслеживать origin/feature/login
// Теперь git pull внутри этой ветки будет использовать origin/feature/login
Или, если вы уже стоите в нужной ветке:
# Вы находитесь в ветке feature/login
git branch --set-upstream-to=origin/feature/login
// Настраиваем upstream-ветку для текущей ветки
Режимы работы git pull: merge, rebase, fast-forward
Git позволяет по-разному встраивать изменения из удалённой ветки в вашу локальную. Это влияет на историю коммитов и на то, как удобно её читать.
По умолчанию: merge (слияние)
Стандартное поведение git pull (если вы явно ничего не настраивали) — использовать merge.
Сценарий:
- Ваша локальная ветка main
- Коллега сделал несколько коммитов в origin/main
- Вы сделали локальные коммиты в main
- Выполняете git pull
Git:
- забирает новые коммиты с сервера (fetch);
- создаёт новый merge-коммит, у которого два родителя:
- последний локальный коммит;
- последний удалённый коммит.
Выглядит это примерно так:
A --- B --- C --- D (origin/main)
\
E --- F (main до pull)
\
M (merge-коммит после git pull)
Здесь:
- D — последний коммит на сервере;
- F — последний ваш локальный коммит;
- M — новый merge-коммит, который создаёт git pull.
Основные особенности режима merge:
- История остаётся разветвлённой, вы видите, как ветки сходятся в merge-коммитах.
- Не переписываются существующие коммиты ни у вас, ни у коллег.
- При активной разработке история может выглядеть «шумно» из-за множества merge-коммитов.
Выполнить git pull в merge-режиме можно явно:
git pull --merge
// Выполнить pull с использованием merge
// Поведение аналогично умолчанию во многих конфигурациях
Режим rebase: линейная история
Многим разработчикам удобнее смотреть на линейную историю без лишних merge-коммитов. Для этого в Git есть режим rebase.
Схема работы git pull с rebase такая:
- Git забирает новые коммиты с сервера.
- Ваши локальные коммиты «переписываются» так, как будто они были сделаны поверх обновлённой удалённой ветки.
Посмотрим на пример. До pull история такая:
A --- B --- C --- D (origin/main)
\
E --- F (main локальная)
После:
git pull --rebase
История станет:
A --- B --- C --- D --- E' --- F' (main локальная)
^
origin/main (может быть обновлён дальше)
Что здесь произошло:
- Коммиты E и F не просто «добавились» к D.
- Git создал новые версии этих коммитов E' и F', основанные на D.
- Старые E и F больше не используются в основной истории (но пока ещё есть в репозитории).
Таким образом:
- История становится линейной: все коммиты идут один за другим.
- При просмотре лога проще понять развитие кода.
- Но: ваши локальные коммиты получили новые хэши, то есть буквально переписаны.
Выполнить pull с rebase можно так:
git pull --rebase
// Получить изменения с сервера
// Поверх них «переиграть» ваши локальные коммиты
// Сохранить линейную историю
Этот вариант популярен в командах, которые ценят аккуратный лог коммитов.
Важно: не стоит делать rebase публичных веток, которые уже использует кто-то ещё. В случае git pull --rebase это обычно не проблема, потому что вы «подстраиваете» свои локальные коммиты под общую ветку, а не ломаете её.
Только fast-forward: без merge-коммитов
Есть ещё один важный режим: fast-forward only. Он не переписывает ваши коммиты, а просто обновляет указатель ветки, если это возможно без merge.
Поясню на примере.
Ситуация 1 — вы не делали локальных коммитов:
A --- B --- C (origin/main)
^
main локальная
После:
git pull --ff-only
История:
A --- B --- C (origin/main, main)
Здесь не требуется слияние, достаточно «перемотать» main до C. Это и есть fast-forward.
Ситуация 2 — вы сделали локальный коммит:
A --- B --- C (origin/main)
\
D (main локальная)
После:
git pull --ff-only
Git скажет, что fast-forward невозможен, потому что есть ответвление (коммит D), и прервёт операцию. Никаких автослияний, никаких merge-коммитов.
Использование:
git pull --ff-only
// Получить изменения
// Обновить ветку только если это можно сделать fast-forward
// В противном случае — остановиться с ошибкой
Этот режим делает поведение git pull более безопасным: вы осознанно управляете моментом слияния и не получаете неожиданные merge-коммиты.
Как выбрать режим по умолчанию
Чтобы не прописывать каждый раз флаги, можно настроить поведение:
# Сделать rebase стандартным при git pull
git config --global pull.rebase true
# Вернуть стандартное merge-поведение
git config --global pull.rebase false
# Заставить git pull по умолчанию использовать режим fast-forward only
git config --global pull.ff only
Комментарии:
pull.rebase true
// Теперь git pull без флагов будет работать как git pull --rebasepull.ff only
// Теперь Git не будет делать merge, если нельзя сделать fast-forward
// Попытается либо fast-forward, либо завершит операцию с ошибкой
Вы можете комбинировать эти настройки или настраивать их для конкретных репозиториев без --global.
Практические сценарии использования git pull
Обновление основной ветки разработки
Самый частый сценарий — вы работаете в ветке main или develop и периодически обновляете её из удалённого репозитория.
Базовый вариант:
# Убедитесь, что вы в нужной ветке
git checkout main
# Обновите ветку с сервера
git pull
// Получить изменения из origin/main
// Влить их в локальную main согласно вашей настройке pull.rebase и pull.ff
Рекомендуется:
- перед началом рабочего дня обновлять основную ветку;
- перед созданием feature-ветки сделать git pull, чтобы ответвляться от актуального состояния.
Обновление feature-ветки перед push
Сценарий:
- Вы создали ветку feature/login от main.
- Пока вы работали, в main появилось много новых коммитов.
- Вы хотите отправить свою ветку в репозиторий, но сначала стоит подтянуть актуальные изменения и разрешить конфликты локально.
Один из подходов:
# Перейти в основную ветку
git checkout main
# Обновить основную ветку
git pull --ff-only
# Вернуться к своей задаче
git checkout feature/login
# Перебазировать свои изменения поверх обновлённой main
git rebase main
Но если ваша ветка отслеживает удалённую ветку (origin/feature/login), вы можете обновить именно её:
# Находитесь в ветке feature/login
git pull --rebase
// Получить обновления из origin/feature/login
// Переписать ваши локальные коммиты поверх обновлённой ветки
После успешного rebase:
git push
Если Git сообщит, что push невозможен из-за расхождений, вам может понадобиться:
git push --force-with-lease
// Аккуратно перезаписать удалённую ветку
// --force-with-lease безопаснее, чем просто --force
// Git проверит, что на сервере нет чужих новых коммитов
Обновление сразу всех веток и тегов
Иногда вам нужно не просто обновить текущую ветку, а синхронизировать локальный репозиторий с удалённым максимально полно.
Для этого есть:
# Обновить все ветки из origin
git fetch --all --prune
// Забрать данные по всем remotes
// Удалить локальные ссылки на удалённые ветки, которые были удалены на сервере
А затем вручную привести нужные ветки к актуальному состоянию с помощью git pull, git rebase или git merge.
git pull сам по себе обновляет только:
- текущую локальную ветку;
- и только от одного удалённого репозитория (обычно origin).
Конфликты при git pull и их решение
Когда возникают конфликты
Конфликты появляются, когда и вы, и кто-то ещё изменили один и тот же фрагмент файла по-разному. Тогда Git не может автоматически решить, какая версия «правильная».
Это может случиться:
- при merge-режиме (git pull или git pull --merge);
- при rebase-режиме (git pull --rebase);
- как при обновлении основной ветки, так и при работе с feature-веткой.
Типичная ситуация:
- Коллега меняет функцию calculateTotal и пушит это в origin/main.
- Вы, не обновив ветку, тоже меняете calculateTotal.
- При git pull Git пытается совместить изменения и видит конфликт.
Как выглядит конфликт в файлах
После неудачного git pull Git пометит конфликтующие файлы специальными маркерами. Например, в файле order.go вы увидите:
// calculateTotal.go
func calculateTotal(order Order) float64 {
<<<<<<< HEAD
// Ваша локальная версия функции
total := 0.0
for _, item := range order.Items {
total += item.Price * float64(item.Quantity)
}
return total
=======
// Версия, пришедшая из origin/main
total := 0.0
for _, item := range order.Items {
total += item.PriceWithDiscount()
}
return total
>>>>>>> origin/main
}
Комментарии к маркерам:
<<<<<<< HEAD
// Начало блока вашей локальной версии (HEAD — это текущий коммит)=======
// Разделитель между вашими и удалёнными изменениями>>>>>>> origin/main
// Конец блока, показывающий, что ниже была версия из origin/main
Ваша задача — вручную отредактировать эти места и оставить только итоговый корректный код.
Пошаговое разрешение конфликта
Давайте разберёмся на практике, как действовать.
- Выполняете git pull, получаете сообщение о конфликте:
Auto-merging order.go
CONFLICT (content): Merge conflict in order.go
Automatic merge failed; fix conflicts and then commit the result.
- Проверяете список файлов с конфликтами:
git status
// Покажет файлы в состоянии unmerged
// Они будут отмечены как both modified
- Открываете файл и решаете, какая версия нужна, либо объединяете изменения. Например, объединённая версия может выглядеть так:
func calculateTotal(order Order) float64 {
// Итоговая версия после ручного слияния
total := 0.0
for _, item := range order.Items {
// Используем скидку, как в удалённой версии
total += item.PriceWithDiscount()
}
// Допустим, вы хотите добавить логирование из своей версии
// logTotal(total)
return total
}
// Здесь вы извлекли полезное из обеих версий
// Комментарии и логика остались осмысленными
Важно: удалите все маркеры <<<<<<<, =======, >>>>>>>. Они не должны остаться в коде.
- Отмечаете файл как исправленный:
git add order.go
// Сообщаете Git, что конфликт в этом файле решён
- Если конфликт был во время merge (стандартный git pull):
git commit
// Завершаете merge, создавая merge-коммит с вашими решениями конфликтов
Если конфликт был во время git pull --rebase:
git rebase --continue
// Говорите Git продолжить rebase после того, как вы исправили конфликт
// При необходимости повторяете шаги, если конфликты есть в следующих коммитах
Отмена git pull, если всё пошло не так
Иногда проще откатить попытку git pull и начать заново.
Если это был merge-пулл
Если вы ещё не завершили merge-коммит (то есть не сделали commit после конфликта), можно выполнить:
git merge --abort
// Отменить текущий процесс слияния
// Вернуться к состоянию ветки до git pull
Это вернёт репозиторий к исходному состоянию до начала слияния.
Если это был rebase-пулл
При git pull --rebase в случае проблем можно сделать:
git rebase --abort
// Отменить процесс rebase
// Вернуться к состоянию до git pull --rebase
Если вы уже завершили pull (commit или завершённый rebase), для отката потребуется использовать git reflog и git reset, но это уже более продвинутая тема.
Настройки и расширенные возможности git pull
Работа с несколькими remotes
В одном локальном репозитории может быть несколько удалённых:
- origin — основной репозиторий;
- upstream — исходный репозиторий, из которого был форк;
- ещё какие-то дополнительные репозитории команды.
Посмотреть список:
git remote -v
// Показывает все удалённые репозитории с их URL
// Для каждого remote есть адрес для fetch и для push
Например:
origin git@github.com:your-org/app.git (fetch)
origin git@github.com:your-org/app.git (push)
upstream git@github.com:main-org/app.git (fetch)
upstream git@github.com:main-org/app.git (push)
Теперь вы можете получать изменения с нужного remote:
# Получить изменения из origin для текущей ветки
git pull origin main
# Получить изменения из upstream для текущей ветки
git pull upstream main
// В обоих случаях изменения будут влиты в вашу текущую локальную ветку
// Но источник изменений будет разный
Важно понимать, что git pull всегда обновляет только текущую ветку, а не все ветки сразу.
Управление поведением с помощью конфигурации
Помимо глобальных настроек pull.rebase и pull.ff, можно настраивать поведение git pull по веткам.
Например, установить для конкретной ветки выполнение pull с rebase:
# Для текущей ветки
git config branch.main.rebase true
// Теперь в ветке main команда git pull будет работать как git pull --rebase
// Но это не затронет другие ветки
Проверить текущие настройки можно так:
git config --show-origin pull.rebase
git config --show-origin pull.ff
// Покажет, где именно задана настройка (глобально или в локальном конфиге репозитория)
Когда не стоит использовать git pull
Есть несколько ситуаций, когда лучше разделить fetch и merge вручную:
Вам нужно сначала посмотреть, что изменилось на сервере, и только потом решать, вливать это или нет.
Тогда вы делаете:
git fetch origin git log HEAD..origin/main// Сначала забираете изменения
// Потом смотрите список новых коммитов между вашей веткой и origin/main
// Только после анализа принимаете решение, как и когда объединятьВы хотите использовать нестандартную стратегию merge, например ours, theirs, или дополнительные опции.
Тогда после fetch вы вызываете merge с нужными параметрами:
git fetch origin git merge --strategy=ours origin/main// Пример - стратегия ours берёт конфликтующие участки из вашей ветки
// и игнорирует конфликтующие изменения из origin/mainВам нужно обновить несколько веток пошагово, а не только текущую.
В таких случаях удобнее сначала использовать git fetch, а потом вручную управлять слияниями или rebase.
Рекомендованные практики при работе с git pull
Явно задавайте желаемое поведение
Чтобы избежать неожиданных merge-коммитов и переписанной истории, имеет смысл сознательно выбрать один из стилей работы:
- предпочитаете аккуратную линейную историю — используйте rebase;
- предпочитаете строгое fast-forward без автослияний — используйте ff-only;
- хотите видеть все точки слияния — используйте merge.
Примеры настройки:
# Линейная история через rebase для всех репозиториев
git config --global pull.rebase true
# Или более строгий вариант - только fast-forward
git config --global pull.ff only
Обновляйте основную ветку перед созданием feature-веток
Последовательность:
git checkout main
git pull --ff-only # или git pull --rebase, если так удобнее
git checkout -b feature/new-report
// Так вы создаёте новую ветку от самого свежего состояния main
// Меньше шансов получить конфликты позже
Не смешивайте незакоммиченные изменения с git pull
Если у вас есть незакоммиченные изменения и вы вызываете git pull, есть риск:
- получить конфликт сразу в грязном рабочем дереве;
- запутаться, где ваши локальные несохранённые правки, а где изменения из удалённого репозитория.
Лучше сделать так:
Закоммитить изменения:
git add . git commit -m "Describe your changes" git pull --rebaseИли временно спрятать изменения:
git stash # Спрятать незакоммиченные изменения git pull # Обновиться с сервера git stash pop # Вернуть изменения поверх обновлённой ветки
Всегда читайте сообщения Git при ошибках
Если git pull завершился с ошибкой, Git почти всегда даёт понятную подсказку:
- предложит выполнить git merge --abort или git rebase --abort;
- покажет, какие файлы в конфликте;
- подскажет, какой режим можно включить в конфигурации.
Не игнорируйте эти сообщения, в них часто есть точное решение.
Заключение
Команда git pull — это удобное сокращение для двух операций: получения изменений с удалённого репозитория и их слияния с вашей текущей веткой. Понимая, что за ней стоят git fetch и git merge или rebase, вы можете гораздо точнее управлять историей коммитов и избегать неожиданных конфликтов.
Ключевые моменты:
- git pull использует настройки отслеживаемых веток, поэтому важно правильно настраивать upstream.
- Существует несколько режимов работы: merge, rebase и fast-forward only, и их можно задать через флаги или конфиг.
- Конфликты при git pull — нормальная часть разработки, главное уметь их последовательно и аккуратно решать.
- В сложных сценариях лучше разделять fetch и merge, чтобы сначала увидеть изменения, а затем осознанно их влить.
Используя описанные приёмы и настройки, вы сможете сделать git pull предсказуемым инструментом, который работает именно так, как вы ожидаете, и помогает держать локальный репозиторий в актуальном и чистом состоянии.
Частозадаваемые технические вопросы по теме
1. Как запретить git pull создавать merge-коммиты вообще
Если вы хотите, чтобы git pull никогда не создавал merge-коммитов, а либо делал fast-forward, либо завершался с ошибкой, настройте:
git config --global pull.ff only
// Теперь git pull будет работать как git pull --ff-only
// Если fast-forward невозможен, вы увидите ошибку и сможете сами решить, что делать дальше
2. Как сделать, чтобы только одна конкретная ветка использовала rebase при pull
Допустим, вы хотите, чтобы main работала с rebase, а остальные ветки — нет. Сделайте так:
git checkout main
git config branch.main.rebase true
// Для ветки main включён rebase при pull
// Для всех остальных поведение останется по умолчанию
3. Почему после git pull origin main я вижу сообщение You have divergent branches
Это значит, что локальная ветка и origin/main разошлись: у каждой есть свои уникальные коммиты. Git не может автоматически решить, использовать merge, rebase или прервать. Чтобы исправить:
# Явно выбираете стратегию
git pull --rebase origin main
# или
git pull --merge origin main
# или настраиваете поведение в конфиге, как предлагает сообщение об ошибке
4. Как обновить локальную ветку так, чтобы она полностью совпала с origin, игнорируя локальные изменения
Если вы уверены, что локальные коммиты в ветке можно потерять, сделайте:
git fetch origin
git checkout main
git reset --hard origin/main
// Локальная ветка main будет точно соответствовать origin/main
// Все локальные неотправленные коммиты в main пропадут
5. Что делать, если после git pull я хочу вернуться к состоянию до него, но уже успел закоммитить merge
Если вы уже создали merge-коммит, поможет git reflog:
git reflog
// Найдите запись с состоянием до git pull, например HEAD@{2}
Затем:
git reset --hard HEAD@{2}
// Вернёт ветку к выбранному состоянию до pull
// Будьте осторожны - локальные изменения после этого шага будут потеряны
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

Основы Git
Антон Ларичев
TypeScript с нуля
Антон Ларичев