Skip to content

Git

Git - система контроля версий, которая позволяет хранить снимки-коммиты изменений файлов репозитория.

Git config

Git config - набор команд для глобальной и локальной настройки поведения git на хосте.

Конфигурируем гит
git config --global user.name "My_user" user.email "my_user@exsample.com"

git config --global --unset user.email # удаляем запись в файле конфигурации
git config -l # посмотреть текущую конфигурацию 
git config --global core.editor "code -w" # указываем гиту редактор по умолчанию 

-- global

Флаг global говорит о том, что мы собираемся применять введенные настройки для всех репозиториев в которых работаем. Эти настройки будут храниться в домашнем каталоге ~/.gitconfig.

-- global
cat ~/.gitconfig # можно посмотреть настройки в консоле
nano ~/.gitconfig # можно менять настройки в редакторе

--local

С помощью флага local можно переопределить глобальные настройки. Задаем настройки гита для определенного репозитория. Настройки будут храниться в repo_dir/.git/config

--local
git config --local user.name "My_user" user.email "my_user@exsample.com"

git config --list --show-origin # просмотр текущих настроек, с разделением на уровни: глобальные и локальные

git alias

В гите есть секция псевдонимов, в которой можно задавать сокращенные наименования для длинных команд

alias
git config --global alias.loga "log --oneline --graph --all" # создаем короткую команду loga

.gitignore

Специальный файл, который храниться в корне репозитория. В нем можно хранить список файлов и папок, которые не нужно включать в индекс и хранить в БД гита.

.gitignore
touch .gitignore # создаем gitignore в корне репозитория
nano .gitignore # добавляем в него файлы, которые будут исключены и БД. Синтаксис файла:
# Можно использовать строки коментариев
tmp-file.txt
# Игнорируем целый каталог
logs/

Создание репозитория

Репозиторий - папка в файловой системе, в которой хранится отдельно взятый проект. Является минимальным необходимым объектом для хранения информации в системе контроля версий. Поскольку git хранит файлы проекта с помощью snapshot (моментальных снимков) - ему нужно место где он будет хранить всю информацию, относящуюся к проекту, для этого и создается репозиторий.

git init

git init команда для инициализации нового репозитория в ФС хостового компьютера. Git создает скрытую папку в корне репозитория с именем .git, в ней будут храниться файлы конфигурации и все снимки проекта, которые будут созданны

создание репозитория
git init # команда для инициализации нового репозитория в ФС хостового компьютера. 

git clone

Команда для клонирования удаленного репозитория на локальный компьютер. После того как удаленный репозиторий скопирован с ним можно работать как с локальным. Чтобы отправить измененые коммиты из локального репозитория на сервер необходимо использовать команду git push. Чтобы залить обновленную версию удаленного репозитория нужно использовать команду git pull.

git clone
git clone <url> # клонирует указанный репозиторий по выбранному протоколу (https, ssh) в текущую директорию.

# Рекомендуется пользоваться протоколом ssh (требует предварительной настройки),
# так будет удобнее пушить изменения на сервер.

git remote

Команда для конфигурирования подключения удаленного репозитория

git remote
git remote -v # показывает подробную информацию об удаленных репозиториях
origin <URL> (fetch) # репозиторий по умолчанию 
origin <URL> (push) # репозиторий по умолчанию для отправки изменений

git push

Команда для отправки измененного локального репозитория на сервер

git push
git config --global push.default simple # указывает как гит должен действовать при пуше изменений на удаленный сервер.

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

git push --set-upstream origin new-local-branch # новые локальные ветки необходимо пушить с помощью этой команды
# при этом создается ветка слежения, которая "соединяет" ветки клонированного и удаленного репозитория
git branch -vv # позволяет посмотреть какие локальные ветки имеют ветки слежения

pull request

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

git pull, git fetch

git pull = git fetch + git merge

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

git fetch - извлекает все удаленные ветки, со всеми их коммитами. Чтобы удаленная ветка стала локальной, на нее достаточно переключиться git switch.

git pull, git fetch
git pull # сливает изменения из удаленного репозитория из конкретной "привязанной" ветки слежения

git fetch # сливает любые изменения из удаленного репозитория и обновляет ветки слежения,
# не затрагивая ветки локального репозитория

