📁⚙️ Полное руководство по основам Git

Из-за большого количества команд новичкам бывает сложно освоить Git. В этом руководстве мы расскажем обо всем, что вам нужно знать, чтобы приступить к работе с Git, начиная с создания первого репозитория и заканчивая слиянием веток. Помимо архитектуры Git рассмотрим принципы работы таких команд, как add, checkout, reset, commit, merge, rebase, cherry-pick, pull, push и tag.

Данная статья является переводом. Автор: Leandro Proença. Ссылка на оригинал.

В этой статье помимо архитектуры Git будут рассмотрены принципы работы таких команд, как add, checkout, reset, commit, merge, rebase, cherry-pick, pull, push и tag.

💡 Обо всем по порядку

Вы должны практиковаться параллельно с чтением поста.

Давайте сначала создадим новый проект с именем git-101, а затем инициализируем репозиторий git с помощью команды git init:

$ mkdir git-101
$ cd git-101

Git CLI предоставляет два типа команд:

  • Plumbing – состоит из низкоуровневых команд, используемых Git за кулисами, когда пользователи вводят высокоуровневые команды.
  • Porcelain – которые являются высокоуровневыми командами, обычно используемыми пользователями Git.

В этом руководстве мы увидим, как команды plumbing связаны с командами porcelain, которые мы используем изо дня в день.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека программиста»

⚙️ Архитектура Git

Внутри проекта, содержащего репозиторий Git, ознакомимся с компонентами Git:

$ ls -F1 .git/

HEAD
config
description
hooks/
info/
objects/
refs/

Мы остановимся на основных:

  • .git/objects/
  • .git/refs
  • HEAD

Разберем подробно каждый компонент.

💾 База данных объектов

Используя find, инструмент UNIX, мы можем ознакомиться со структурой папки .git/objects:

$ find .git/objects

.git/objects
.git/objects/pack
.git/objects/info

В Git все хранится в структуре .git/objects, которая представляет собой Git Object Database.

Что мы можем сохранить в Git? Все.

🤔 Подождите!

Как это возможно?

С помощью хэш-функций.

🔵 Спасаемся хэшированием

Хэш-функция преобразует данные произвольного динамического размера в значения фиксированного размера. Делая это, мы можем хранить/сохранять что угодно, потому что конечное значение всегда будет иметь один и тот же размер.

Плохая реализация хэш-функций может легко привести к коллизиям, когда два разных данных динамического размера могут отображаться в один и тот же окончательный хэш фиксированного размера.

SHA-1 – известная реализация хэш-функции, которая в целом безопасна и почти не имеет коллизий.

Возьмем, к примеру, хэширование строки my precious:

$ echo -e "my precious" | openssl sha1
fa628c8eeaa9527cfb5ac39f43c3760fe4bf8bed

Примечание. Если вы работаете в Linux, вы можете использовать команду sha1sum вместо OpenSSL.

🔵 Сравнение различий в содержании

Хорошее хэширование – это безопасная практика, когда мы не можем знать необработанное значение, т. е. реверс-инжиниринг.

В случае если мы хотим знать, изменилось ли значение, мы просто помещаем значение в хэш-функцию и вуаля – мы можем сравнить разницу:

$ echo -e "my precious" | openssl sha1
fa628c8eeaa9527cfb5ac39f43c3760fe4bf8bed

$ echo -e "no longer my precious" | openssl sha1
2e71c9ae2ef57194955feeaa99f8543ea4cd9f9f

Если хэши разные, то можно считать, что значение изменилось.

Можете ли вы найти здесь возможность? Как насчет использования SHA-1 для хранения данных и просто отслеживания всего путем сравнения хэшей? Это именно то, что Git делает внутри 🤯.

🔵 Git и SHA-1

Git использует SHA-1 для генерации хэширования всего и сохраняет его в .git/objectsпапке. Просто так!

hash-object, команда plumbing:

$ echo "my precious" | git hash-object --stdin
8b73d29acc6ae79354c2b87ab791aecccf51701f

Сравним с OpenSSL версией:

