Tracking branches: связь локального с remote
Когда git status пишет “Your branch is ahead of origin/main by 3 commits” — он не угадывает. У твоей локальной main явно прописан upstream — ветка на remote, с которой её надо сравнивать. Это называется tracking relationship.
Без понимания tracking невозможно разобраться, почему git push иногда требует -u, почему git pull без аргументов знает, что pull-ить, и почему git status показывает “ahead/behind” не для всех веток.
Зачем нужен tracking
Tracking — это сохранённая связь “локальная ветка X следит за remote-веткой Y”. Сохраняется она в .git/config:
[branch "main"]
remote = origin
merge = refs/heads/main
Эта запись говорит Git: для ветки main upstream — это main на remote origin. Когда ты делаешь git pull на main, Git идёт в эту запись и понимает: fetch из origin, merge origin/main.
git branch -vv: посмотри все tracking
Лучшая команда для обзора tracking — git branch -vv (два v для verbose+verbose):
$ git branch -vv
* main f4e5d6c [origin/main: ahead 3] feat: add spark job
develop a1b2c3d [origin/develop] update readme
feature/spike 9876543 wip: experimenting
feature/bigquery abc1234 [origin/feature/bigquery: behind 2] add gcp creds
Читай слева направо:
*— текущая ветка.- Имя ветки, hash верхнего коммита.
- В квадратных скобках — upstream и его состояние:
[origin/main: ahead 3]— твояmainна 3 коммита впередиorigin/main.[origin/develop]— нет diverge, всё синхронно.[origin/feature/bigquery: behind 2]— на 2 коммита отстаёт от сервера.
- Без скобок (как
feature/spike) — нет upstream. Это локальная ветка, не привязанная ни к чему.
git branch -vv — твой основной инструмент диагностики. Если ты не понимаешь, почему git pull не работает на ветке — первым делом посмотри сюда.
Откуда берётся upstream
Tracking появляется в трёх сценариях:
1. Автоматически при git clone
После clone локальная ветка (обычно main) автоматически трекает origin/main:
$ git clone [email protected]:acme/repo.git
$ cd repo
$ git branch -vv
* main f4e5d6c [origin/main] init commit
Все остальные ветки на сервере НЕ становятся локальными — они доступны как origin/develop, origin/feature-x. Это remote-tracking branches, но НЕ локальные.
2. При git switch <branch> для existing remote ветки
Если на сервере есть develop, и ты делаешь git switch develop, Git автоматически:
- Создаёт локальную
developизorigin/develop. - Настраивает её как трекающую
origin/develop.
$ git switch develop
branch 'develop' set up to track 'origin/develop'.
Switched to a new branch 'develop'
Это поведение управляется настройкой branch.autoSetupMerge (по умолчанию true).
3. При git push -u origin <branch>
Когда ты создал локальную ветку и пушишь её первый раз с -u:
$ git switch -c feature/x
$ git push -u origin feature/x
* [new branch] feature/x -> feature/x
branch 'feature/x' set up to track 'origin/feature/x' from 'origin'.
Флаг -u (--set-upstream) после push настраивает tracking. Без него ты бы создал ветку на сервере, но локальная feature/x не знала бы своего upstream.
Управлять tracking руками
Установить upstream для существующей ветки
Сценарий: создал локальную ветку, поработал, потом понял, что на сервере уже есть аналогичная ветка от коллеги. Хочешь её трекать.
git branch --set-upstream-to=origin/feature/x feature/x
# или короче, если ты на этой ветке
git branch --set-upstream-to=origin/feature/x
git branch -u origin/feature/x # совсем коротко
Убрать tracking
git branch --unset-upstream feature/x
После этого git branch -vv покажет ветку без квадратных скобок. git pull/git push без аргументов перестанут работать.
Переключить tracking на другой remote
git branch -u upstream/main main
Полезно при работе с fork: твоя локальная main отслеживает upstream/main (оригинал), а не origin/main (твой fork).
Что значит “ahead” и “behind”
Когда git status пишет:
Your branch is ahead of 'origin/main' by 3 commits.
Your branch is behind 'origin/main' by 5 commits.
Your branch and 'origin/main' have diverged, and have 3 and 5 different commits each.
Это вычисляется так:
То есть:
- ahead N = N коммитов в твоей ветке, которых нет в upstream.
- behind N = N коммитов в upstream, которых нет у тебя.
Если оба числа ненулевые — diverged. Это значит, что ни fast-forward push, ни fast-forward pull невозможны. Нужен merge или rebase.
Точно так же это можно посчитать руками:
$ git rev-list --left-right --count main...origin/main
3 5
3 5 — слева ahead (твоя сторона), справа behind.
push.default: куда именно пушить
Когда ты пишешь git push без аргументов, какая локальная ветка отправляется в какую remote-ветку? Это контролируется настройкой push.default.
Современный дефолт (Git 2.0+) — simple:
git config --global push.default simple
Поведение simple:
git pushотправляет текущую ветку в её upstream с тем же именем.- Если имена локальной и upstream не совпадают — ошибка.
- Если нет upstream — ошибка с подсказкой
--set-upstream.
Это безопасно и предсказуемо. Не используй matching (старый дефолт) — он пушит все ветки с совпадающими именами, что иногда приводит к “ой, я случайно запушил три ветки”.
push.autoSetupRemote
Полезная настройка с Git 2.37+:
git config --global push.autoSetupRemote true
С ней git push для новой локальной ветки автоматически настраивает upstream — без флага -u. Удобно: один меньше шаг для запоминания.
branch.autoSetupMerge и branch.autoSetupRebase
При создании ветки от remote (git switch develop или git switch -c x origin/main) Git настраивает её для tracking. Контролируется через:
# По умолчанию: настраивать tracking при ветвлении ОТ remote-tracking
git config --global branch.autoSetupMerge true
# Чтобы pull всегда был rebase
git config --global branch.autoSetupRebase always
autoSetupRebase = always интересный: при создании любой новой ветки она будет настроена для pull --rebase. Полезно для команд с rebase-first culture.
Типичные диагностические сценарии
Сценарий 1: “Why does git push ask for upstream every time?”
Признак — каждый push требует -u.
$ git push
fatal: The current branch feature/x has no upstream branch.
Решение: либо запушить с -u, либо настроить push.autoSetupRemote = true.
git push -u origin feature/x
# или раз и навсегда:
git config --global push.autoSetupRemote true
Сценарий 2: “Why does git pull say ‘no tracking information’?”
$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
Ветка локальная, у неё нет upstream. Решение:
# Если на сервере уже есть аналогичная ветка
git branch -u origin/feature/x
# Если ещё нет — запушить
git push -u origin feature/x
Сценарий 3: “Why is my push rejected with non-fast-forward?”
$ git push
! [rejected] main -> main (non-fast-forward)
Твоя локальная main и origin/main разошлись. Скорее всего, коллега напушил, а ты сделал коммиты локально. Решение:
git fetch
git rebase origin/main # или git merge origin/main
git push
Никогда не “решай” проблему через git push --force без проверки. Это перезапишет коммиты коллеги. Сначала разберись, что ты конфликтуешь, и сделай явный rebase или merge.
Локальная ветка без upstream — это нормально?
Да. Не всякая локальная ветка обязана иметь upstream:
- Экспериментальные ветки — пока не уверен, что будешь пушить.
- Хранилище WIP — личные ветки для черновиков.
detached HEADwork — переключение на коммит без создания ветки.
Признак отсутствия upstream — пустые скобки в git branch -vv:
$ git branch -vv
feature/spike 9876543 wip experiment
Если потом захочешь запушить — git push -u origin feature/spike это исправит.
Попробуй сам
# 1. Клонируй любой публичный репо
git clone [email protected]:rails/rails.git
cd rails
# 2. Посмотри tracking
git branch -vv
# main отслеживает origin/main
# 3. Создай локальную ветку без upstream
git switch -c my-experiment
# 4. Посмотри: нет upstream
git branch -vv | grep my-experiment
# 5. Попробуй push — получишь подсказку про -u
git push
# fatal: The current branch my-experiment has no upstream branch.
# 6. Не пуша, а просто настрой tracking на main как пример
git branch -u origin/main my-experiment
git branch -vv
# 7. Теперь git status покажет diverge (вероятно)
git status
Feature-branch workflow в dbt: push и pull-request