The Jerusalem PostQatari electricity lifeline for Lebanon includes two power plants, LNG supply dealESPN DeportesAmérica: 30 jugadores en 11 partidos y es líderESPNColts fire GM Ballard after home loss to Titans, cite 'necessary' changeRTP DesportoCristiano Ronaldo suspenso 30 dias por abandonar estágio da SeleçãoDaily MaverickDIGITAL POLITICS OP-ED: Borders, ballots, court clashes — how the anti-immigrant movement is setting SA’s elections agendaZDF heuteEntdecken Sie das ZDF-NachrichtenstudioFootball ItaliaYaya Touré: ‘Slovan come to Roma to win, Gasperini was incredible at Atalanta’Business AMLeveringen van Patriot-raketten nemen af terwijl Zelensky om Amerikaanse hulp vraagtWirtualna PolskaPutin chce "nowej Jałty" i zatrzymanie żołnierza podejrzanego o szpiegostwo [SKRÓT DNIA]سكاي نيوز عربيةعلى الملأ.. نتنياهو ينشر فيديو لـ"مكالمة الدعم" مع ترامبThe Independent‘We are the shame of Europe’: France imposes curfews to stop growing riotsCBS SportsArch Manning remains Texas starting QB, but Steve Sarkisian says he needs a 'better version' of Longhorns star
The Daily Newsstand · Free, Always
Monday, October 19, 2026

[Перевод] origin master или origin/master: как Git хранит ветки в обычном словаре «ключ — значение»

Translate

Недавно я заметно поправил свою ментальную модель Git. Возьмём две команды:

$ git fetch origin master
$ git switch -c my-feature origin/master

Понимаете, почему в одной написано origin master, а в другой origin/master? Я — нет, ещё пару недель назад не понимал!

Я представлял Git как контентно-адресуемую базу данных. Git хранит коммиты, коммит определяется хешем своего содержимого, а содержимое коммита — это в основном:

  • снимок состояния кода в определённый момент, без какой-либо памяти о прошлом;

  • список (хешей) родительских коммитов.

Этого мне хватало, чтобы читать вывод git log и выбираться из любого запоротого ребейза, не клонируя репозиторий заново. (Примерно половину карьеры я как раз клонировал его заново. Ничего стыдного! Разобраться в Git полезно, но, когда только начинаешь, это не самое важное, чему стоит учиться.)

Теперь я понимаю, что в Git есть не только неизменяемая база с адресацией по содержимому, куда можно лишь дописывать, но и самое обычное изменяемое хранилище «ключ — значение».

У Git есть изменяемый словарь: ключи в нём — строки, значения — объекты из той самой контентно-адресуемой базы. Ключи по соглашению записываются как пути в файловой системе, и содержимое словаря обычно можно посмотреть, просто заглянув в каталог .git/refs:

$ eza -T .git/refs
.git/refs
├── heads
│   ├── make
│   ├── master
│   ├── my-feature
│   └── pbd-adt
├── origin
├── remotes
│   └── origin
│       ├── context-switches
│       ├── gh-pages
│       ├── HEAD
│       ├── make
│       └── master
└── tags

$ cat .git/refs/heads/master
b59148228e52f7c615ead7fdd4e91001994ad50f

$ git show-ref refs/heads/master
b59148228e52f7c615ead7fdd4e91001994ad50f refs/heads/master

Запутывает в этой инфраструктуре ссылок (refs) вот что:

  • На ней держится множество разных видимых пользователю возможностей Git, но сами ссылки — деталь реализации.

  • Абстракция немного протекает: в повседневной работе ссылки почти не видны.

  • В командах Git полно сокращённых записей ссылок и аргументов по умолчанию, поэтому далеко не очевидно, что конкретный аргумент — это ссылка.

  • И, как обычно, Git любит давать одной вещи несколько имён и называть одним именем разные вещи.

Ветки, теги и git notes — всё это просто ссылки!

Структура становится куда понятнее, если развернуть все сокращения. Исходная команда

$ git fetch origin master

превращается в

$ git fetch \
    https://github.com/matklad/matklad.github.io \
    refs/heads/master:refs/remotes/origin/master