$ echo -e "my precious" | openssl sha1
fa628c8eeaa9527cfb5ac39f43c3760fe4bf8bed

Упс ... это совсем другое. Это потому, что Git добавляет определенное слово, за которым следует размер содержимого и разделитель \0. Это слово Git называет типом объекта.

Да, у объектов Git есть типы. Первый объект, который мы рассмотрим, – это объект blob.

🔵 blob-объект

Когда мы отправляем, например, строку my precious в команду hash-object, Git добавляет паттерн {object_type} {content_size}\0 к функции SHA-1, так что:

blob 12\0myprecious

Затем:

$ echo -e "blob 12\0my precious" | openssl sha1
8b73d29acc6ae79354c2b87ab791aecccf51701f

$ echo "my precious" | git hash-object --stdin
8b73d29acc6ae79354c2b87ab791aecccf51701f

Ура! 🎉

🔵 Хранение blob в базе данных

Но сама команда hash-object не сохраняется в папке .git/objects. Мы должны добавить -w и объект будет сохранен:

$ echo "my precious" | git hash-object --stdin -w
8b73d29acc6ae79354c2b87ab791aecccf51701f

$ find .git/objects
...
.git/objects/8b
.git/objects/8b/73d29acc6ae79354c2b87ab791aecccf51701f

### Or, simply
$ find .git/objects -type f
.git/objects/8b/73d29acc6ae79354c2b87ab791aecccf51701f

Данное изображение и все последующие взяты отсюда.

🔵 Чтение необработанного содержимого блоба

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

🤔 Хорошо, но подождите.

Как Git узнает исходное значение?

Он использует хэш в качестве ключа, указывающего на значение, которое является оригинальным содержимым, используя алгоритм сжатия под названием Zlib, который сжимает содержимое и сохраняет его в базе данных объектов, тем самым экономя место для хранения.

cat-file, команда plumbing, при наличии ключа распаковывает сжатые данные, таким образом, получая исходное содержимое:

$ git cat-file -p 8b73d29acc6ae79354c2b87ab791aecccf51701f
my precious

Таким образом, Git – это база данных с ключом и значением!

🔵 Как поделиться blob

Используя Git, мы хотим работать над содержимым и делиться им с другими людьми

Как правило, после работы над различными файлами/блобами мы готовы поделиться ими и подписать свои имена.

Другими словами, нам нужно сгруппировать, продвигать и добавлять метаданные в наши блобы. Этот процесс работает следующим образом:

  1. Добавьте большой двоичный объект в промежуточную область
  2. Сгруппируйте все blob-объекты в рабочей области в древовидную структуру
  3. Добавьте метаданные в древовидную структуру (имя автора, дата, смысловое сообщение)

Давайте рассмотрим описанные выше шаги подробнее.

🔵 Stage area и index

update-index, команда plumbing, позволяет добавить blob в stage area и дать ему имя:

$ git update-index \
    --add \
    --cacheinfo 100644 \
    8b73d29acc6ae79354c2b87ab791aecccf51701f \
    index.txt
  • --add: добавляет blob в stage, также называемый индексом.
  • --cacheinfo: используется для регистрации файла, которого еще нет в рабочем каталоге
  • хэш blob
  • index.txt: имя большого двоичного объекта в индексе.

Где Git хранит индекс?

$ cat .git/index

DIRCsҚjT¸zQp    index.txtÆ
                          7CJVVÙ

Недоступен для чтения человеком и сжат с использованием Zlib.

Мы можем добавить в индекс столько больших двоичных объектов, сколько захотим, например:

$ git update-index {sha-1} f1.txt
$ git update-index {sha-1} f2.txt

После добавления blob-объектов в индекс мы можем сгруппировать их в древовидную структуру, чтобы мы могли поделиться ими.

🔵 Объект дерева

Команда write-tree (plumbing) позволяет Git группировать все blob, которые были добавлены в индекс, и создает в папке еще один объект: .git/objects

$ git write-tree
3725c9e313e5ae764b2451a8f3b1415bf67cf471