# на ветки слежения можно таак же переключаться с помощью git switch
git switch <remotes-branch> # ветка слежения будет скопированна в локальную ветку склонированного
# репозитория, теперь к ней применимы команды push/pull

Перед тем как "пушить" локальную ветку в удаленный репозиторий, рекомендуется выполнить git fetch, если в удаленной ветки появились новые коммиты и, теперь локальная и удаленная ветки расходятся - необходимо "смержить" локальную и удаленную ветки и, только после этого - "пушить" обновленную ветку в удаленный репозиторий. Перед вливанием своей рабочей ветки в базовую (интеграционную) ветку всего проекта, рекомендуется забрать все изменения ветки master с удаленного сервера, локально влить их в свой рабочую ветку, проверить работоспособность полученного результата и только потом вливать свою рабочую ветку в интеграционную, локально или через pull request. После того как интеграция веток выполненна, необходимо удалить лишние ветки: удаленную ветку слежения и локальную ветку.git fetch + git merge origin/master + TEST + git merge <work-branch> master + git switch master + git push + git branch -d <work-branch>илиgit fetch + git merge origin/master + TEST + git push <work-branch> + PULL REQUEST + git fetch -p + git branch -d <work-branch>

Пример совместной работы над одним удаленным репозиторием
git clone <URL> # клонируем удаленный репозиторий
git fetch # на всякий случай сливаем изменения и мержим их со своей основной веткой
git merge origin/master # если наша основная ветка отстает от удаленной
git switch -c my-branch # создаем локальную ветку для работы
# working process ... работаем в своей ветке
# ready to merge with master готовы внести свои изменения в основную ветку
git fetch # проверяем удаленные ветки
git merge origin/master # вливаем все новые изменения из "удаленного мастера" с свою рабочую ветку
# TEST тестируем работоспособность 
git push # "пушим" свою локальную рабочую ветку в удаленный репозиторий
# PULL REQUEST создаем сапрос на слияние и ждем удачного вливания своей ветки в основную ветку удаленного репозитория
git fetch -d # обновляем ветки слежения, одновременно удаляя старые (удаленные на сервере) ветки слежения 
git switch master # переключаемся на основную ветку
git merge origin/master # "догоняемся" до основной ветки в удаленном репозитории
git branch -d my-branch # удаляем ненужную локальную ветку

git commit

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

Команда git commit производит фиксацию версии проекта, но она фиксирует только те файлы и каталоги, которые были подготовленны с помощью команды git add. Выполняя коммит, Git создает объект коммита с уникальным именем-идентификатором (хеш-сумма), который сохраняется в папке .git. Уникальный идентификатор коммита вычисляется с использованием набора метаданных, который включает в себя: имя пользователя, текущее время, сообщение коммита и пр.

добавление файлов в репозиторий
# Команда для добавления файлов в предстоящий коммит, при этом указанные файлы помещаются в индекс гита
git add file_name1 file_name2 # Добавляем в коммит конкретные файлы
git add * # Добавляем в коммит все файлы находящиеся в репозитории

# Команда для создания коммита, при этом состояние индекса  записывается в объектную БД гита
git commit -m "commit name"

# Команда для проверки состояния гит-репозитория: какая ветка, какие файлы добавлены в предстоящий коммит
git status

Разделение репозитория гита на индекс и внутренню объектную БД, в которую попадают только закоммиченные объекты, дает возможность вносить изменения во множество файлов проекта, а добавлять в коммит только определенные файлы, которые логически соотвествуют данному коммиту.

git add

После того как файл добавлен в индекс с помощью git add, Git начинает за ним следить, файл становиться отслеживаемым и измененным, это видно в git status. Теперь что-бы зафиксировать изменения и перевисти файл в статус неизмененный необходимо произвести коммит. Пока коммит не произошел изменения можно откатить с помощью команды git restore file_name.

Можно сказать, что у команды git add двойное назначение. С ее помощью происходит как добавление новых файлов, так и индексирование изменений в отслеживаемых.

