Пошук уроків, статей та іншого контенту
Розберетеся, як Git зберігає посилання на коміти та як HEAD, гілки й теги вказують на об’єкти.
Git зберігає коміти та інші об’єкти в базі даних .git/objects. Кожен об’єкт має унікальний ідентифікатор — SHA-1 або SHA-256 хеш, наприклад:
8a4f2c1...Запам’ятовувати такі хеші незручно. Тому Git використовує refs — іменовані посилання на об’єкти.
Наприклад:
refs/heads/mainможе вказувати на коміт:
8a4f2c1...Коли створюється новий коміт, сам попередній коміт не змінюється. Git створює новий об’єкт коміту, а ref гілки переміщується на цей новий коміт.
Спрощено це можна уявити так:
main ───────► Commit C
│
▼
Commit B
│
▼
Commit AГілка main — це не окрема копія файлів і не список усіх комітів. Це вказівник на один коміт. Попередні коміти Git знаходить через поле parent у цьому коміті.
У локальному репозиторії refs зазвичай мають такі імена:
refs/heads/main
refs/heads/feature
refs/tags/v1.0refs/heads/ — локальні гілки;
refs/tags/ — теги;
refs/remotes/ — посилання на віддалені гілки.
У простих репозиторіях Git може зберігати локальну гілку у файлі:
.git/refs/heads/mainУ цьому файлі буде хеш коміту, на який вказує гілка:
8a4f2c1...Однак не варто змінювати такі файли вручну. Для роботи з refs слід використовувати команди Git.
Переглянути всі refs можна так:
git show-refПриклад результату:
8a4f2c1... refs/heads/main
8a4f2c1... refs/tags/v1.0HEAD — спеціальне посилання, яке показує, де зараз перебуває користувач у репозиторії.
Зазвичай HEAD вказує не безпосередньо на коміт, а на поточну гілку:
HEAD ─────► refs/heads/main ─────► Commit CВміст файлу .git/HEAD у такому стані має приблизно такий вигляд:
ref: refs/heads/mainПереглянути поточне значення HEAD можна командами:
git status
git branch --show-current
git symbolic-ref --short HEADКоманда git branch --show-current виведе назву поточної гілки:
mainКоманда git rev-parse HEAD виведе хеш коміту, на який зрештою вказує HEAD:
git rev-parse HEADОтже, коли ви створюєте коміт у гілці main, відбувається приблизно така послідовність:
Git створює об’єкт нового коміту.
Новий коміт зберігає посилання на попередній коміт.
refs/heads/main переміщується на новий коміт.
HEAD і далі вказує на main.
Розглянемо простий репозиторій:
mkdir refs-demo
cd refs-demo
git init
git config user.name "Demo User"
git config user.email "demo@example.com"
git branch -M main
printf "Перша версія\n" > file.txt
git add file.txt
git commit -m "Створити файл"
printf "Друга версія\n" >> file.txt
git commit -am "Оновити файл"
echo "Поточна гілка:"
git branch --show-current
echo "Коміт, на який вказує HEAD:"
git rev-parse HEAD
echo "Усі локальні refs:"
git show-ref
echo "Історія комітів:"
git log --oneline --decorateПісля виконання команда git show-ref покаже ref гілки main і хеш останнього коміту:
<hash> refs/heads/mainКоманда git log --oneline --decorate може показати:
8a4f2c1 (HEAD -> main) Оновити файл
3b7d9e4 Створити файлПозначка HEAD -> main означає:
HEAD вказує на гілку main;
main вказує на коміт 8a4f2c1.
Після нового коміту вказівник main переміститься вперед:
До нового коміту:
main ───► Commit B ───► Commit A
Після нового коміту:
main ───► Commit C ───► Commit B ───► Commit AКоміт B при цьому не змінюється і не зникає. Він залишається частиною історії.
Команда git rev-parse допомагає перетворити ім’я ref на хеш об’єкта.
git rev-parse main
git rev-parse HEAD
git rev-parse refs/heads/mainУ звичайному стані всі три команди виведуть один і той самий хеш, якщо HEAD перебуває в гілці main.
Ім’я main — скорочений запис:
refs/heads/mainGit автоматично шукає відповідну гілку.
Також можна отримати попередній коміт відносно поточного:
git rev-parse HEAD^
git rev-parse HEAD~1HEAD^ — батьківський коміт поточного коміту;
HEAD~1 — також перший батьківський коміт;
HEAD~2 — коміт на два кроки назад.
Ці записи не створюють нових refs. Вони лише допомагають звертатися до об’єктів за відносними іменами.
Тег — це ім’я для певної точки історії. Найчастіше теги використовують для позначення версій:
v1.0.0
v2.1.0Створімо легковаговий тег:
git tag v1.0.0Тепер він вказує на поточний коміт:
refs/tags/v1.0.0 ─────► Commit CПереглянути refs можна так:
git show-refДля легковагового тегу команда покаже той самий хеш, що й для поточного коміту:
<hash> refs/heads/main
<hash> refs/tags/v1.0.0Легковаговий тег — це безпосереднє посилання на об’єкт, зазвичай на коміт.
Анотований тег створюється з повідомленням:
git tag -a v1.1.0 -m "Реліз версії 1.1.0"Такий тег має власний об’єкт у базі Git. Схема виглядає так:
refs/tags/v1.1.0 ─────► Tag object ─────► Commit CПодивитися тип об’єкта можна так:
git cat-file -t refs/tags/v1.1.0Для анотованого тегу результатом буде:
tagЩоб отримати коміт, на який зрештою вказує тег, використовуйте суфікс ^{}:
git rev-parse refs/tags/v1.1.0^{}У більшості випадків у командах на кшталт git show Git сам переходить від тегу до об’єкта, на який він посилається:
git show v1.1.0Легковаговий та анотований теги обидва дають ім’я певній точці історії, але анотований тег додатково зберігає інформацію про тег: повідомлення, автора та час створення.
Для огляду гілок і тегів з відповідними комітами зручно використовувати:
git log --oneline --decorate --allПриклад:
8a4f2c1 (HEAD -> main, tag: v1.1.0) Оновити файл
3b7d9e4 Створити файлТут один коміт має два імені:
main — поточна гілка;
v1.1.0 — тег.
Це не означає, що створилися два коміти. Обидва refs вказують на один і той самий об’єкт.
Перевірити, які refs вказують на конкретний коміт, можна командою:
git branch --contains HEAD
git tag --contains HEADЗазвичай HEAD вказує на гілку. Але можна перейти безпосередньо до конкретного коміту:
git switch --detach HEAD~1Тепер схема має такий вигляд:
HEAD ─────► Commit B
main ─────► Commit CHEAD більше не вказує на main. Такий стан називається detached HEAD — «від’єднаний HEAD».
Перевірити його можна так:
git statusGit повідомить, що ви перебуваєте у стані detached HEAD.
У цьому стані можна переглядати файли та запускати код. Якщо створити новий коміт, HEAD буде вказувати на нього без назви гілки:
HEAD ─────► Commit D
main ─────► Commit CЩоб не втратити такий коміт, можна створити з нього нову гілку:
git switch -c experimentПісля цього схема стане такою:
experiment ─────► Commit D
HEAD ────────────► experiment
main ────────────► Commit CЯкщо ж залишити detached HEAD і перейти на іншу гілку, коміт без гілки може стати недоступним за звичайним іменем.
У повсякденній роботі refs переміщуються автоматично:
git commit переміщує поточну гілку;
git switch -c name створює нову гілку на поточному коміті;
git reset може перемістити поточну гілку на інший коміт;
git tag name створює тег на поточному коміті.
Подивитися всі локальні гілки з комітами можна так:
git branch -vПриклад:
* main 8a4f2c1 Оновити файлЗірочка позначає гілку, на яку зараз опосередковано вказує HEAD.
Git також має низькорівневу команду git update-ref, яка може змінити ref без створення коміту:
git update-ref refs/heads/main HEADЦя команда встановлює main на той самий об’єкт, на який вказує HEAD. Вона не потрібна для звичайної роботи, але показує головну ідею refs: ref — це ім’я, пов’язане з ідентифікатором об’єкта.
Будьте обережні з низькорівневими командами, оскільки неправильне переміщення гілки може приховати коміти від звичайної історії.
Гілка не є директорією з окремими файлами. Це ref, який вказує на коміт. Файли робочої директорії Git відновлює на основі поточного коміту та його історії.
HEAD — спеціальний вказівник. Зазвичай він вказує на гілку, але в стані detached HEAD він вказує безпосередньо на коміт.
Перевіряйте стан командою:
git statusТег не копіює коміт. Він лише додає ім’я для наявного об’єкта. Декілька refs можуть вказувати на один коміт.
Ref зберігає посилання лише на один об’єкт. Історія будується переходом від поточного коміту до його батьківських комітів.
.git/refs вручнуТакі зміни можуть пошкодити стан репозиторію або призвести до втрати зручного доступу до комітів. Використовуйте команди Git для створення та переміщення refs.
Ref — іменоване посилання на об’єкт Git.
Локальні гілки зберігаються під refs/heads/.
Теги зберігаються під refs/tags/.
Гілка зазвичай вказує на останній коміт своєї історії.
Після створення коміту Git переміщує поточну гілку на новий коміт.
HEAD показує поточне положення користувача.
Зазвичай HEAD вказує на гілку, але може безпосередньо вказувати на коміт у стані detached HEAD.
Легковаговий тег безпосередньо вказує на об’єкт, а анотований тег вказує на окремий tag object, який містить посилання на цільовий об’єкт.
Кілька гілок і тегів можуть вказувати на один і той самий коміт.