Как удалить Git-ветку локально и на удалённом репозитории (пояснение концепций branch, remote, prune)

Очистка веток Git с командной строкой Вы наверняка бывали в этой ситуации. Вы закончили фичу, замержили всё в main, и теперь хотите навести порядок. Вы гуглите «как удалить ветку git», копируете команду со StackOverflow, вводите её... а через день, после очередного git pull, эта проклятая ветка снова маячит в списке. Или Git ругается, что вы что-то не домержили, хотя вы точно знаете, что код уже на продакшене.

Сегодня мы перестанем слепо копировать команды. Мы разберемся, что такое ветка на самом деле, чем локальная копия отличается от трекинга, и научимся хирургически точно удалять их — локально, на сервере, а также в кэше.

Проблема на миллион (со StackOverflow)

Самый популярный вопрос на StackOverflow по теме Git имеет тысячи апвоутов. Люди пытаются выполнить что-то вроде:

git branch -d remotes/origin/feature-super

И получают ошибку. Или удаляют ветку через веб-интерфейс, а в терминале она всё ещё видна. Проблема не в командах, а в ментальной модели. Git — это распределенная система, и «удаление» в одном месте не означает автоматического исчезновения в другом.

Концептуальные основы: Три головы дракона

Чтобы понять, как удалять, нужно понять, что именно мы удаляем. В Git ветка — это не контейнер для кода. Это просто глупый подвижный указатель с названием, который приклеен к конкретному коммиту (хешу).

Когда вы работаете с удаленным репозиторием (например, на GitLab или GitHub), у одной логической ветки feature-super на самом деле есть три воплощения. Если вы удалите одно, два других останутся.

1. Локальная ветка (refs/heads/feature-super)

Это ваш личный указатель в вашем ноутбуке. Вы видите её, когда пишете git branch. Она живет (физически) в файле .git/refs/heads/feature-super. Если вы откроете этот файл, там будет просто 40 символов хеша коммита. Удалить локальную ветку — значит просто удалить этот файл. Код (блобы и деревья) при этом никуда не девается (пока его не съест сборщик мусора, но это другая история).

2. Удалённая ветка (refs/heads/feature-super на сервере)

Это указатель, который живет на сервере GitHub/GitLab. Вы не можете добраться до него напрямую командами вроде rm. Вы можете только попросить сервер: «Эй, удали у себя этот указатель».

3. Remote-tracking ветка (refs/remotes/origin/feature-super)

Вот здесь происходит вся магия и путаница. Это локальная копия (кэш) того, в каком состоянии была ветка на сервере в момент вашего последнего контакта с ним (fetch, pull, push). Это «призрак» удаленной ветки. Вы не можете на неё переключиться (checkout), она только для чтения. Она нужна, чтобы Git мог сказать вам: «Эй, ты отстал от сервера на 2 коммита».


Операция 1: Удаляем локальную ветку

Это самое простое. Вы просто удаляете указатель со своего ноутбука.

Безопасное удаление: -d

git branch -d feature-super

Git проверит, была ли эта ветка замержена в ту, на которой сейчас находитесь, или в ее апстрим. Если нет — он не даст удалить, защищая вас от потери работы.

Форсированное удаление: -D

git branch -D feature-super

Это шорткат для --delete --force. Git плевать хотел на мержи. Он просто удаляет файл .git/refs/heads/feature-super. Используйте, если эксперимент не удался и код вам не нужен.

Важно: Вы не можете удалить ветку, на которой сейчас стоите. Сначала переключитесь (git checkout main), потом удаляйте.


Операция 2: Удаляем ветку на сервере

Вы удалили локальную ветку. Но на GitHub она всё ещё висит. Ваши коллеги её видят. Чтобы удалить её на сервере, нужно отправить («спушить») команду удаления.

Современный способ (Git 1.7.0+):

git push origin --delete feature-super

Читается как: «В удаленном репозитории origin выполни флаг --delete для имени feature-super». После этого указатель на сервере исчезает.

