Локально Git самодостаточен, но команда синхронизируется через удалённый репозиторий — общую копию проекта на сервере (GitHub, GitLab, Bitbucket). Три команды — push, pull, fetch — весь трафик между вами и сервером. Здесь же живут два главных страха: «push отклонён» и «я затёр чужую работу». Разберём так, чтобы страхов не осталось.
Коллега запушил коммит: на сервере он есть, а в вашей копии origin/main его нет — Git сам в сеть не ходит, и копия устаревает. Поэтому push и отклонён: вы просите сервер заменить его историю вашей, а в вашей этого коммита нет. fetch обновляет только копию и файлы не трогает; pull доводит чужой коммит и до вашей ветки — после этого push проходит.
origin и отслеживаемые ветки
При git clone адрес сервера сохраняется под именем origin — это просто псевдоним URL (серверов может быть и несколько, но обычно один). Локальная ветка отслеживает (tracks) одноимённую серверную: main знает про origin/main, и git status сообщает расхождение: «ваша ветка отстаёт на 2 коммита».
Важная деталь: origin/main — это локальная копия серверного состояния, снятая при последней синхронизации. Между синхронизациями она устаревает — Git не ходит в сеть сам.
Из этого следует ловушка git status: расхождение он считает по этой самой копии. Если коллега запушил час назад, а вы с тех пор не делали fetch, status честно пишет «ваша ветка актуальна», потому что сравнивает с устаревшим origin/main. Поэтому перед выводами о состоянии ветки сначала git fetch, и только потом git status.
git push -u origin fix-cart # первый push новой ветки: -u связывает её с серверной
git push # дальше просто push
Откуда origin берётся, если репозиторий не клонировали, а создали через git init: его добавляют руками, взяв адрес со страницы пустого репозитория на сервере.
git remote -v # какие серверы известны и по каким адресам
git remote add origin git@github.com:team/shop.git # назвать сервер origin
git push -u origin main # отправить первую ветку и связать её
Адрес бывает двух видов, https://… и git@…, и это выбор способа доступа: по HTTPS сервер спросит персональный токен вместо пароля, по SSH пустит по ключу; как их завести, разобрано в первой статье раздела. У сервера может быть и второе имя. Когда вы правите чужой проект, у вас его копия (fork) под своим аккаунтом: origin смотрит на копию, а исходный репозиторий добавляют как upstream и время от времени подтягивают из него: git fetch upstream && git rebase upstream/main. Pull request при этом открывают из ветки вашей копии в исходный репозиторий.
fetch и pull: посмотреть или забратьспросят на собеседовании
git fetch— скачать новое с сервера, не трогая ваши файлы: обновляются только копииorigin/*. После этого можно спокойно посмотреть, что принесли коллеги:git log main..origin/main.git pull= fetch + слияние отслеживаемой ветки (дляmainэтоorigin/main) в вашу. Файлы обновляются сразу.
Pull — повседневность, fetch — аккуратность: когда хочется сначала увидеть чужие изменения, а потом решать.
Отдельная привычка — git pull --rebase (или pull.rebase=true в конфиге): ваши локальные коммиты переносятся поверх свежих серверных вместо создания мелких merge-коммитов «Merge branch main», и история заметно чище. Почему это работает — в статье про rebase.
«Push отклонён»: что происходит и что делатьспросят на собеседовании
! [rejected] main -> main (fetch first)
Это не поломка, а защита: на сервере есть коммиты, которых нет у вас (коллега успел запушить). Git отказывается затирать их вашей версией истории. Лечение штатное:
git pull --rebase # забрать чужое и переставить свои коммиты поверх
git push # теперь уходит
Флаг тут не украшение. Начиная с версии 2.34 голый git pull в этой ситуации ничего не сливает, а останавливается: fatal: Need to specify how to reconcile divergent branches. Git отказывается угадывать, чего вы хотите — слияния или переноса, — и ждёт прямого ответа. Отвечать можно каждый раз флагом (--rebase либо --no-rebase) или один раз на все проекты:
git config --global pull.rebase true # pull всегда переносит ваши коммиты поверх
Конфликт при этом возможен и разбирается как обычно.
git push --force в общую ветку перезаписывает серверную историю вашей: коммиты коллег, которых у вас нет, исчезают с сервера, и остаются только в их локальных копиях, если остаются. Поэтому в общую ветку так не делают, а в main/develop force-push обычно запрещён настройками сервера, и это правильно. Настройка называется защитой ветки (branch protection): владелец репозитория включает её для main, и там же живут остальные правила, на которые опирается процесс с pull request: запрет прямого push, обязательные проверки конвейера, обязательное одобрение одного или двух ревьюеров, требование линейной истории или подписанных коммитов. Легитимен он в одном случае: ваша личная ветка, историю которой вы сознательно переписали перед ревью, и лучше в форме --force-with-lease: она сверяет серверную ветку с вашей копией origin/* и отказывается перетирать чужое, которого вы ещё не видели. У этой сверки есть слабое место, разобранное в статье про rebase.
В нижнем ряду коммита коллеги 9f07 уже нет: force-push заменил серверную ленту вашей, и вернуть чужую работу можно только с его машины.
Ежедневный цикл
Типичный день с удалённым репозиторием:
git switch main && git pull # утро: свежий main
git switch -c feature-export # ветка под задачу
# ...работа, коммиты...
git push -u origin feature-export # отправить ветку
# дальше — pull request, ревью и слияние на сервере
git switch main && git pull # после вливания: обновиться
git branch -d feature-export # прибрать ветку
Одна неожиданность ждёт на последней строке. Если ветку слили через squash — а это самый ходовой способ — Git не считает её влитой: в main уехал один новый коммит, а ваших в нём формально нет. git branch -d ответит error: the branch 'feature-export' is not fully merged. Это не поломка, а честный вопрос «точно удалять?». Содержимое уже в main, так что отвечать надо заглавной буквой: git branch -D feature-export.
Ветка при этом удалена только у вас: на сервере origin/feature-export жива, пока её не удалили там, и копия origin/feature-export в вашем клоне тоже. Серверную ветку обычно удаляет кнопка при слиянии pull request, руками это git push origin --delete feature-export. А устаревшие копии origin/* веток, которых на сервере уже нет, Git сам не чистит: через месяц git branch -a показывает десятки мёртвых имён. Убирает их git fetch -p (prune), а чтобы не помнить об этом, включают git config --global fetch.prune true.
Единственная привычка, которая делает всё это гладким: начинать от свежего main и пушить ветку рано, не копя неделю локальных коммитов. Ранний push — это ещё и бэкап: сгоревший ноутбук не уносит работу.
Коротко
- origin — псевдоним сервера;
origin/main— локальный снимок серверного состояния, устаревает между синхронизациями. - fetch скачивает не трогая файлы, pull = fetch + слияние.
pull --rebaseдержит историю чистой. - «Push rejected» = на сервере есть новое:
pull --rebase, затем push. Это защита, а не ошибка; голыйpullна разошедшихся ветках Git делать откажется. - Force-push — только в свою ветку и лучше
--force-with-lease; в общие ветки — никогда. - Ритм: от свежего main, ветка на задачу, ранний push.
git statusсравнивает с устаревшей копиейorigin/mainи может писать «актуально», когда на сервере уже есть новое: сначалаfetch.- Сервер добавляют
git remote add origin <адрес>(SSH или HTTPS с токеном); чужой проект правят через fork сoriginиupstream; правилаmain(запрет push, проверки, одобрения) это защита ветки. - Серверную ветку удаляет
git push origin --delete, мёртвые копииorigin/*чиститgit fetch -pилиfetch.prune true.
Что почитать дальше
- Pull request и код-ревью — что происходит с веткой на сервере.
- Ветки и слияние — предыдущая тема цикла.
- Отмена и спасение — если что-то всё-таки пошло не так.
- Модели ветвления — как команда договаривается, куда и когда пушить.