Таким образом, простой алгоритм одного цикла работы (от коммита до коммита) с git-репозиторием выглядит так:

  1. Вносятся изменения в проект: правятся имеющиеся файлы, добавляются/удаляются новые.
  2. С помощью git add выполняется индексирование тех изменений, которые должны быть учтены в предстоящем коммите.
  3. Командой git commit -m "Описание изменений" фиксируется новое состояние проекта.

Если все измененные отслеживаемые файлы должны быть добавлены в коммит (неотслеживаемых это не касается), то вместо одтельных git add и git commit можно дать "объединенную" команду, в которой вместо git add используется ключ -a у git commit.

git commit -a
# Добавляем файлы в индекс и коммитем их одной командой
git commit -am "New version"
Жизненный цикл файла в git-репозитории
# 1. Добавляем новый файл в репозиторий
touch Readme.md 
git status # Проверяем состояние рапозитория
Untracked files: Readme.md # Гит сообщает нам, что иммется неотслеживаемый файл, это означает,
# что гит не следит за изменениями файла.
# Сейчас индекс и объектная БД гита пусты, файл присутствует только в рабочем каталоге

#2. Добавляем файл в индекс теперь Гит за ним следит
git add Readme.md
git status
new file:   Readme.md # В индексе появился новый файл, которого пока нет во внутренней БД Гита,
# но после коммита он туда попадет.
# Теперь копия файла помещена в индекс, файл в рабочем каталоге и индексе идентичны

#3. Делаем коммит, после чего новому файлу присваивается статус "неизмененный", т.е. все версии
# файла находящиеся в рабочем каталоге, индексе и внутренней объектной ДБ гипа идентичны
git commit -m "add Readme.md"
# Теперь Гит поместил содержимое индекса в объектную БД, файлы везде одинаковые

#4. Изменим файл в рабочем каталоге, файл станет "измененным"
echo "new string" > Readme.md
git status
Changes not staged for commit: # Измененные данные не внесены в индекс
  (use "git add <file>..." to update what will be committed) # можно внести изменения в индекс
  (use "git restore <file>..." to discard changes in working directory) # можно откатить изменения до закомиченной версии
        modified:   Readme.md # данный файл изменен, но не внесен в индекс

#5. Добавляем измененный файл в индекс, теперь файл становится подкотовленным (staged) для коммита
git add Readme.md
git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage) # подготовленный файл можно убрать из индекса
        modified:   Readme.md # данный файл внесен в индекс

#6. Делаем новый коммит - файл добавляется во внутреннюу БД гита и опять становится "неизмененным"
git commit -m "modify Readme.md"

Создавая новые коммиты в гит-репозитории создается история изменений. В объект каждого нового коммита добавляется информация (идентификатор) о его предшественнике (родителе). Дочерние коммиты ссылаются на родительские, а не наоборот. Указатели являются однонаправленными, но это не мешает коммиту иметь несколько дочерних элементов или несколько родителей.

git commit --amend

Сообщение в последнем коммите можно изменить используя флаг --amend, при этом рабочий каталог должен быть чист, т.е. в индекс не должны быть добавленны незакоммиченные файлы. Если в индекс будут добавленны измененные файлы они попадут в новый коммит!

git commit --amend
git commit --amend -m "new message"
# Таким способом можно изменять только последний коммит ветки.

При фиксации коммита в объектной БД гит фиксирует набор изменений в файлах из индекса, записывается именно разница между новым и родительским коммитом, так называемая "дельта"

git branch

Ветвление кода в Гите - способ организации работы в одном проекте, но над разными задачами одновременно. Например, есть основная ветка кода master - она используется для хранения сто-процентно рабочей версии; при необходимости доработки проекта создаются новые ветки, в которых происходит создание нового кода, потом эти ветки сливаются с основной.

Ветка - это просто ссылка на последний коммит ветки по его ID. При создании нового коммита ссылка обновляется. У каждой ветки есть имя и она указывает на последний коммит сделанный на этой ветке.

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

git branch
git branch # отображает существующие ветки
git branch -m <new-branch-name> # переименовывает текущую ветку
git branch -m <old-branch-name> <new-branch-name> # переименовывает указанную ветку
git branch <new-branch-name> # создает новую ветку, но не переключается на нее автоматически

git switch

Команда для переключения между ветками кода