Проверяя папку .git/objects, обратите внимание, что был создан новый объект:

$ find .git/objects

### The new object
.git/objects/37
.git/objects/37/25c9e313e5ae764b2451a8f3b1415bf67cf471

### The blob previously created
.git/objects/8b
.git/objects/8b/73d29acc6ae79354c2b87ab791aecccf51701f

Давайте извлечем исходное значение с помощью cat-file для лучшего понимания:

### Using the option -t, we get the object type
$ git cat-file -t 3725c9e313e5ae764b2451a8f3b1415bf67cf471
tree

$ git cat-file -p 3725c9e313e5ae764b2451a8f3b1415bf67cf471
100644 blob 8b73d29acc6ae79354c2b87ab791aecccf51701f index.txt

Это интересный вывод, он сильно отличается от BLOB-объекта, который вернул исходное содержимое.

В дереве объектов Git возвращает все объекты, которые были добавлены в индекс.

100644 blob 8b73d29acc6ae79354c2b87ab791aecccf51701f index.txt
  • 100644: кэш-информация
  • blob: тип объекта
  • хэш blob
  • имя blob

После завершения работы добавим некоторые метаданные в дерево, чтобы мы могли присвоить имя автора, дату и так далее.

🔵 Объект коммита

commit-tree, команда plumbing, получает дерево, сообщение коммита и создает еще один объект в папке .git/objects:

$ git commit-tree 3725c -m 'my precious commit'
505555f4f07d90ae14a0f2e67cba7f7b9af539ee

Что это за объект?

$ find .git/objects
...
.git/objects/50
.git/objects/50/5555f4f07d90ae14a0f2e67cba7f7b9af539ee

### cat-file
$ git cat-file -t 505555f4f07d90ae14a0f2e67cba7f7b9af539ee
commit

А как насчет его стоимости?

$ git cat-file -p 505555f4f07d90ae14a0f2e67cba7f7b9af539ee

tree 3725c9e313e5ae764b2451a8f3b1415bf67cf471
author leandronsp <leandronsp@example.com> 1678768514 -0300
committer leandronsp <leandronsp@example.com> 1678768514 -0300

my precious commit
  • tree 3725c: объект дерева ссылок
  • автор/коммиттер
  • сообщение коммита my precious commit

🤯 ОМГ! Я вижу здесь закономерность?

Кроме того, коммиты могут ссылаться на другие коммиты:

$ git commit-tree 3725c -p 50555 -m 'second commit'
5ea578a41333bae71527db537072534a199a0b67

-p позволяет ссылаться на родительский коммит:

$ git cat-file -p 5ea578a41333bae71527db537072534a199a0b67

tree 3725c9e313e5ae764b2451a8f3b1415bf67cf471
parent 505555f4f07d90ae14a0f2e67cba7f7b9af539ee
author leandronsp <leandronsp@gmail.com> 1678768968 -0300
committer leandronsp <leandronsp@gmail.com> 1678768968 -0300

second commit

Мы видим, что, благодаря коммиту с родителем, мы можем рекурсивно пройти все коммиты по всем их деревьям, пока не доберемся до финальных blob-объектов .

Возможное решение:

$ git cat-file -p <first-commit-sha1>
$ git cat-file -p <first-commit-tree-sha1>
$ git cat-file -p <first-commit-parent-sha1>
$ git cat-file -p <parent-commit-sha1>
...

И так далее. Ну вы попали в точку.

🔵 Логирование для восстановления

git log, команда porcelain, решает эту проблему, просматривая все коммиты, их родителей и деревья, давая нам представление о временной хронологии нашей работы.

$ git log 5ea57

commit 5ea578a41333bae71527db537072534a199a0b67
Author: leandronsp <leandronsp@gmail.com>
Date:   Mon Mar 13 22:42:48 2023 -0300

    second commit

commit 505555f4f07d90ae14a0f2e67cba7f7b9af539ee
Author: leandronsp <leandronsp@gmail.com>
Date:   Mon Mar 13 22:35:14 2023 -0300

    my precious commit

🤯 ОМГ!

