Локальный репозиторий - это вся история проекта у тебя на диске. Он самодостаточен: коммить, ветви, ходи по истории без интернета. Но как только над проектом работает больше одного человека (или хотя бы ты с двух машин), нужна общая точка, куда сходятся изменения. Эта точка - удалённый репозиторий git: ровно такой же репозиторий, как твой, только живёт он на сервере (GitHub, GitLab, Gitea, голый сервер по SSH) и доступен по сети.
Главная боль, которую решают remote-команды: как обмениваться коммитами между репозиториями не теряя контроль. Новичок часто живёт в режиме "нажал кнопку - что-то синхронизировалось", а потом ловит конфликты, перезатёртые чужие коммиты и панику. После этого урока ты будешь понимать, что именно скачивается, куда оно ложится и почему git fetch безопасен, а git pull - уже нет.
Заранее договоримся о легенде графа: кружок-узел - это коммит, стрелка идёт от коммита к его родителю, а ярлык вроде main, HEAD или origin/main - это указатель (ref), который просто показывает на конкретный коммит.

Что такое remote и откуда берётся origin
Remote - это именованный URL другого репозитория. Никакой магии: Git хранит короткое имя (например, origin) и адрес, по которому к этому репозиторию ходить. Имя нужно, чтобы не писать длинный URL руками каждый раз.
Когда ты делаешь git clone, Git автоматически заводит remote с именем origin - это просто соглашение, "место, откуда я клонировал". Имя origin ничем не священно, его можно переименовать или удалить, но трогать без нужды не стоит: на него завязаны скрипты, привычки и документация.
Посмотрим, что есть:
Код: Выделить всё
$ git remote -v
origin git@github.com:acme/webapp.git (fetch)
origin git@github.com:acme/webapp.git (push)
Базовый набор команд для управления remote:
Код: Выделить всё
# добавить новый remote по имени
$ git remote add upstream git@github.com:original/webapp.git
# подробности про конкретный remote
$ git remote show origin
# переименовать
$ git remote rename origin gh
# удалить
$ git remote remove upstream
# сменить адрес (например, переехали с https на ssh)
$ git remote set-url origin git@github.com:acme/webapp.git
Разберём вывод git remote show origin - он недооценён, а пользы много:
Код: Выделить всё
$ git remote show origin
* remote origin
Fetch URL: git@github.com:acme/webapp.git
Push URL: git@github.com:acme/webapp.git
HEAD branch: main
Remote branches:
main tracked
feature/auth tracked
stale/old stale (use 'git remote prune' to remove)
Local branch configured for 'git pull':
main merges with remote main
Local ref configured for 'git push':
main pushes to main (up to date)
git fetch против git pull - ключевое различие
Это сердце урока, запомни намертво.
git fetch - скачивает из remote новые коммиты и обновляет remote-tracking ссылки (вроде origin/main), но не трогает твои рабочие ветки и рабочий каталог. После fetch твоя main осталась там же, где была. Ты просто узнал, что есть нового на сервере. Поэтому fetch абсолютно безопасен: его можно делать сколько угодно раз, он ничего не ломает и не порождает конфликтов.
Код: Выделить всё
$ git fetch origin
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
Unpacking objects: 100% (8/8), done.
From github.com:acme/webapp
a1b2c3d..f4e5d6c main -> origin/main
* [new branch] feature/pay -> origin/feature/pay
Код: Выделить всё
a1b2c3d..f4e5d6c main -> origin/maingit pull - это не отдельная операция, а две подряд:
Код: Выделить всё
git fetchКод: Выделить всё
git mergeКод: Выделить всё
git rebaseФормула простая:
Код: Выделить всё
git pull = git fetch + git merge origin/<ветка>
Код: Выделить всё
git log --oneline main..origin/mainБазовый ежедневный цикл: clone, commit, push, pull
Теперь собёрем рутину одиночки и небольшой команды. Это тот минимум, который ты будешь делать каждый день.
Код: Выделить всё
# 1. один раз: получить проект
$ git clone git@github.com:acme/webapp.git
$ cd webapp
# 2. работаешь: правишь файлы, потом фиксируешь
$ git add src/login.php
$ git commit -m "Fix login redirect"
# 3. отправляешь своё на сервер
$ git push
# 4. перед началом нового куска работы - забираешь чужое
$ git pull
Код: Выделить всё
$ git push
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:acme/webapp.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
Код: Выделить всё
--forceОтслеживаемые ветки, upstream и origin/main
Откуда git pull без аргументов знает, что и откуда тянуть? За это отвечает tracking branch (отслеживаемая ветка) и её upstream.
Сначала про природу origin/main. Это remote-tracking ref - локальный снимок того, где была ветка main на remote в момент последнего fetch. Он лежит в
Код: Выделить всё
.git/refs/remotes/origin/mainКод: Выделить всё
.git/refs/heads/mainСвязь "локальная main -> следит за origin/main" называется upstream. Благодаря ей работают
Код: Выделить всё
git pullКод: Выделить всё
git pushКод: Выделить всё
$ git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
Код: Выделить всё
# задать upstream существующей ветке
$ git branch -u origin/main
# или с явными именами
$ git branch --set-upstream-to=origin/feature/pay feature/pay
# задать upstream прямо при первом push
$ git push -u origin feature/pay
Код: Выделить всё
--set-upstreamКод: Выделить всё
-uКод: Выделить всё
$ git config --global push.autoSetupRemote true
Код: Выделить всё
git pushgit fetch --prune - чистка устаревших remote-веток
Со временем ветки на сервере удаляют (смёржили фичу - стёрли feature/pay). Но твой локальный origin/feature/pay сам собой не исчезнет: fetch добавляет новое, а удалённое не убирает. Накапливается мусор из мёртвых remote-tracking ссылок - именно их git remote show помечал как stale.
Чистим:
Код: Выделить всё
$ git fetch --prune
From github.com:acme/webapp
- [deleted] (none) -> origin/feature/pay
- [deleted] (none) -> origin/stale/old
Код: Выделить всё
[deleted] (none) -> origin/feature/payКод: Выделить всё
git branch -dЧтобы не помнить про флаг, включи prune навсегда:
Код: Выделить всё
$ git config --global fetch.prune true
Код: Выделить всё
git fetchКод: Выделить всё
git pullЧто такое refspec - механика под капотом
Мы говорили "fetch обновляет origin/main". Откуда Git знает это соответствие "ветка на сервере -> мой локальный ref"? Из refspec. Refspec - это правило отображения ссылок в формате:
Код: Выделить всё
[+]<src>:<dst>
Загляни в
Код: Выделить всё
.git/configКод: Выделить всё
[remote "origin"]
url = git@github.com:acme/webapp.git
fetch = +refs/heads/*:refs/remotes/origin/*
Код: Выделить всё
+refs/heads/*:refs/remotes/origin/*Refspec можно задать и в командной строке. Скачать только одну ветку в свой ref:
Код: Выделить всё
$ git fetch origin +refs/heads/main:refs/remotes/origin/main
Код: Выделить всё
git push origin main:productionКод: Выделить всё
git push origin :feature/payПонимание refspec снимает кучу "магии": fetch, pull, push, prune - всё это операции над ссылками по правилам refspec.
git ls-remote - заглянуть на сервер не скачивая
Иногда нужно узнать, что есть на сервере, вообще ничего не скачивая в репозиторий. Для этого есть лёгкая команда git ls-remote - она просто спрашивает у сервера список его ссылок:
Код: Выделить всё
$ git ls-remote origin
f4e5d6c7... HEAD
f4e5d6c7... refs/heads/main
9a8b7c6d... refs/heads/feature/auth
1122aabb... refs/tags/v2.3.0
Код: Выделить всё
git ls-remote git@host:repo.gitТипичные грабли
- Думать, что fetch обновил рабочую ветку. Нет. После fetch твоя main на месте, новое лежит в origin/main. Чтобы влить - или
Код: Выделить всё
git merge origin/main.Код: Выделить всё
git pull - Пытаться двигать origin/main руками. Это не твоя ветка, а память о сервере. Хочешь догнать сервер - или
Код: Выделить всё
git pullна своей ветке, а не правка ref напрямую.Код: Выделить всё
git reset --hard origin/main - Не настроить upstream у новой ветки. Тогда голый ругается "no upstream". Лечится
Код: Выделить всё
git pushили опцией push.autoSetupRemote.Код: Выделить всё
git push -u - Удивляться "мёртвым" веткам в автодополнении. Это отсутствие prune. Включи fetch.prune.
- Лечить "rejected (fetch first)" через --force. Так затирают чужие коммиты. Правильный путь - , разрулить, потом push. Если переписывал историю осознанно -
Код: Выделить всё
git pull, а не голыйКод: Выделить всё
--force-with-lease.Код: Выделить всё
--force - Путать локальную ветку и одноимённый origin/<имя>. и
Код: Выделить всё
git branch -d featureтрогают разные вещи.Код: Выделить всё
git fetch --prune
Сделаем "сервер" локально, без интернета, чтобы увидеть механику вживую.
Код: Выделить всё
# 1. создаём голый репозиторий - он сыграет роль сервера
$ git init --bare /tmp/server.git
# 2. клонируем его в рабочую копию
$ git clone /tmp/server.git /tmp/work && cd /tmp/work
$ git config init.defaultBranch main # в 2026 ваниль ещё создаёт master
# 3. первый коммит и push
$ echo "hello" > readme.txt
$ git add readme.txt && git commit -m "init"
$ git branch -M main
$ git push -u origin main
# 4. смотрим remote и refspec
$ git remote -v
$ git config --get remote.origin.fetch
# 5. имитируем второго разработчика: второй клон
$ git clone /tmp/server.git /tmp/work2 && cd /tmp/work2
$ echo "from dev2" >> readme.txt
$ git commit -am "dev2 change" && git push
# 6. возвращаемся в первую копию и НЕ pull, а fetch
$ cd /tmp/work
$ git fetch
$ git status # ahead/behind относительно origin/main
$ git log --oneline main..origin/main # что приехало, но ещё не влито
# 7. вливаем и проверяем
$ git pull
$ cat readme.txt # появилась строка from dev2
# 8. создаём и удаляем ветку на сервере, чистим prune
$ git push origin main:throwaway # завели ветку throwaway
$ git push origin :throwaway # удалили её
$ git fetch --prune # видим [deleted] origin/throwaway
$ git ls-remote origin # убеждаемся: на сервере её нет
Контрольные вопросы
- Чем git fetch отличается от git pull и почему fetch считают безопасным?
- Что такое origin/main физически, где он лежит и когда обновляется?
- Расшифруй refspec - что значат звёздочки и ведущий плюс?
Код: Выделить всё
+refs/heads/*:refs/remotes/origin/* - Что именно удаляет git fetch --prune, а что он гарантированно не трогает?
Remote - это просто именованный URL чужого репозитория, origin - имя по умолчанию после clone. git fetch безопасно скачивает и обновляет remote-tracking ссылки вроде origin/main, не трогая твою работу; git pull - это fetch плюс merge/rebase, и он уже меняет твою ветку. Ежедневный цикл: clone, правки, commit, push, а перед push - pull. Tracking-ветка через upstream даёт магию pull/push без аргументов, fetch --prune чистит мёртвые ссылки, а refspec вида
Код: Выделить всё
+src:dst