git switch
git switch <branch-name> # переключает на указанную ветку
git checkout <branch-name> # старая команда гит для переключения между ветками
git switch -c <branch-name> # создает новую ветку и переключает на нее

git merge

Команда с помощью которой осуществляется слияние двух и более веток в одну. Обычно объединяет две ветки: базовую (активная в данный момент) и дополняющую (ветку с которой мы хотим объединиться). При слиянии веток происходит объединение истории коммитов.

Методы слияния веток:

  • Fast-forward - самый простой метод слияния веток, в основную ветку просто добавляется указатель на последний коммит вливаемой ветки, новый коммит становится последним в основной ветке и содержание рабочего каталога меняется соответствующим образом. Сценарий слияния fast-forward является оптимальным сценарием, поскольку технически одна ветка догоняет другую, просто добавляя в свою историю коммиты вливаемой ветки. Слияние с помощью этой стратегии возможно, только если последний коммит базовой ветки является родителем для вливаемой ветки.
  • Merge-commit (коммит слияния) - применяется, если последний коммит базовый ветки ушел вперед относительно родительского коммита вливаемой ветки. В таком случае создается новый коммит "коммит слияния" у которого будут два родителя: последний коммит базовой ветки и последний коммит вливаемой ветки. Теперь основная ветка указывает на коммит слияния, который состоит из всех изменений внесенным в объединяемые ветки.
  • Конфликт слияния (merge conflict) - возникает в том случае, когда в объединяемых ветках присутствуют файлы с одинаковыми названиями, в которых отдельные строчки содержат разные значения. При этом Гит остановит процесс авто-слияния, выдаст сообщение в каких строках каких файлов имеется конфликт и будет ждать пока пользователь исправит и сохранит эти файлы вручную, затем добавит отредактированные файлы в индекс и корректно завершит коммит слияния.

Удаление веток. Слитые ветки необходимо удалять! При этом не стоит волноваться о комитах сделанных в удаляемых ветках, поскольку последний коммит слитой ветки является родителем коммита-слияния в основной ветке - он не может быть удален и останется в истории.

Автоматическое безконфликтное слияние веток fast-forward
git merge <branch-name-to-merge> # команда вливает указанную ветку в активную ветку, добавляя к ней всю истории коммитов вливаемой ветки.

# Автоматическое безконфликтное слияние веток, с помощью коммита слиятия
git merge <branch-name-to-merge> # Гит обнаруживает, что последний коммит базовой ветки убежал вперед, метод fast-forward не применим
# Гит автоматически создает коммит слияния и отрывает окно тексового редактора, достаточно ввести сообщение коммита и закрыть окно редактора.
Merge made by the 'ort' strategy. # Гит успешно завершил авто-слияние, с помощью создания коммита слияния.
Конфликт слияния (merge conflict)
git merge <branch-name-to-merge> 
Auto-merging <conflict-file-name> # гит пытается произвести auto-merge
CONFLICT (content): Merge conflict in <conflict-file-name> # Гит сообщает в каких файлах произошел конфликт слияния
Automatic merge failed; fix conflicts and then commit the result. # Гит предлагает вручную пофиксить все конфликты и закоммитить результат
# Необходимо вручную изненить конфликтующие файлы и сохранить изменения в редакторе.
git add * # добавляем в индекс отредактированные файлы
git commit # завершаем коммит
Удаляем слитую ветку
git branch -d <branch-name-to-delete> # команда проверяет, слита ли удаляемая ветка в базовую, если да - удаляет, если нет - выдает сообщение об ошибке
# Правилом хорошего тона является немедленное удаление слитой ветки. Тем самым поддерживается
# порядок в репозиторие и исключается возможность возобновления работы в слитой ветке.
# Если есть необходимость продолжить работать над задачами в старой (слитой) ветке - создается новая ветка и уже в ней продолжается работа.

git rebase

Команда, которая перемещает начальный коммит ветки (и всю ветку вместе с ним) на потомка своего первоначального родителя. По сути, создается новая ветка с новыми коммитами, только начало этой ветки переносится на другой коммит (обычно последний) основной ветки.

Данная команда позволяет "спрямить" историю коммитов, не прибегая к коммиту слияния