Git – это гигантская, но легкая база данных графа ключ-значение!

🔵 Граф Git

В Git мы можем манипулировать указателями на граф.

  • Blob – это моментальные снимки данных/файлов.
  • Деревья представляют собой набор блобов или другое дерево.
  • Коммиты ссылаются на деревья и/или другие коммиты, добавляя метаданные

Это очень мило и все такое, но использование sha1 в команде git log может быть громоздким.

Как насчет присвоения имен хэшам? Используйте ссылки.

Ссылки на Git

Ссылки находятся в папке .git/refs:

$ find .git/refs

.git/refs/
.git/refs/heads
.git/refs/tags

🔵 Дадим имена коммитам

Мы можем связать любой хэш коммита с произвольным именем, расположенным в .git/refs/heads, например:

echo 5ea578a41333bae71527db537072534a199a0b67 > .git/refs/heads/test

Теперь давайте выполним git log, используя новую ссылку:

$ git log test

commit 5ea578a41333bae71527db537072534a199a0b67
Author: leandronsp <leandronsp@gmail.com>
Date:   Mon Mar 13 22:42:48 2023 -0300

    second commit

commit 505555f4f07d90ae14a0f2e67cba7f7b9af539ee
Author: leandronsp <leandronsp@gmail.com>
Date:   Mon Mar 13 22:35:14 2023 -0300

    my precious commit

Что еще лучше, Git предоставляет update-ref, команду plumbing, и мы можем использовать ее для обновления связи коммита со ссылкой:

$ git update-ref refs/heads/test 5ea578a41333bae71527db537072534a199a0b67

Звучит знакомо, да? Да, речь идет о ветках.

🔵 Ветки

Ветки – это ссылки, указывающие на конкретный коммит.

Поскольку ветки представляют команду update-ref, хэш коммита может измениться в любое время, то есть ссылка на ветку является изменяемой.

На мгновение давайте подумаем о том, как git log работает без аргументов:

$ git log

fatal: your current branch 'main' does not have any commits yet

🤔 Хм...

Как Git узнает, что моя текущая ветка является «основной»?

🔵 HEAD

Ссылка на HEAD находится в .git/HEAD. Это один файл, который указывает на главную ссылку (ветвь):

$ cat .git/HEAD

ref: refs/heads/main

Точно так же, используя команду porcelain:

$ git branch
* main

Используя symbolic-ref, команду plumbing, мы можем управлять тем, на какую ветку указывает HEAD:

$ git symbolic-ref HEAD refs/heads/test

### Check the current branch
$ git branch
* test

Как и update-ref в ветках, мы можем обновить HEAD, используя symbolic-ref в любое время.

На картинке ниже мы изменим HEAD с ветки main на ветку fix:

Без аргументов команда git log обходит корневой коммит, на который ссылается текущая ветвь (HEAD):

$ git log

commit 5ea578a41333bae71527db537072534a199a0b67 (HEAD -> test)
Author: leandronsp <leandronsp@gmail.com>
Date:   Tue Mar 14 01:42:48 2023 -0300

    second commit

commit 505555f4f07d90ae14a0f2e67cba7f7b9af539ee
Author: leandronsp <leandronsp@gmail.com>
Date:   Tue Mar 14 01:35:14 2023 -0300

    my precious commit

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

Пришло время связать все эти знания с porcelain командами, которые мы используем ежедневно.

🍽️ Porcelain команды

Git предоставляет большое количество команд высокого уровня, которые мы можем использовать без необходимости напрямую манипулировать объектами и ссылками.

Эти команды называются porcelain командами.

🔵 git add

Команда git add принимает файлы в рабочем каталоге в качестве аргументов, сохраняет их как blob-объекты в базе данных и добавляет их в индекс.

Короче говоря, git add:

  1. запускает hash-object для каждого аргумента файла
  2. запускает update-index для каждого аргумента файла

🔵 git commit

git commit принимает в качестве аргумента сообщение, группирует все ранее добавленные в индекс файлы и создает объект коммита.

Сначала выполняется write-tree:

Затем выполняется commit-tree:

$ git commit -m 'another commit'

[test b77b454] another commit
 1 file changed, 1 deletion(-)
 delete mode 100644 index.txt

🕸️ Управление указателями в Git

Широко используются следующие команды porcelain, которые манипулируют ссылками Git под капотом.

Предполагая, что мы только что клонировали проект, в котором HEAD указывает на main ветку, которая указывает на коммит C1:

Как мы можем создать еще одну новую ветку из текущей HEAD и переместить HEAD в эту новую ветку?

🔵 git checkout

Используя git checkout с параметром -b, Git создаст новую ветку из текущей (HEAD) и переместит HEAD в эту новую ветку.

### HEAD
$ git branch
* main

### Creates a new branch "fix" using the same reference SHA-1
#### of the current HEAD
$ git checkout -b fix
Switched to a new branch 'fix'

### HEAD
$ git branch
* fix
main

Какая plumbing-команда отвечает за перемещение HEAD? Точно, symbolic-ref.

После этого мы делаем новую работу в ветке fix, а затем выполняем git commit, который добавит новый коммит под названием C3:

Запустив git checkout, мы можем продолжать переключать HEAD между разными ветвями:

Иногда нам может понадобиться переместить коммит, на который указывает ветка.

Мы уже знаем, что это делает команда plumbing update-ref:

$ git update-ref refs/heads/fix 356c2

🔵 git reset

Команда git reset (porcelain) запускает update-ref внутри, поэтому нам просто нужно выполнить:

$ git reset 356c2

Но как Git узнает, какую ветку нужно переместить? Что ж, git reset перемещает ветку, на которую указывает HEAD.

Что делать, если есть различия между ревизиями? Используя reset, Git перемещает указатель, но оставляет все различия в рабочей области (индексе).

$ git reset b77b

Проверка с помощью git status:

$ git status

On branch fix
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        another.html
        bye.html
        hello.html

nothing added to commit but untracked files present (use "git add" to track)

Коммит ревизии был изменен в ветке fix и все отличия перенесены в index .

Тем не менее, что нам делать, если мы хотим сбросить и отбросить все различия? Просто использовать параметр --hard:

При использовании git reset --hard любые различия между ревизиями будут отброшены, и они не будут отображаться в индексе .

💡 Золотой совет о перемещении ветки

Если мы хотим выполнить подключение update-ref к другой ветке, нет необходимости проверять ветку, как это необходимо в git reset.

Вместо этого мы можем выполнить porcelain-команду git branch -f source target:

$ git branch -f main b77b

Под капотом он выполняет git reset --hard в исходной ветке. Давайте проверим, на какой коммит указывает основная ветка:

$ git log main --pretty=oneline -n1
b77b454a9a507f839880879a895ac4f241177a28 (main) another commit

Также мы подтверждаем, что ветка fix по-прежнему указывает на коммит 369cd:

$ git log fix --pretty=oneline -n1
369cd96b1f1ef6fa7de1ff2ed12e15be979dcffa (HEAD -> fix, test) add files

Мы сделали git reset без перемещения HEAD!

Нередко вместо перемещения указателя ветки мы хотим применить конкретный коммит к текущей ветке.

🔵 git cherry-pick

С помощью porcelain-команды git cherry-pick мы можем применить произвольную фиксацию к текущей ветке.

Возьмем следующий сценарий:

  • main-пункты к C3 – C2 – C1
  • fix для точек на C5 – C4 – C2 – C1
  • HEAD указывает на fix

В ветке исправления отсутствует фиксация C3, на которую ссылается основная ветка.

Для этого запустим git cherry-pick C3:

Обратите внимание, что:

  • коммит C3 будет клонирован в новый коммит с именем C3
  • этот новый коммит будет ссылаться на коммит C5
  • fix переместит указатель на C3'
  • HEAD продолжает указывать на исправление

После применения изменений график будет представлен следующим образом:

Однако есть еще один способ переместить указатель ветки. Он заключается в использовании произвольного коммита другой ветки, и при необходимости выполняется слияние различий.