Олдскульный способ (для понимания механики):

git push origin :feature-super

Заметьте двоеточие. Синтаксис git push обычно такой: локальная_ветка:удаленная_ветка. Если мы пишем git push origin master:master, мы пушим локальный мастер в удаленный. Если мы пишем git push origin :feature-super, мы пушим пустоту в удаленную ветку. Пустота перезаписывает ветку, и она исчезает.


Операция 3: Зачищаем призраков (Remote-tracking)

Вот вы удалили ветку на сервере (через Pull Request интерфейс или консоль). Вы вводите git branch -a и видите:

  main
  remotes/origin/main
  remotes/origin/feature-super  <-- ОНА ЕЩЁ ТУТ!

Почему? Потому что ваш локальный Git не знает, что вы что-то нажали в браузере на GitHub. У него лежит устаревший кэш в .git/refs/remotes/origin/.

Чтобы очистить этот список, нужно синхронизироваться с удалением.

git fetch --prune

Или настроить Git делать это автоматически при каждом фетче (рекомендую):

git config --global fetch.prune true

Флаг --prune говорит: «Сходи на сервер, посмотри, какие ветки там исчезли, и удали соответствующие remote-tracking ветки у меня локально».


Типичные ошибки и почему «оно само вернулось»

Сценарий «Зомби-ветка»

  1. Алиса удаляет ветку feature-X на сервере.
  2. Боб не сделал git fetch --prune. У него всё еще есть локальная origin/feature-X (призрак).
  3. Боб случайно делает git push (или использует GUI, который пушит 'All branches').
  4. Git Боба говорит серверу: «Слушай, у меня тут есть инфа про feature-X, держи её».
  5. Ветка воскресает на сервере.

Лечение: Все в команде должны использовать git fetch --prune.

Ошибка: «error: unable to push to unqualified destination»

Бывает, когда у вас есть ветка, а также тег с одинаковым именем (никогда так не делайте, но если уж случилось). Git не понимает, что именно удалять.

Нужно указывать полный путь к рефу:

git push origin --delete refs/heads/feature-super

Практические сценарии "Бери и делай"

Давайте соберем всё вместе. Вот три самых частых кейса.

Сценарий 1: Я закончил задачу, всё вмёржено

Вы успешно закрыли Pull Request. Ветка больше не нужна нигде.

  1. На сервере: (обычно делает сам GitHub/GitLab галочкой "Delete branch after merge"). Если нет: git push origin --delete feature-super

  2. Локально: Переключаемся на main, подтягиваем обновления и удаляем свою копию.

    git checkout main
    git pull
    git branch -d feature-super
    git fetch --prune  # На случай, если кто-то другой уже удалил её на сервере
    

Сценарий 2: Я наговнокодил и хочу всё забыть

Код не нужен, на сервер ничего не пушилось (или пушилось, но там тоже надо сжечь).

  1. Локально:

    git checkout main
    git branch -D experiments-gone-wrong
    
  2. Если успели запушить:

    git push origin --delete experiments-gone-wrong
    

Сценарий 3: Генеральная уборка (Spring Cleaning)

Вы открываете git branch -a, а там кладбище из веток 2021 года.

  1. Удаляем всех «призраков» (веток, которых уже нет на сервере, но они висят у вас):

    git fetch --prune
    
  2. Теперь удаляем локальные ветки, которые уже cмержены в main: (Осторожно, это магия Bash)

    git checkout main
    git pull
    git branch --merged | grep -v "\*" | xargs -n 1 git branch -d
    

    Разбор: git branch --merged показывает ветки, влитые в текущую. grep -v "\*" исключает текущую ветку (main). xargs удаляет их по одной.

Заключение

Git не сложен, он просто очень буквален. Когда вы хотите «удалить ветку», всегда спрашивайте себя:

  1. Удаляю ли я свой локальный указатель? (branch -d)
  2. Прошу ли я сервер удалить его указатель? (push --delete)
  3. Очищаю ли я свой кэш о состоянии сервера? (fetch --prune)