git rebase
# Становимся на ветку, которую требуется перебазировать
git rebase master # запускаем rebase и укзываем ветку по которой  следует перемещаться.

git log

Git позволяет путешествовать по истории коммитов. Первый инструмент - команда git log, по-умолчанию выводит список всех коммитов текущей ветки, от последнего коммита к первому. Данная команда выводит много полезной информации: ID коммита, автор, время создания, сообщение

Как работает команда git log? Git просматривает последний сделанный коммит на текущей ветке и отображает отформатированную информацию о нем, затем переходит к родительскому коммиту, и так до самого первого коммита.

git log
git log # информация о коммитах для текущей ветки
git log --abbrev-commit # сокращенное отображение ID коммита
git log --abbrev-commit --pretty=oneline # форматирование вывода в одну строку
git log --oneline # сокращенный вариант предыдущей команды
git log --oneline --all --graph # отображает все коммиты на всех ветках репозитория в виде графа

git diff

Команда которая показывает различия между тем, что находится в индексе и тем, что находится в рабочем каталоге. Строки, начинающиеся с префикса - , относятся к индексу, а строки с префиксом + относятся к рабочему каталогу.

Сравнение веток. Можно применять git diff к целым веткам, в таком случае гит сравнивает последние коммиты веток

git diff
git diff # команда выводит все файлы из рабочего каталога, которые были изменены и не добавленны в индекс
diff --git a/readme.md b/readme.md # наименование файла для которого перечисляются различия
--- a/readme.md # файл из индекса
+++ b/readme.md # файл из рабочего каталога
@@ -6,8 +6,8 @@ # границы Ханка, измененной части файла (число 6 - номер начальной строки ханка, число 8 - количество отбражаемых строк в этом ханке)
-index string # строка в файле из индекса
+work_dir string # строка в файле из рабочей директории
@@ -45,3 +45,3 @@ # второй и последующие ханки

git diff --word-diff # отображает конкретные слова, которые были изменены

git diff --staged # выводит различия между закоммиченными файлами и файлами из индекса
git diff --cached # синоним предыдущей команды

git diff branch1 master # сравнее двух веток, первым указывают приемник (префикс -), вторым - источник (префикс +)

git diff commit1 commit2 # сравнение коммитов

git restore

Команда для востановления измененных файлов. Отслеживает процесс изменения файлов хранящихся в индексе и рабочей директории, и при необходимости позволяет востанавливать версию из индекса или объектной БД гита

git restore
git restore <file-name> # востанавливает версию файла хранящуюся в индексе гита - помещает ее в рабочую директорию
git restore --staged <file-name> # востанавливает версию файла хранящуюся в объектной БД гита (на момент последнего коммита) - помещает ее в индекс

git rm

Команда, которая позволяет удалять отслеживаемые гитом файлы из индекса и рабочего каталога. Из объектной БД файлы не удаляются, потому что они закоммичены. Что бы внести изменения в объектную БД необходимо выполнить новый коммит.

git rm
git rm <file-name> # удаляет отслеживаемые файлы из индекса и рабочей директории
git rm -r <dir-name> # удаляет каталог с файлами

# Файл можно удалить и средствами ОС, но в этом случае они не будут удалены из индекса. Придется использовать дополнительные команды для обновления индекса, что иррационально.
rm <file-name> # удаляем файл средствами ОС
git add -u <file-name> # Обновляем информацию об удаленном файле в индексе

git mv

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

git mv
git mv <old-file-name> <new-file-name>
# переименовывает отслеживаемый файл, первым аргументом указывается старое имя файла, вторым - новое.

Указатель на текущее местоположение в истории коммитов. По умолчанию указывает на последний коммит рабочей ветки. HEAD можно использовать для ссылки на коммиты относительно текущего местоположения, это делается с помощью оператора тильда ~. HEAD~n указывает на предка n-го поколения.

При работе с коммитами слияния, у которых два родителя, используют оператор вставки ^.

HEAD^1 - первый родитель, является последним коммитом в базовой ветке

HEAD^2 - второй родитель, является последним коммитом вливаемой ветки.

Важно понимать: коммит на который указывает HEAD будет родителем следующего коммита.