Вы не ошиблись, здесь мы говорим о git merge.

🔵 git merge

Опишем следующий сценарий:

  • Main-пункты к C3 – C2 – C1
  • Fix точек на C4 – C3 – C2 – C1
  • HEAD указывает на main

Мы хотим применить исправленную ветку к текущей (основной) ветке, т. е. выполнить git merge fix.

Обратите внимание, что ветка fix содержит все коммиты, принадлежащие основной ветке (C3 – C2 – C1), имея только один коммит перед основным (C4).

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

Такое слияние называется fast-forward, как показано на изображении ниже:

Когда fast-forward невозможен

Иногда текущее состояние нашей древовидной структуры не позволяет выполнять ускоренную перемотку вперед. Рассмотрим сценарий ниже:

Когда в ветке слияния – ветке исправления в приведенном выше примере – отсутствует одна или несколько коммитов из текущей ветки (основной): коммит C3.

Таким образом, fast-forward невозможен.

Однако, чтобы слияние прошло успешно, Git выполняет технику, называемую Snapshotting, состоящую из следующих шагов.

Во-первых, Git ищет следующего общего родителя двух ветвей, в этом примере коммит C2.

Во-вторых, Git делает снимок целевой ветки фиксации C3:

В-третьих, Git делает снимок исходной ветки фиксации C5:

Наконец, Git автоматически создает слияние фиксации (C6) и указывает на двух родителей соответственно: C3 (цель) и C5 (источник):

Вы когда-нибудь задумывались, почему в дереве Git отображаются некоторые коммиты, созданные автоматически?

Не заблуждайтесь, этот процесс слияния называется трехсторонним слиянием!

Далее давайте изучим другой метод слияния, при котором fast-forward невозможен, но вместо моментального снимка и автоматического слияния коммита Git применяет различия поверх исходной ветки.

Да, это git rebase.

🔵 git rebase

Рассмотрим следующее изображение:

  • Main-пункты к C3 – C2 – C1
  • fix точек на C5 – C4 – C2 – C1
  • HEAD указывает на fix

Мы хотим перебазировать основную ветку в ветку исправления, посредством git rebase main. Но как работает git rebase?

👉git reset

Сначала Git выполняет git reset main, при этом ветка fix будет указывать на тот же указатель основной ветки: C3 – C2 – C1.

На данный момент у коммитов C5-C4 нет ссылок.

👉git cherry-pick

Во-вторых, Git выполняет git cherry-pick C5 в текущую ветку:

Обратите внимание, что выбранные коммиты клонируются, поэтому окончательный хэш изменится: C5 – C4 станет C5' – C4'.

После у нас может быть следующий сценарий:

👉git reset еще раз

Наконец, Git выполнит git reset C5', поэтому указатель ветки fix переместится с C3 на C5'.

Процесс rebase завершен.

До сих пор мы работали с локальными ветками, т.е. хранящимися на нашем устройстве. Пришло время научиться работать с удаленными ветками, которые синхронизированы с удаленными репозиториями в интернете.

🌐 Удаленные ветки

Чтобы работать с удаленными ветками, нам нужно добавить удаленную ветку в наш локальный репозиторий с помощью команды porcelain – git remote.

$ git remote add origin git@github.com/myaccount/myrepo.git

Удаленные репозитории находятся в папке .git/refs/remotes:

$ find .git/refs
...
.git/refs/remotes/origin
.git/refs/remotes/origin/main

🔵 Скачать с удаленного репозитория

Как нам синхронизировать удаленную ветку с нашей локальной веткой?

Git предлагает два шага:

👉git fetch

С помощью git fetch origin main Git загрузит удаленную ветку и синхронизирует ее с новой локальной веткой с именем origin/main, также известной как upstream branch (восходящая ветка).

👉git merge

После извлечения и синхронизации вышестоящей ветки мы можем выполнить git merge origin/main и, поскольку восходящая ветка опережает нашу локальную ветку, Git безопасно применит ускоренное слияние.