Первый аргумент fetch (https://...) — адрес удалённого репозитория. Git «звонит» по этому адресу и перекачивает с той машины часть данных по сети.

Второй аргумент — пара строковых ключей (ссылок) вида источник:цель. Источник — ключ в удалённом репозитории, цель — имя локального ключа. В целом fetch просит Git прочитать значение из удалённого репозитория и сохранить его у себя под другим именем.

Чтобы не набирать каждый раз URL репозитория, Git даёт им символические имена, и origin — общепринятое имя для основного удалённого репозитория:

$ git fetch origin \
    refs/heads/master:refs/remotes/origin/master

refs/heads/master — полное имя ветки в удалённом репозитории. То есть ветка my-feature — это всего лишь ссылка refs/heads/my-feature. Её могли бы назвать refs/branch/my-feature, но не назвали :)

Точных правил сокращения я не знаю, но в целом Git позволяет писать только хвост имени ссылки:

$ git fetch origin \
    master:refs/remotes/origin/master

refs/remotes/origin/master — имя локальной ссылки, куда мы сохраним результат. Казалось бы, естественнее взять локально то же имя, что и в удалённом репозитории, но это работает, только пока удалённый репозиторий один. Если их два (например, ваш форк и оригинальный репозиторий, от которого вы форкнулись), имена ссылок столкнутся. Поэтому ссылки удалённого репозитория foo складываются в отдельное пространство имён refs/remotes/foo. А origin — просто общепринятое имя для того самого удалённого репозитория в простых конфигурациях.

Опять же, естественнее было бы напрямую повторить у себя структуру удалённых ссылок:

refs/ heads/my-branch -> refs/ remotes/origin/ heads/my-branch

но Git выбрасывает лишний компонент heads. Это отображение «убрать heads, добавить remotes/$remote» встроено в Git, и оно сжимает команду до

$ git fetch origin master

Прим. перев.: точнее, отображение задаёт не сам Git, а настройка remote.origin.fetch, которую git clone и git remote add записывают по умолчанию: +refs/heads/*:refs/remotes/origin/*. Если её поменять, git fetch origin master обновит другую ссылку.

Стоит задуматься, почему всё устроено именно так. Модель Git рассчитана на работу без сети. Более того, она предполагает явные точки синхронизации. Git не синхронизируется с удалённым репозиторием в фоне, когда есть связь: чтобы байты ушли по проводу, нужно явно выполнить fetch или push. В такой парадигме полезно хранить у себя модель того, в каком состоянии была удалённая сторона, когда мы с ней в последний раз говорили. Теория разума, только для репозиториев!

Надеюсь, это проясняет, чем в Git локальные ветки отличаются от удалённых. Возьмём ветку main. В удалённом репозитории origin она существует как refs/heads/main. Когда вы синхронизируете свой локальный репозиторий с origin, у вас появляется refs/remotes/origin/main — ваше последнее знание о том, в каком состоянии main на origin.

А ещё есть ваша локальная refs/heads/main. Обычно она сначала указывает на тот же коммит, что и refs/remotes/origin/main. Но когда вы делаете коммит, refs/heads/main сдвигается вперёд, а refs/remotes/origin/main остаётся на месте.

Когда вы пытаетесь запушить свой коммит в origin, а ветка main на origin за это время ушла вперёд, вы получите конфликт. Тогда Git сам обновит refs/remotes/origin/main (это ведь всего лишь локальное зеркало удалённого состояния), а вот обновить refs/heads/main и запушить снова — уже ваша забота.

Прим. перев.: здесь автор неточен. Отклонённый push ничего не скачивает, и refs/remotes/origin/main остаётся прежней: мы проверили это в контейнере с Git. Чтобы увидеть новые коммиты с origin, сначала нужен git fetch (или git pull), и только потом rebase или merge.

Вернёмся к исходному примеру:

$ git fetch origin master
$ git switch -c my-feature origin/master

Первая команда находит в .git/config URL удалённого репозитория origin и отправляет запрос на ту машину. В результате локальная refs/remotes/origin/master начинает указывать на тот же коммит, что и refs/heads/master на удалённой стороне (а сам коммит со всеми предками при этом скачивается к вам).

Вторая команда создаёт ссылку refs/heads/my-feature (то есть ветку), начальная точка которой — refs/remotes/origin/master. Тут абстракция как раз и протекает.

Второй аргумент здесь — (сокращённая) ссылка, поэтому можно написать и так:

$ git switch -c my-feature \
    refs/remotes/origin/master

А вот первый аргумент, хотя и создаёт ссылку, сам ссылкой не является. Иначе говоря, если развернуть и его

$ git switch -c refs/heads/my-feature \
    refs/remotes/origin/master

получится

refs/heads/refs/heads/my-feature

Вот и всё! Не уверен, что это особенно полезно, но, может быть, хотя бы интересно!

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.