git diff HEAD
# Для обращения к родительским коммитам используют тильду ~
git diff HEAD~1 HEAD # показывает разницу между последним и предпоследним коммитом.

# У коммита слияния два родителя, для обращения к ним используют оператор вставки ^
git diff HEAD^1~1 HEAD^1 # сравнивает родительский коммит из базовой ветки с его родителем

git checkout

База данных гита представляет собой историю коммитов, и по этой истории можно перемещаться, например для того, чтобы посмотреть на состояние закоммиченных файлов в определенный момент времени. Для этого существует команда git checkout

При этом гит переходит в состояние detached HEAD, если в этот момент внести изменения и сделать новый коммит - этот коммит легко потеряется.

git checkout
git checkout <commit ID> # переключает состояние рабочего каталога и индекса на момент конкретного коммита в прошлом
# При этом гит переходит в состояние detached HEAD, если в этот момент внести изменения и сделать новый коммит - этот коммит легко потеряется.
# Поэтому отсюда есть 2 корректных выхода:
# 1. Посмотрели что хотели и возвращаемся обратно в нашу ветку
git switch - # происходит переключение на кончик предыдущей ветки
# 2. Хотим внести изменения: сначала создаем новую ветку, потом вносим изменения
git switch -c <new-branch-name> # создаем и переключаемся на новую ветку, вносим изменения, коммитим их, вливаем в основную ветку

git reset

Команда для удаления коммитов.

git reset перемещает указатель HEAD на указанный коммит, при этом существует 3 варианта отмены коммита:

  • git reset --soft - отменяет коммит, копирую измененные файлы в индекс и каталог. Состояние гита откатывается на шаг который был перед последним коммитом, т.е гит перемещает изменения из комимита в индекс и рабочий каталог.
  • git reset (--mixed) - отменяет коммит, при этом измененный файлы из отмененного коммита помещаются в рабочий каталог, а в индекс помещается состояние коммита, на который теперь указывает HEAD
  • git reset --hard - отменяет коммит, и удаляет измененный файлы из всех мест: объектной БД, в которой хранятся коммиты, индекса и рабочего каталога. Состояние всех трех пространств хранения гита становится одинаковым и соответствует состоянию HEAD-коммита.
git reset
git reset --soft HEAD~1 # Откатываемся на коммит назад, помещая отмененные изменения в индекс, их можно заново закомитить
git reset HEAD~1 # Откатываемся на коммит назад, помещая отмененные изменения в рабочий каталог, их можно заново закомитить, предварительно добавив в индекс
git reset --hard HEAD~1 # Откатываемся на коммит назад, безвозвратно удаляя сделанные изменения.

git reset позволяет откатываться на несколько коммитов назад за один раз, например git reset HEAD~4, при этом все измененные файлы из отменяемых коммитов будут помещены в рабочий каталог.

git revert

Можно отменить последний комит с помощью "антикоммита". Команда git revert интвертирует указанный коммит и записывает эти инвертированые изменения в новый коммит.

git revert
git revert HEAD # создает новый коммит в который записывает инверсию последнего коммита.
# фактически отменяет последний коммит, но не удаляет его, а создает новый. Таким образом неудачный коммит остается в истории

git cherry-pick

Команда для копирования коммита из одной ветки в другую. Позволяет добавить измененный коммит из одной ветки в другую не прибегая к слиянию

git cherry-pick
git cherry-pick <commit ID> # копирует указанный коммит в текущую ветку, на которой находится HEAD 

git tag

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

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

git tag
git tag v1.2.3 # создает тэг в текущем коммите
git tag V3.2.1 <commit-ID> # создает тэг, который указывает на определенный коммит

git tag -l # список всех тэгов

git stash

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

Когда их можно применить? Например, пользователь работал над изменениями и перед тем как коммитить их заметил, что находится не в той ветке. В такой ситуации можно: спрятать измененные файлы в тайник, переключится на нужную ветку, достать файлы из тайника и произвести коммит в правильной ветке.

git stash
git stash # помещаем изменения в тайник, получается псевдокоммит (рабочей директории и индекса), который храниться в отдельном месте
git stash pop --index # достаем последний псевдокоммит из тайника и помещаем в текущее местоположение