Однако комбинация fetch + merge может повториться, так как мы будем синхронизировать локальные/удаленные ветки несколько раз в день.

Но сегодня наш счастливый день, и Git предоставляет команду git pullchina, которая выполняет fetch + merge от нашего имени.

👉git pull

git pull выполнит выборку (синхронизирует удаленную ветвь с вышестоящей ветвью), а затем объединит восходящую ветвь с локальной ветвью.

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

🔵 Загрузить на удаленный репозиторий

Git предоставляет porcelain команду под названием git push:

👉git push

Выполнение git push origin main приведет к загрузке изменения на удаленный репозиторий:

Затем Git объединит восходящую ветвь origin/main с локальной main-веткой:

В конце мы получим следующее изображение:

Где:

  • Удаленный репозиторий обновлен (локальные изменения отправлены)
  • main к C4
  • origin/main к C4
  • HEAD указывает на main

🔵 Предоставление неизменяемых имен коммитам

Мы знаем, что ветки – это просто изменяемые ссылки на коммиты, поэтому мы можем переместить указатель ветки.

Однако Git также предлагает способ предоставления неизменяемых ссылок, указатели которых не могут быть изменены (если только вы не удалите их и не создадите снова).

Неизменяемые ссылки полезны, например, когда мы хотим пометить/отметить коммиты, которые готовы для какого-то производственного выпуска.

Да, мы говорим о тегах.

👉git tag

Используя команду (porcelain) git tag, мы можем давать имена коммитам, но мы не можем выполнить сброс или любую другую команду, которая изменила бы указатель.

Это очень полезно для управления версиями. Теги находятся в папке .git/refs/tags:

$ find .git/refs

...
.git/refs/tags
.git/refs/tags/v1.0

Если мы хотим изменить указатель тега, мы должны удалить его и создать еще один с тем же именем.

💡 Git reflog

И последнее, но не менее важное – вызываемая команда git reflog, которая сохраняет все изменения, которые мы сделали, в нашем локальном репозитории.

$ git reflog

369cd96 (HEAD -> fix, test) HEAD@{0}: reset: moving to main
b77b454 (main) HEAD@{1}: reset: moving to b77b
369cd96 (HEAD -> fix, test) HEAD@{2}: checkout: moving from main to fix
369cd96 (HEAD -> fix, test) HEAD@{3}: checkout: moving from fix to main
369cd96 (HEAD -> fix, test) HEAD@{4}: checkout: moving from main to fix
369cd96 (HEAD -> fix, test) HEAD@{5}: checkout: moving from fix to main
369cd96 (HEAD -> fix, test) HEAD@{6}: checkout: moving from main to fix
369cd96 (HEAD -> fix, test) HEAD@{7}: checkout: moving from test to main
369cd96 (HEAD -> fix, test) HEAD@{8}: checkout: moving from main to test
369cd96 (HEAD -> fix, test) HEAD@{9}: checkout: moving from test to main
369cd96 (HEAD -> fix, test) HEAD@{10}: commit: add files
b77b454 (main) HEAD@{11}: commit: another commit
5ea578a HEAD@{12}:

Это очень полезно, если мы хотим перемещаться вперед и назад по временной шкале Git. Наряду с reset, cherry-pick и подобными, это мощный инструмент, если мы хотим освоить Git.

Подведение итогов

Какое долгое путешествие!

Эта статья была слишком длинной, но я смог изложить основные темы, которые, по моему мнению, важны для понимания Git.

Я надеюсь, что после прочтения этой статьи вы будете более уверенно использовать Git, разрешая ежедневные конфликты и сложные случаи во время процесса слияния/перебазирования.

***

Материалы по теме

Источники

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

Библиотека программиста
18 октября 2017

Шпаргалка по Git, в которой представлены основные команды

Git сегодня - это очень популярная система контроля версий. Поэтому шпаргал...
admin
21 июня 2017

Про Git, Github и Gitflow простыми словами

Не самое исчерпывающее, но точно вполне доходчивое руководство по Git, Gith...
admin
23 февраля 2017

Git за полчаса: руководство для начинающих

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