# Git

## Git

## Содержание

* [Начало работы](#начало-работы)
  * [Начальная настройка окружения](#начальная-настройка-окружения)
  * [Инициализация репозитория (init)](#инициализация-репозитория)
* [Основы](#основы)
  * [Клонирование репозитория (clone)](#клонирование-репозитория)
  * [Просмотр текущего состояния (status)](#просмотр-текущего-состояния)
  * [Подробный просмотр файлов при изменении (diff)](#подробный-просмотр-файлов-при-изменении)
  * [Добавление в отслеживаемые файлы (add)](#добавление-в-отслеживаемые-файлы)
  * [Фиксация изменений (commit)](#фиксация-изменений)
  * [Игнорирование файлов (.gitignore)](#игнорирование-файлов)
  * [Удаление файлов (rm)](#удаление-файлов)
  * [Перемещение или переименовывание файлов (mv)](#перемещение-или-переименование-файлов)
  * [История коммитов (log)](#история-коммитов)
  * [Работа с удалёнными репозиториями (remote)](#работа-с-удалёнными-репозиториями)
  * [Отправка данных на сервер (push)](#отправка-данных-на-сервер)
  * [Получение данных с сервера (fetch, pull)](#получение-данных-с-сервера)
  * [Тэги (tag)](#тэги)
  * [Псевдонимы (alias)](#псевдонимы)
  * [Настройка репозитория (config)](#настройка-репозитория)
  * [Именование коммитов](#именование-коммитов)
  * [Полезные ссылки](#полезные-ссылки)
* [Ветвление](#ветвление)
  * [Создание и просмотр веток (branch)](#создание-и-просмотр-веток)
  * [Смена ветки (checkout, switch)](#смена-ветки)
  * [Слияние веток (merge)](#слияние-веток)
  * [Перемещение веток (rebase)](#перемещение-веток)
  * [Сравнение слияния и перемещения веток](#сравнение-слияния-и-перемещения-веток)
  * [Модели ветвления](#модели-ветвления)
    * [GitFlow](#gitflow)
    * [GitHubFlow](#githubflow)
    * [GitLabFlow](#gitlabflow)
    * [Trunk Based Development](#trunk-based-development)
* [Инструменты Git](#инструменты-git)
  * [Редактирование коммитов (rebase, commit)](#редактирование-коммитов)
    * [Редактирование последнего коммита (commit)](#редактирование-последнего-коммита)
    * [Редактирование сообщения нескольких коммитов (rebase)](#редактирование-сообщения-нескольких-коммитов)
    * [Объединение коммитов (rebase)](#объединение-коммитов)
    * [Смена коммитов местами (rebase)](#смена-коммитов-местами)
    * [Варианты использования (rebase)](#варианты-использования)
  * [Отмена коммитов (reset)](#отмена-коммитов)
  * [Отмена изменений при уже отправленном коммите (revert)](#отмена-изменений-при-уже-отправленном-коммите)
  * [Перенос указателя ветки (branch, checkout)](#перенос-указателя-ветки)
  * [Восстановление файла к определённому коммиту (restore)](#восстановление-файла-к-определённому-коммиту)
  * [Откладывание изменений (stash)](#откладывание-изменений)
  * [Копирование коммитов (cherry-pick)](#копирование-коммитов)
  * [Подключение к репозиторию по ssh](#подключение-к-репозиторию-по-ssh)
  * [Подмодули (submodule)](#подмодули)
  * [Подпись коммитов](#подпись-коммитов)
  * [Повторное слияние (rerere)](#повторное-слияние)
* [Поиск ошибок](#поиск-ошибок)
  * [Быстрый поиск плохого коммита (bisect)](#быстрый-поиск-плохого-коммита)
  * [Просмотр кто написал эту строку (blame)](#просмотр-кто-написал-эту-строку)
  * [Поиск по истории коммитов (grep)](#поиск-по-истории-коммитов)
* [Администрирование](#администрирование)
  * [Сборка мусора и оптимизация репозитория (gc)](#сборка-мусора-и-оптимизация-репозитория)
  * [Очистка проекта от лишних файлов (clean)](#очистка-проекта-от-лишних-файлов)
  * [Журнал действий (reflog)](#журнал-действий)
  * [Массовое изменение истории (filter-branch)](#массовое-изменение-истории)
  * [Целостность объектов (fsck)](#целостность-объектов)
  * [Запуск сервера (instaweb)](#запуск-сервера)
* [Внутреннее строение Git](#внутреннее-строение-git)
  * [Blob](#blob)
  * [Tree](#tree)
  * [Commit](#commit)
  * [Tag](#Tag)
  * [References](#references)
* [Низкоуровневые команды](#низкоуровневые-команды)
  * [Просмотр объекта](#просмотр-объекта)

## Начало работы

### Начальная настройка окружения

[Наверх](#содержание)

Для начальной конфигурации репозитория требуется установить имя и email для репозитория. Опция `--global` используется для настройки всех репозиториев текущего пользователя.

```
git config --global user.name "<your-name>"
git config --global user.email "<your-email>
```

### Инициализация репозитория

[Наверх](#содержание)

Инициализация репозитория git в текущей директории, в результате будет создана специальная директория `.git` со всеми настройками git-репозитория.

```
git init
```

## Основы

### Клонирование репозитория

[Наверх](#содержание)

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

```
git clone <url>
```

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

```
git clone <url> <path>
```

Для клонирования определённой ветки, нужно указать через флаг `-b`.

```
git clone -b <branch-name> <url>
```

*Примеры:*

Клонирование репозитория по https.

```
git clone https://github.com/torvalds/linux.git
```

Клонирование в определённую директорию.

```
git clone https://github.com/torvalds/linux.git ./my-linux
```

Клонирование определённой ветки в определённую директорию.

```
git clone -b new_linux https://github.com/torvalds/linux.git ./my-linux
```

### Просмотр текущего состояния

[Наверх](#содержание)

Проверка текущего состояния всех файлов.

```
git status
```

При любом изменении состояния файлов, и их содержимым, *status* будет выдавать всю подробную информацию.

Если требуется получить краткий отчёт можно воспользоваться опцией `--short` или `-s`.

```
git status -s
```

Так же рядом с файлами будут показаны в каком сейчас файлы формате.

* ?? - неотслеживаемый файл.
* A - добавлен в отслеживаемые файлы.
* M - модифицированный файл.
* D - удалённый файл.
* R - переименованный файл.

### Подробный просмотр файлов при изменении

[Наверх](#содержание)

Для просмотра изменений в файлах используется `diff`.

Опции:

* `staged` - посмотреть что из проиндексированного войдёт в коммит.
* `check` - проверка пробелов в конце строки. (если они будут, тогда будут подсвечиваться красным).

*Примеры:*

Просмотр изменений файлов от последнего коммита.

```
git diff
```

Просмотр проиндексированных изменений.

```
git diff --staged
```

Просмотр изменений определённого файла.

```
git diff <file-name>
```

Проверка на наличие лишних пробелов в коде.

```
 git diff --check
```

Сравнение изменений веток.

```
git diff <branch-1> <branch-2>
```

Сравнение изменений определённого файла между ветками.

```
git diff <branch-1> <branch-2> <file-name>
```

Сравнение по коммиту.

```
git diff <hash-commit-1> <hash-commit-2>
```

Сравнение изменений определённого файла между коммитами.

```
git diff <hash-commit-1> <hash-commit-2> <file-name>
```

### Добавление в отслеживаемые файлы

[Наверх](#содержание)

Для того чтобы начать отслеживать новый файл.

```
git add <file-name>
```

Если же добавляется директория, то все файлы находящиеся в ней будут так же добавлены в отслеживаемые.

Добавление всех изменений в репозитории.

```
git add -A
```

Добавление только созданных и измененных файлов, а не удаленных.

```
git add .
```

Добавление только удаленных и измененных файлов, а не новых созданных.

```
git add -u
```

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

```
git add -p <path-file>
git add -p .
```

* `y` - разместить этот фрагмент.
* `n` - не размещать этот фрагмент.
* `q` - выйти; не размещать этот фрагмент или любой из оставшихся.
* `a` - разместить этот фрагмент и все последующие фрагменты в файле.
* `d` - не размещать этот фрагмент и все последующие фрагменты в файле.
* `e` - вручную отредактировать текущий фрагмент.
* `/` - поиск фрагмента.
* `j` - оставить этот фрагмент неопределенным, посмотреть следующий неопределенный фрагмент.
* `J` - оставить этот фрагмент нерешённым, перейти на следующий фрагмент.
* `k` - оставить этот фрагмент неопределенным, смотреть на предыдущий неопределенный фрагмент.
* `K` - оставить этот фрагмент нерешенным, смотреть на предыдущий фрагмент.
* `s` - разбить текущий кусок на более мелкие куски.
* `e` - вручную отредактировать текущий фрагмент.
* `?` - показать справку.

*Примеры:*

Добавление файла.

```
git add README.md
```

Добавление директории и находящийхся в ней файлов.

```
git add docs/
```

Добавление только одного файла в директории.

```
git add docs/Git.md
```

### Фиксация изменений

[Наверх](#содержание)

Фиксация измений.

```
git commit
```

После команды откроется текстовый редактор, установленный по умолчанию. Где требуется дать комментарий коммиту.

Если нужна более подробная информация об изменениях.

```
git commit -v
```

Так же можно задать сообщение об фиксации и в самой команде, без открытия текстового редактора.

```
git commit -m "<comment>"
```

Выполнение коммита со всеми изменениями в рабочем каталоге.

```
git commit -a
```

Для изменения сообщения последнего коммита используется флаг `--amend`.

```
git commit --amend
```

Изменение сообщения последнего коммита, без редактора.

```
git commit --amend -m "<new-comment>"
```

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

```
git commit -m "<old-commit>"            # создание коммита
git add <file-name>                     # добавление изменений
git commit --amend -m "<new-commit>"    # фиксация в последний коммит
```

Если был добавлен или изменён файл, и при этом не требуется менять имя коммита дополнительно используется флаг `--no-edit`.

```
git commit --amend --no-edit
```

Для создания коммита с определённой датой используется флаг `--date`.

```
git commit --date="<your-date>"
```

Для создания коммита с определённым автором используется флаг `--autor`.

```
git commit --author="Name <exemple_email@exemple.com>"
```

Создание пустого коммита - коммита без изменений.

```
git commit --allow-empty -m "<message>"
```

*Примеры:*

Коммит файла.

```
git add file.txt
git commit -m "add file.txt"
```

Добавление файла в последний коммит.

```
git add file.txt
git commit --amend --no-edit
```

Создание коммита с определённой датой (27.10.2022 16:31:15).

```
git commit --date="2022-10-27 16:31:15"
```

### Игнорирование файлов

[Наверх](#содержание)

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

Команда для создания такого файла.

```
touch .gitignore
```

Составление `.gitignore` файла.

```
# комментарий – это игнорируется
*.a # пропускаются файлы, заканчивающиеся на .a
*.[oa] # пропускаются файлы, заканчивающиеся на .a или .o
!lib.a # при указанном *.a, файл lib.a, будет всё равно отслеживаться
/file # игнорируется корневой файл
build/ # игнорировать все файлы в этой директории
doc/*.txt # игнорируются все файлы .txt в директории doc, но не в doc/.../file.txt
dir/**/debug.log # ** - Две звездочки соответствуют множеству каталогов или ни одному
```

Так же можно найти список файлов [.gitignore](https://github.com/github/gitignore) для языков и проектов.

Для просмотра файлов которые игнорируются в текущем проекте.

```
git ls-files --others --ignored --exclude-standard
```

Можно создать глобальный gitignore.

```
git config --global core.excludesFile ~/.gitignore
```

### Удаление файлов

[Наверх](#содержание)

Команда `git rm` позволяет удалить файл и сразу добавить это его в индекс.

```
git rm <file-name>
```

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

```
git rm --cached <file-name>
```

Если файл был изменён, но всё равно требуется его удалить, то добавляется опция `--force` или `-f`.

```
git rm -f <file-name>
```

Если нужно посмотреть какие файлы будут удалены, но не удалять их, то используется опция `--dry-run` или `-n`.

```
git rm --dry-run <file-name>
```

*Примеры:*

Удаление всех файлов с расширением .txt.

```
git rm *.txt
```

### Перемещение или переименование файлов

[Наверх](#содержание)

Переименование файла или директории.

```
git mv <old-file-name> <new-file-name>
```

Перемещение файла или директории.

```
git mv <file-name> <path-to-file-name>
```

*Примеры:*

Переименование файла.

```
git mv Git.md Docker.md
```

Перемещение файла в другую директорию.

```
git mv Git.md docs/Git.md
```

### История коммитов

[Наверх](#содержание)

Команда для просмотра истории коммитов. Первыми выводятся самые свежие коммиты.

```
git log
```

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

```
git log -n <count-commits>
```

Для получения краткой характеристики используется опцию `--stat`.

```
git log --stat
```

Самое подробное представление истории коммитов. Показывает какие изменения были сделаны в каждом файле.

```
git log -p
```

Показывает изменённые файлы в данном коммите.

```
git log --name-status
```

Поиск по коммитам.

```
git log --grep="<pattern>"
```

Показывает коммиты по определённому автору.

```
git log --author="<author>"
```

Параметр `--pretty` определяет формат вывода информации.

```
git log --pretty=oneline
```

Стандартные параметры для pretty.

* oneline
* short
* full
* fuller

Так же можно определять свой вывод информации.

```
git log --pretty=format:"%h - %an, %ar : %s"
```

Самые распространённые параметры форматирования.

| Параметр | Описание                                       |
| -------- | ---------------------------------------------- |
| %H       | Хэш-код коммита                                |
| %h       | Сокращенный хэш-код коммита                    |
| %T       | Хэш-код дерева                                 |
| %t       | Сокращенный хэш-код дерева                     |
| %P       | Хэш-код родительских коммитов                  |
| %p       | Сокращенный хэш-код родительских коммитов      |
| %an      | Имя автора                                     |
| %ae      | Электронная почта автора                       |
| %ad      | Дата создания оригинала                        |
| %ar      | Дата создания оригинала, в относительной форме |
| %cn      | Имя создателя версии                           |
| %ce      | Электронная почта создателя                    |
| %cd      | Дата создания версии                           |
| %cr      | Дата создания версии в относительном формате   |
| %s       | Комментарий                                    |

Вывод коммитов с историей ветвлений и слияний.

```
git log --graph
```

Очень удобная команда, которая выводит дерево всех коммитов.

```
git log --all --decorate --oneline --graph
```

Команда для просмотра что было изменено в определённом коммите.

```
git show <hash-commit>
```

### Работа с удалёнными репозиториями

[Наверх](#содержание)

Отображение удалённых репозиториев.

```
git remote
```

Посмотреть адреса, для сокращенного имени.

```
git remote -v
```

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

```
git remote add <remote-server-name> <url>
```

Удаление подключения к удаленному репозиторию с именем.

```
git remote rm <remote-server-name>
```

Переименование удаленного подключения с имени.

```
git remote rename <old-name> <new-name>
```

Изменение адреса, для определённого имени.

```
git remote set-url <remote-server-name> <url>
```

Просмотр удалённого репозитория.

```
git remote show <remote-server-name>
```

### Отправка данных на сервер

[Наверх](#содержание)

Отправка данных на удалённый сервер. Если имя удалённого сервера origin, при отправке можно не указывать. Так же если вы находитесь в ветке, которую требуется отправить, то её можно тоже не указывать.

```
git push <remote-server-name> <branch-name>
```

Удаление удалённой ветки с сервера.

```
git push <remote-server-name> --delete <branch-name>
```

### Получение данных с сервера

[Наверх](#содержание)

Команда `git fetch` - скачивает коммиты в специальные ветки.

Для просмотра удалённых веток.

```
git branch -a
```

Ветки будут помечены по стандарту красным цветом и выглядят - `remotes/<remote-server-name>/<branch-name>`.

Извлечение всех веток из репозитория.

```
git fetch <remote-server-name>
```

Извлечение определённой ветки.

```
git fetch <remote-server-name> <branch-name>
```

Извлечение всех зарегистрированных удаленных репозиториев и их веток.

```
git fetch --all
```

Опция `--dry-run` выводит на экран действия, которые были бы выполнены при извлечении, не выполняя их на самом деле.

```
git fetch --dry-run
```

Если после загрузки с определённого сервера появилась новая ветка, можно её переместить в свою ветку.

```
git checkout -b <branch-name> <remote-server-name>/<branch-name>
```

Команда `git pull` - это git fetch + git merge. При её использовании происходит скачвание данных с сервера и автоматичское слияние.

Скачать данные с удалённого сервера.

```
git pull <remote-server-name> <branch-name>
```

Если нужно скачать текущую ветку и с сервера origin.

```
git pull
```

При отправке данных `git push`, может произойти конфликт, что на удалённом репозитории уже были добавлены коммиты. Для этого нужно сначала скачать изменения, и только потом отправить их.

```
git pull <remote-server-name> <branch-name>
git push <remote-server-name> <branch-name>
```

Если были локальные коммиты, и на удалённом сервере тоже были сделаны коммиты, то при обычном `pull`, будет сделан мердж-коммит. Чтобы избавиться от него, требуется использовать опцию `--rebase`. При этой опции локальный коммит окажется поверх новых коммитов с сервера, а мердж-коммита не будет.

Если идёт командная работа, то лучше всегда использовать опцию `--rebase`, при взятии изменений.

```
git pull --rebase <remote-server-name> <branch-name>
```

### Тэги

[Наверх](#содержание)

Тэги позволяют помечать определённые коммиты как важные. Как правило,используют это для пометки выходящих версий (v1.0 и т.п.).

Просмотр всех тегов.

```
git tag
```

Просмотр тегов по шаблону.

```
git tag -l "v1.*"
```

Есть 2 типа тегов, легковесные и аннотируемые теги. Легковесные это просто указатель на определённый коммит. Аннотируемые теги хранятся в базе уже как полноценный объект. Они содержат имя человека, поставившего тег, адрес его электронной почты и дату создания и пр.

Создание легковесных тегов.

```
git tag <tag-name>
```

Создание аннотируемых тегов. Если не указать опцию `-m` то открывается редактор, и чтобы ввести тег.

```
git tag -a <tag-name> -m "<comment>"
```

Просмотр определённого тега.

```
git show <tag-name>
```

Так же тэг можно поставить на определённый коммит.

```
 git tag -a <tag-name> <hash-commit> -m "<commit-tag>"
```

Отправление тегов на удалённый сервер.

```
git push origin <tag-name>
```

Отправка нескольких тегов сразу.

```
git push origin --tags
```

Удаление тега.

```
git tag -d <tag-name>
```

### Псевдонимы

[Наверх](#содержание)

Можно задавать псевдонимы определённым командам.

Как пример можно задать длинным командам их псевдонимы.

```
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
```

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

Например, вывод последнего коммита.

```
git config --global alias.last "log -1 HEAD"
```

### Настройка репозитория

[Наверх](#содержание)

Посмотреть все опции команды.

```
git config
```

Есть 3 уровня конфигурации:

* `--local` - локальный уровень, стоит по умолчанию, применяется к одному текущему репозиторию.
* `--global` - глобальный уровень, применяется к пользователю для всех его репозиториев.
* `--system` - системный уровень, применяется ко всем настройкам машины. Охватывает всех пользователей операционной системы.

Глобальные настройки для определённого пользователя обычно лежит по пути `~/.gitconfig`.

Команда для просмотра заданных уже настроек.

```
git config --list
```

Команда для просмотра определённой настройки.

```
git config user.name
```

Настройка имени пользователя.

```
git config --global user.name "<name>"
```

Настройка email адреса пользователя.

```
git config --global user.email "<email-address>"
```

Выбор редактора кода по умолчанию.

```
git config --global core.editor <editor>
```

При команде git pull очень часто используется опция `--rebase`, настройка для автоматического использования этой опции.

```
git config --global pull.rebase true
```

Автоматическое исправление опечаток, будет автоматически предложена команда с исправлением.

```
git config --global help.autocorrect 1
```

Возможность сохранить имя и пароль в незашифрованном виде и использовать его при отправке коммитов на удалённый сервер.

```
git config --global credential.helper store
```

Команда `git init` по стандарту создаёт ветку `master`, если требуется изменить создаваемую ветку по стандарту.

```
git config --global init.defaultBranch <branch-name>
```

Моя настройка для удобного отображения дерева коммитов.

```
git config --global alias.tree "log --all --decorate --oneline --graph"
```

### Именование коммитов

[Наверх](#содержание)

История коммитов это очень важная документация, которая документирует историю работы разработчков.

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

В начале коммита устанавливается тип коммита.

* `test` - указывает на любое создание или изменение кода тестов.
* `feat` - указывает на разработку новой функции для проекта.
* `refactor`- используется, когда происходит рефакторинг кода, не влияющий на логику/правила системы.
* `style` - используется при изменениях форматирования и стиля кода, которые никак не меняют систему.
* `fix` - используется при исправлении ошибок, которые порождают баги в системе.
* `chore` - указывает на изменения в проекте, которые не влияюкт на систему или тестовые файлы. Это изменения, связанные с разработкой. Примеры: изменение правил для eslint, добавление prettier, добавление расширений файлов в .gitignore.
* `docs` - используется при изменениях в документации проекта.
* `build` - используется для указания изменений, которые влияют на процесс сборки проекта, или внешних зависимостей.
* `perf` - указывает на изменение, которое улучшает производительность системы.
* `ci` - используется для указания на изменения в конфигурационных файлах CI.
* `revert` - указывает на отмену предыдущего коммита. Так же тип коммита позволяет более удобно использовать поиск по коммитам.

### Полезные ссылки

[Наверх](#содержание)

* [Интерактивный учебник направленный на закрепление теории](https://learngitbranching.js.org/).

## Ветвление

### Создание и просмотр веток

[Наверх](#содержание)

Ветвление в git одна из самых важный знаний, для работы с репозиторием. Ветка - это указатель на определённый коммит.

Показать текущие ветки.

```
git branch
```

Создать ветку от текущей ветки.

```
git branch <new-branch>
```

Создание ветки от определённой ветки.

```
git branch <new-branch> <base-branch>
```

Если требуется указать ветку на определённый коммит.

```
git branch <branch-name> <hash-commit>
```

Если ветка уже создана нужно использовать флаг `-f`.

```
git branch -f <branch-name> <hash-commit>
```

Безопасное удаление ветки, если ветка не смёрджена, то она не будет удалена.

```
git branch -d <branch-name>
```

Принудительное удаление ветки.

```
git branch -D <branch-name>
```

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

```
git branch -v
```

Показать на сколько опережает или отстаёт ветка от удалённого репозитория.

```
git branch -vv
```

Информация о всех ветках, включая удалённые.

```
git branch -a
```

Просмотр удалённых веток.

```
git branch -r
```

Так же можно использовать опции `--merged` - показывает ветки объединённые с текущей, и `--no-merged` - ветки которые не слиты в другие ветки.

```
git branch --merged
git branch --no-merged
```

*Примеры:*

Создание ветки.

```
git branch feature
```

Создание ветки на определённый коммит.

```
git branch feature fd85dd7
```

Удаление ветки.

```
git branch -d feature
```

### Смена ветки

[Наверх](#содержание)

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

```
git checkout <branch-name>
```

Смена веток так же приводит к смене файлов в рабочей директории.

Можно создать ветку и перейти в неё с помощью одной команды.

```
git checkout -b <new-branch>
```

Так же существует более новая команда для смены ветки `switch`.

Смена ветки.

```
git switch <branch-name>
```

Создать ветку и перейти в неё.

```
git switch -c <branch-name>
```

*Примеры:*

Создание ветки.

```
git checkout -b feature
```

Смена ветки.

```
git checkout master
```

Перемещение на определённый коммит.

```
git checkout 977baff
```

### Слияние веток

[Наверх](#содержание)

Слияние - соединение двух веток.

Есть 2 типа слияния:

1. fast-forward - слияние перемоткой.

Если была история веток, где от одной ветки сделана другая, и в этой ветки не было других коммитов.

```
o---o  master
     \
      o---o---o---o---o  feature
```

Тогда при слиянии будет сделана перемотка коммитов и получится такая история при слиянии.

```
o---o---o---o---o---o---o  master,feature
```

2\) 3 way merge - трёхэтапное слияние.

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

```
o---o---o---o---o  master
     \
      o---o---o---o---o  feature
```

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

Итог слияния:

```
o---o---o---o---o-------o  master, feature
     \                 /
      o---o---o---o---o
```

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

```
git checkout <branch-name>
git merge <merged-branch-name>
```

Конфликты при слиянии.

Процесс слияния далеко не всегда проходит гладко. Если в двух ветках, которые сливаются, были внесли разные изменения в один и тот же файл, Git не cможет просто взять и объединить их. При слиянии появляется конфликт.

Чтобы посмотреть какие файлы имеют конфликт.

```
git status
```

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

Пример:

```
<<<<<<< HEAD
qwerty
=======
123456
>>>>>>> new
```

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

Если написать `git commit` то git самостоятельно сгенерирует комментарий о мерже и останется только подтвердить коммит.

При возникновении конфликта можно отменить слияние.

```
git merge --abort
```

Если при мердже будет ускоренное слияние, но для истории коммитов требуется вариант 3 way, тогда можно использовать опцию `--no-ff`. Будет создан коммит слияния, без перемотки.

```
git merge --no-ff <branch-name>
```

До слияния.

```
o---o  master
     \
      o---o---o---o---o  feature
```

После слияния.

```
o---o-------------------o  master, feature
     \                 /
      o---o---o---o---o
```

Если в ветке было много коммитов, и эти коммиты не требуется отображать в релизе. Можно применить флаг `--squash`. После этого потребуется указать общий коммит для слияния.

```
git merge --squash <branch-name>
```

### Перемещение веток

[Наверх](#содержание)

`Перемещение` - перенос всех коммитов определённой ветки, с одного коммита на коммит/коммиты выше от наследуемой ветки.

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

```
git checkout <rebased-branch-name>
git rebase <branch-name>
```

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

```
git rebase <branch-name> <rebased-branch-name>
```

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

Переход на следующий коммит.

```
git rebase --continue
```

После перемещение ветки, можно выполнить перемотку ветки куда происходило перемещение.

```
git checkout <branch-name>
git merge <rebased-branch-name>
```

Если ветка больше не нужна, её можно удалить.

```
git branch -d <rebased-branch-name>
```

При возникновении конфликта можно отменить перемещение.

```
git rebase --abort
```

*Пример:*

Начальная история версии.

```
 o---o---o---o---o  master
      \
       o---o---o---o  feature
```

Для перемещения требуется перейти в ветку feature.

```
git checkout feature
```

И запустить перемещение.

```
git rebase master
```

Если произошли конфликты решить их.

После этого история будет выглядеть так.

```
 o---o---o---o---o  master
                  \
                   o---o---o---o  feature
```

Если требуется переместить указать master на feature.

```
git checkout master
git merge feature
```

Дальше стало так.

```
 o---o---o---o---o---o---o---o---o  feature, master
```

И если ветка feature не нужна - удалить её.

```
git branch -d feature
```

Для трудного перемещения используется флаг `--onto`.

```
o---o---o---o---o  master
     \
      o---o---o---o---o  feature_1
           \
            o---o---o  feature_2
```

feature\_2 основан на feature\_1, и для того чтобы переместить feature\_2 к master.

```
git rebase --onto master feature_1 feature_2
```

Дерево веток после перемещения.

```
                  o---o---o  feature_2
                 /
o---o---o---o---o  main
     \
      o---o---o---o---o  feature_1
```

### Сравнение слияния и перемещения веток

[Наверх](#содержание)

Есть два вида соединения веток - слияние и перемещение, что лучше?

`rebase`:

Плюсы:

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

Минусы:

* Если работа ведётся не одним человеком, то может произойти ситуация когда один разработчики переместил ветку выше, а другой ещё не перешёл на неё и при этом сделал какие-то изменения в старой версии - очень трудно потом восстанавливаться.
* Возможны ошибки в коммитах - если в старой версии использовался метод *Hello*, а потом он изменился на *HelloSay* и при этом может не обнаружится этого, то все коммиты которые были перемещены будут "битые", так же на них будет очень плохо делать откат.

Это можно частично исправить если есть какие-либо тесты. Их можно пртестировать перед перемещением, и если будет ошибка то об этом будет сообщаться, и rebase будет ждать действий для исправления.

```
git rebase -x '...' master
```

`merge`:

Плюсы:

* При мердже и не плохом коммите, будет повреждён только один коммит, и при этом оставшиеся коммиты будут целы.

Минусы:

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

Использовать `rebase` и `merge` нужно в правильных ситуациях. Считается, что rebase для приватных веток, когда изменения не выложены, и не будет конфликтов с другими разработчиками при перемещении. А merge лучше использовать когда уже над веткой работают несколько разработчиков.

### Модели ветвления

#### GitFlow

[Наверх](#содержание)

* `main / master` - Ветка, где один коммит является релизом выпускаемый в prod. Обычно на него могут давать тег.
* `develop / dev` - Ветка, для ведения разработки и объединение всех функций.
* `feature` - Ветки, которые начинаются от develop, и в них разработаевается какая-то новая фича.
* `release` - Ветка, для подготовки релиза, перед пушем в main.
* `hotfix` - Ветка предназначена для быстрого исправления ошибок в main.

Пример:

```
o-----------------------------------o  main
 \                                 /
  \                       o---o---o  release
   \                     /         \
    o---o---------------o-----------o  dev
         \             /
          o---o---o---o  feature
```

После создания ветки release и слияния с main, требуется также слить изменения в dev.

Если в main ветке происходит критическая ошибка, то требуется создать ветку hotfix. После изменений, изменения требуется слить и в main и в develop.

```
o-----------o  main
 \         /
  o-------o  hotfix
   \       \
    o-------o  dev
```

#### GitHubFlow

[Наверх](#содержание)

Облегчённая версия GitFlow, отсутствуют ветки dev, release, hotfix.

`main / master` - являются конечными ветками.

Пример:

```
o---------------o  main
 \             /
  o---o---o---o  feature
```

От main создаётся ветка feature, в которой разрабатывается новая фича, и после этого сразу идёт слияние в main.

#### GitLabFlow

[Наверх](#содержание)

Очень похожый вариант с GitHubFlow, но ветка main, не является конечной веткой.

Появляются такие ветки:

* `staging` - опциональная ветка, первый рубеж тестирования.
* `pre-production` - опциональная ветка, второй рубеж тестирования.
* `production` - обязательная ветка, из которой происходит деплой проекта.

Пример:

```
o-------------------o prod
 \                 /
  o---------------o  main
   \             /
    o---o---o---o  feature
```

#### Trunk Based Development

[Наверх](#содержание)

Полностью схожая идея с GitHubFlow, только ветки feature должны иметь жизнь не более 1-2 дней.

## Инструменты Git

### Редактирование коммитов

#### Редактирование последнего коммита

[Наверх](#содержание)

Для редактирования сообщения последнего коммита.

```
git commit --amend
```

Если требуется добавить изменения каких-то файлов в последний коммит.

```
git add <file-name>
git commit --amend
```

Если требуется добавить и при этом не изменять сообщение коммита.

```
git add <file-name>
git commit --amend --no-edit
```

#### Редактирование сообщения нескольких коммитов

[Наверх](#содержание)

Для редактирования нескольких коммитов используется команда `rebase`.

Команда для того чтобы отредактировать более ранний коммит или коммиты.

```
git rebase -i HEAD~10
```

В данном варианте будут выведены на экран последние 10 коммитов.

Так же можно задать диапазон коммитов.

```
git rebase -i <hash-commit-1> <hash-commit-2>
```

Дальше открывается текстовый редактор, где можно указать с какие коммиты нужно отредактировать. Коммиты будут отображаться в обратном порядке (в отличии от git log) - сначала старые, потом новые.

```
pick 8754de7 1 commit
pick 408c784 2 commit
pick ec467ea 3 commit
pick ec1f912 4 commit
```

`pick` - использовать данный коммит, т.е. он будет использоваться как он есть.

Чтобы отредактировать сообщение нужно `pick` заменить на `reword`.

```
pick 8754de7 1 commit
reword 408c784 2 commit
reword ec467ea 3 commit
pick ec1f912 4 commit
```

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

Если же выбрать `edit`, то будет происходить переход на каждый коммит, и мможно будет вручную отредактировать комммит и/или изменить файлы.

После редактирования коммита можно перейти на следующий коммит.

```
    git rebase --continue
```

#### Объединение коммитов

[Наверх](#содержание)

Сначала нужно вывести коммиты, которые нужно объединить.

```
git rebase -i HEAD~5
```

Вывод команды.

```
pick 8754de7 1 commit
pick 408c784 2 commit
pick ec467ea 3 commit
pick ec1f912 4 commit
pick 8871386 5 commit
```

Для того чтобы объединить коммиты нужно заменить `pick` на `squash` у более позднего коммита, для объединения с более старым.

Если требуется обединить коммиты `2 commit` и `3 commit`, требуется у коммита `3 commit` поменять pick на squash.

```
pick 8754de7 1 commit
pick 408c784 2 commit
squash ec467ea 3 commit
pick ec1f912 4 commit
pick 8871386 5 commit
```

Или, если нужно объеденить несколько коммитов. В данном примере объединяются коммиты `2 commit`, `3 commit` и `4 commit`. Коммит будет объединяться с предыдущим своим коммитом.

```
pick 8754de7 1 commit
pick 408c784 2 commit
squash ec467ea 3 commit
squash ec1f912 4 commit
pick 8871386 5 commit
```

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

Всё, что помечено "#", является комментарием.

Было:

```
# This is a combination of 3 commits.
# The first commit's message is:

commit 1

# This is the 2nd commit message:

commit 2

# This is the 3rd commit message:

commit 3
```

Стало:

```
new commit squash
```

#### Смена коммитов местами

[Наверх](#содержание)

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

```
git rebase -i HEAD~3
```

И внутри уже просто поменять местами коммиты которые нам нужны.

Было:

```
pick 1e09d8d commit 1
pick a747cf7 commit 2
pick 914847a commit 3
```

Стало:

```
pick 1e09d8d commit 1
pick 914847a commit 3
pick a747cf7 commit 2
```

Но если в коммитах были какие-то общие изменения, то будет конфликт, который нужно будет решить.

Либо же можно отменить изменения.

```
git rebase --abort
```

#### Варианты использования

[Наверх](#содержание)

Все варианты команд для rebase.

* `p, pick` - использовать коммит.
* `r, reword` - использовать коммит, но отредактировать сообщение фиксации.
* `e, edit` - использовать коммит, но остановить для внесения поправок.
* `s, squash` - использовать коммит, но объединить с предыдущим коммитом.
* `f, fixup` - как "сквош", но отбросить сообщение журнала этого коммита.
* `x, exec <command>` - выполнить команду (остальная часть строки) с помощью оболочки.
* `b, break` - остановиться здесь.
* `d, drop` - удалить коммит.
* `l, label <label>` - пометить текущую HEAD.
* `t, reset <label>` - сбросить HEAD на метку.

### Отмена коммитов

[Наверх](#содержание)

reset - универсальный инструмент для отмены изменений.

По умолчанию при вызове команды `git reset` используются неявные аргументы `--mixed` и `HEAD`.

Поэтому эти две команды одинаковы.

```
git reset
git reset --mixed HEAD~
```

* `--soft` - отменяются коммиты, до указанного, но при этом файлы, которые были изменены в этих коммитах, остаются и все их изменения. И так же они добавлены в отслеживаемые файлы.
* `--mixed` - отменяются коммиты, до указанного, изменённые файлы сохраняются, но не добавлены в отслеживаемые.
* `--hard` - отменяются коммиты, до указанного, и при этом файлы не сохраняются.

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

```
git reset --hard HEAD~1
git push --force
```

*Примеры:*

Откатить 2 последних коммита, и оставить изменения.

```
git reset --soft HEAD~2
```

или

```
git reset --mixed HEAD~2
```

Удалить 2 последних коммита.

```
git reset --hard HEAD~2
```

### Отмена изменений при уже отправленном коммите

[Наверх](#содержание)

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

Для того чтобы исправить такую ситуацию, можно использовать `revert`. Он делает новый коммит который отменяет все сделанные изменения. С одной стороны это очень плохо - загрязняет историю, но с другой, может быть одним из выходов в данной ситуации.

Отмена последнего коммита.

```
git revert HEAD
```

Отменить любой коммит можно по хэшу коммита, но может случиться конфликт, который потребуется решить самостоятельно.

```
git revert <hash-commit>
```

Можно отменять несколько коммитов.

```
git revert <hash-commit-1>..<hash-commit-3>
```

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

```
git revert -n <hash-commit>
```

### Перенос указателя ветки

[Наверх](#содержание)

Принудительный перенос ветки на определенный коммит.

```
git branch -f <branch-name> <hash-commit>
```

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

```
 git checkout -B <branch-name-1> <branch-name-2>
 git checkout -B <branch-name> <hash-commit>
```

### Восстановление файла к определённому коммиту

[Наверх](#содержание)

Если файл был удалён или изменён и требуется вернуть его к последнему коммиту.

```
git restore <file-name>
```

Для возвращение файла к определённому коммиту.

```
git restore --source=<hash-commit> <file-name>
```

Чтобы убрать файл из отслеживаемых файлов, но не убирать его изменения.

```
git restore --staged <file-name>
```

### Откладывание изменений

[Наверх](#содержание)

Если были сделаны изменения в файлах, и нужно изменить ветку, при этом не сделав коммит, можно воспользоваться операцией `stash`. Операция stash берёт изменённые файлы каталога и сохраняет их в хранилище, которые можно применить в любое время.

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

```
git stash
```

Чтобы посмотреть список припрятанных изменений.

```
git stash list
```

Так же можно добавить сообщение при откладывании изменений.

```
git stash -m "<message>"
```

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

```
git stash apply stash@{<index>}
```

C опцией `--index`, команда попытается восстановить изменения в индексе.

```
git stash apply --index stash@{<index>}
```

Для удаления изменения из хранилища.

```
git stash drop stash@{<index>}
```

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

```
git stash pop stash@{<index>}
```

При опции `--keep-index` будут припрятаны изменения, но и при этом будут оставлены в индексе.

```
git stash --keep-index
```

При опции `--include-untracked` или `-u`, будут спрятаны так же и все не отслеживаемые файлы.

```
git stash --include-untracked
git stash -u
```

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

```
git stash --patch
```

Так же можно изменения вынести в новую ветку.

```
git stash branch <new-branch-name> stash@{<index>}
```

Удаление всех спрятанных изменений.

```
git stash clear
```

### Копирование коммитов

[Наверх](#содержание)

Команда `cherry-pick` позволяет копировать коммиты из другой ветки и вставить его в текущую.

Копирование одного коммита в текущую ветку.

```
git cherry-pick <hash-commit>
```

Копирование нескольких коммитов. Коммит `<hash-commit-1>` должен быть старше коммита `<hash-commit-n>`.

```
git cherry-pick "<hash-commit-1>..<hash-commit-n>"
```

При этом первый коммит не будет учитываться.

Для того чтобы он учитывался, требуется прописать `^` после первого коммита.

```
git cherry-pick "<hash-commit-1>^..<hash-commit-n>"
```

Если требуется изменить сообщение об фиксации.

```
git cherry-pick --edit <hash-commit>
```

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

```
git cherry-pick --continue
```

Отмена копирование коммитов.

```
git cherry-pick --abort
```

### Подключение к репозиторию по ssh

[Наверх](#содержание)

Сначала требуется сгенерировать ssh ключ. Сначала попросят ввести имя ssh ключа, его можно оставить по умолчанию. А дальше попросят ввести кодовую фразу, можно не вводить и просто нажать enter.

```
ssh-keygen
```

Ключ обычно хранится в директории \~/.ssh, по умолчанию его имя id\_rsa. Генерируется два ключа открытый - id\_rsa.pub, и закрытый - id\_rsa. Сообщать можно только открытый ключ.

Для того чтобы передать ssh ключ github, нужно зайти на github.com, Settings, SSH and GPG keys, New SSH Key, в Title - ввести описание ключа, Key - ввести публичный ключ. И после этого можно пушить коммиты без постоянного потверждения.

Если GitHub репозиторий изначально был подключен по web URL, то требуется его поменять на ssh URL.

```
git remote set-url <remote-server-name> <ssh-url>
```

### Подмодули

[Наверх](#содержание)

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

Добавление подмодуля.

```
git submodule add <url-repository>
```

При клонировании репозитория с подмодулями.

```
# для инициализации локального конфигурационного файла
git submodule init
# для получения всех данных этого проекта
git submodule update
```

Но можно сделать это более просто, передать git clone флаг `--recurse-submodules`.

```
git clone --recurse-submodules <url-repository>
```

Команда git pull получает изменения для подмодулей, но она не обновляет подмодули. Поэтому нужно обновить подмодули, и лучше запускать submodule update с параметром `--init`, чтобы проинициализировать новые подмодули, а так же с параметром `--recursive`, чтобы обновить вложенные подмодули.

```
git pull
git submodule update --init --recursive
```

Если установить параметр конфигурации status.submodulesummary, при git status будет так же показываться сводка по подмодулям.

```
git config status.submodulesummary 1
git status
```

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

```
git config -f .gitmodules submodule.Repoitory.branch <branch-name>
```

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

При значении `check`, push просто завершится ошибкой, если какой-то из подмодулей не был отправлен на сервер. И тогда нужно будет вручную проходиться и оправлять изменения на сервер.

```
git push --recurse-submodules=check
```

При значении `on-demand`, будет пытаться отправлять самостоятельно без помощи пользователя.

```
git push --recurse-submodules=on-demand
```

Так же можно установить такое поведение по умолчанию.

```
git config push.recurseSubmodules check
# или
git config push.recurseSubmodules on-demand
```

### Подпись коммитов

[Наверх](#содержание)

Для подписи коммитов используется утилита `gpg`.

Для того чтобы получить список имеющихся GPG-ключей.

```
gpg --list-keys
```

Для генерации ключа.

```
gpg --gen-key
```

При генерации нужно написать своё имя, email адрес. И далее нужно установить пароль для защиты ключа, так же можно оставить поле пустым и не указывать пароль.

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

```
gpg --full-gen-key
```

Для того чтобы подписи коммитов были автоматически.

```
git config --global commit.gpgsign true
```

Если ключей несколько, можно установить определённый ключ.

```
git config --global user.signingkey <hash-pub-key>
```

Для загрузки открытого ключа на GitHub, нужно его экспортировать. Для записи его на GitHub нужно перейти в Settings, и далее SSH and GPG keys.

```
gpg --export --armor <hash-key> > ~/gpg-key.pub
```

Команда для сохранения резервной копии привтного ключа.

```
gpg --export-secret-keys --armor <hash-key> > ./gpg-key.asc
```

Для подписи тегов вместо флага `-a` используется флаг `-s`.

```
git tag -s <tag-name> -m "<comment>"
```

Так же при просмотре тега видно подпись.

```
git show <tag-name>
```

Для проверки подписанного тега. Для ее корректной работы нужно, чтобы в хранилище ключей присутствовал открытый ключ автора, поставeвшего подпись.

```
git tag -v <tag-name>
```

Для подписи коммитов используется флаг `-S`.

```
git commit -a -S -m "<comment>"
```

Для просмотра и проверки подписей есть параметр `--show-signature`.

```
git log --show-signature
```

Команды `merge` и `pull` можно заставить проверять и отклонять слияния, если коммит не содержит доверенной GPG подписи с помощью опции `--verify-signatures`.

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

```
git merge --verify-signatures <branch-name>
```

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

Так же если использовать флаг `-S` при слиянии, то коммит так же будет подписан.

```
git merge --verify-signatures -S <branch-name>
```

Для удаления ключа используется сначала удаление приватного ключа.

```
gpg --delete-secret-keys <name-user>
```

А после можно удалить уже сам ключ.

```
gpg --delete-keys <name-user>
```

Подписывать коммиты должен каждый, но при этом каждый коллега в команде должен уметь подписывать их.

### Повторное слияние

[Наверх](#содержание)

Команда `git rerere` - это сокращение для reuse recorded resolution (повторно использовать сохранённое решение).

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

Разрешённые конфликты хранятся в директории `./git/rr-cache`.

Команда для включения rerere.

```
git config --global rerere.enabled true
```

Просмотр какие снимки состояния сохранены.

```
git rerere status
```

Показать текущее состояние разрешения конфликта.

```
git rerere diff
```

Конфигурация.

* `gc.rerereResolved` - как долго хранятся записи о разрешенных ранее конфликтных слияниях. По умолчанию это 60 дней.
* `gc.rerereUnresolved` - как долго хранятся записи о неразрешенных конфликтах слияния. По умолчанию это 15 дней.

## Поиск ошибок

### Быстрый поиск плохого коммита

[Наверх](#содержание)

Если в какой-то момент оказалось, что какие-то тесты не закпускаются или какая-то часть кода перестала правильно работать, но при этом было сделано довольно много коммитов, отыскать где была допущена ошибка может быть пробематично. Команда `bisect` позволяет найти очень быстро плохой коммит.

Для старта поиска нужно запустить bisect.

```
git bisect start
```

Дальше требуется указать хороший коммит и плохой коммит.

```
git bisect bad <hash-commit>
git bisect good <hash-commit>
```

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

Сказать что в данном коммите не работает нужный код.

```
git bisect bad
```

Сказать что в данном коммите работает нужный код.

```
git bisect good
```

Остановить поиск, и вернуться к начальной точке.

```
git bisect reset
```

Просмотр последнего успешно выполненного bisect.

```
git bisect log
```

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

Сначала нужно запустить bisect и указать хороший или плохой коммит. А далее запустить автоматическую проверку коммитов.

```
git bisect run <script>
```

### Просмотр кто написал эту строку

[Наверх](#содержание)

Для полного просмотра кто написал каждую строчку в файле.

```
git blame <file-name>
```

Опция `-L` позволяет указать диапазон строк.

```
git blame -L 1,5 <file-name>
```

Опция `-e` позволяет отобразить адреса электронной почты авторов вместо имен пользователей.

```
git blame -e <file-name>
```

Опция `-M` позволяет находить перемещённые или скопированные строки внутри одного и того же файла. Указываться будет первоначальный автор строк.

```
git blame -M <file-name>
```

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

```
git blame -C README.md
```

### Поиск по истории коммитов

[Наверх](#содержание)

Для поиска строк по коммитам используется команда `git grep`. git grep принимает многие ключи, что и утилита grep в Linux.

Для поиска строки в git репозитории.

```
git grep <search-string>
```

Для того чтобы показать на какой строке найденный паттерн, используется опция `-n` или `--line-number`.

```
git grep -n <search-string>
```

Для того чтобы показать количество найденных параметров, используется опция `-c` или `--count`.

```
git grep -c <search-string>
```

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

```
git grep --break --heading <search-string>
```

Поиск строки включая git подмодули:

```
git grep --recurse-submodules <search-string>
```

Поиск строки в файлах, подходящих под указанный шаблон:

```
git grep <search-string> -- <search-files>
```

Поиск по строке внутри указанного коммита

```
git grep <search-string> <hash-commit>
```

`-e` - поиск по нескольким параметрам.

```
git grep -e <search-string-1> -e <search-string-2>
```

Так же можно использовать дополнительные параметры `--and`, `--or` и `--not`, для сочетания нескольких шаблонов.

Если нужно найти когда было добавлено или удалено выражение можно использовать `git log` c флагом `-S`.

```
git log -S <search-string>
```

Так же есть поиск по журналу изменений строки. Для этого используется git log с флагом `-L`.

Можно либо указать диапазон строк и файл `<start>,<end>:<file>`, либо по регулярному выражению и файлу `:<pattern>:<file>`.

*Примеры:*

```
git log -L 4,6:file_test

git log -L :pattern:file_test
```

## Администрирование

### Сборка мусора и оптимизация репозитория

[Наверх](#содержание)

`git gc` занимается сборкой мусора - удаление ненужных файлов из хранилища объектов и эффективное упаковывание файлов.

Ручной запуск оптимизации репозитория.

```
git gc
```

Проверка требуется ли какое-то обслуживание.

```
git gc --auto
```

Некоторые команды могут самостоятельно запускать эту команду. Для отключения этой опции.

```
git config --global gc.auto 0
```

`--aggressive` - опция заставит git более агрессивно оптимизировать репозиторий за счет гораздо большего времени.

`--prune=<date>` - Удаление незакрепленных объектов старше даты (по умолчанию 2 недели назад). `--prune=now` - удаляет свободные объекты независимо от их возраста.

Конфигурация.

`gc.pruneExpire` - насколько старыми должны быть незакрепленные объекты, на которые нет ссылок, прежде чем они будут удалены. По умолчанию 2 недели назад.

### Очистка проекта от лишних файлов

[Наверх](#содержание)

Команда `clean` предназначена только для удаления неотслеживаемых файлов и директорий.

Популярные опции для `clean`

* -d - удалялись не только файлы но и директории
* -x - удалялись файлы, которые игнорируются через .gitignore
* -f - принудительное удаление
* -n - предварительный запуск, показывает какие файлы будут удалены

### Журнал действий

[Наверх](#содержание)

Git хранит все изменения, которые происходили в репозитории. Любые перемещения в ветки, перебазирование, удаление, всё это сохраняется в истории git.

Команда для просмотра изменений которые были совершены.

```
git reflog
```

Вывод изменений определённой ветки.

```
git reflog <branch-name>
```

Так же можно вывести в привычном формате reflog.

```
git log -g
```

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

```
git checkout <hash-reflog>
```

Либо откатиться до этого изменения

```
git reset <hash-reflog>
```

Так же можно эти изменения перенести в ветку для удобства.

```
git branch <recover-branch> <hash-reflog>
```

Данные о reflog остаются только локально, и при отправке на удалённый сервер они не будут сохранены на нём.

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

Конфигурация.

`gc.reflogExpire` - как долго записи в журнале ссылок каждой ветки должны оставаться доступными. По умолчанию он составляет 90 дней.

`gc.reflogExpireUnreachable` - как долго записи журнала ссылок, не являющиеся частью текущей ветки, должны оставаться доступными. Этот параметр по умолчанию равен 30 дням.

### Массовое изменение истории

[Наверх](#содержание)

`filter-branch` - эта команда предназначена для перезаписи истории коммитов. Это очень мощная утилита и имеет множество подводных камней, которые могут привести к неочевидным искажениям предполагаемой перезаписи истории.

Опции.

* `--prune-empty` - при удалении файла из коммита, может случится такое, что в коммите ничего нет. Эта опция сделает так, чтобы пустые коммиты будут удалены.
* `--subdirectory-filter <directory>` - будет просмотр только истории, касающейся данного подкаталога.
* `--tree-filter <command>` - для перезаписи истории файлов в коммитах.
* `--index-filter <command>` - для перезаписи только индексированных файлов. Похож на tree-filter, но не проверяет дерево коммитов на не индексированные файлы, из-за этого работает быстрее. Очень часто использутся с `git rm --cached --ignore-unmatch ...`.
* `--env-filter <command>` - применяется если нужно изменить только данные, в которой выполняется фиксация. Можно переписать автора/коммитера/электронная почта/время и т.п.
* `--msg-filter <command>` - для перезаписи сообщений фиксации.
* `--tag-name-filter <command>` - для перезаписи имён тегов. Оригинальные теги не удаляются, но могут быть перезаписаны. `--tag-name-filter cat`, опция для того чтобы обновить теги.
* `--commit-filter <command>` - для перезаписи комиитов.

Переменные по умолчанию:

* GIT\_COMMIT
* GIT\_AUTHOR\_NAME
* GIT\_AUTHOR\_EMAIL
* GIT\_AUTHOR\_DATE
* GIT\_COMMITTER\_NAME
* GIT\_COMMITTER\_EMAIL
* GIT\_COMMITTER\_DATE

При запуске `filter-branch`, для всех веток, которые эта команда затронет, будет создана копия по пути `refs/original/refs/heads/<branch-name>`. Это сделано для того чтобы после массового изменения истории, можно было просмотореть изменённую версию, и при ошибке откатиться обратно.

Один из вариантов удаление одной ветки.

```
git update-ref -d <path-for-branch>
```

Один из вариантов удаление всех веток.

```
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
```

Один из вариантов отмены одной ветки.

```
# Возращение на прошлую ветку
git update-ref <path-for-branch> <branch-name>

# Удаление бекап ветки
git update-ref -d <path-for-branch>
```

Один из вариантов отмены всех изменений.

```
# Возращение на прошлую ветку
git for-each-ref --format="update %(refname:lstrip=2) %(objectname)" refs/original/ | git update-ref --stdin

# Удаление прошлой ветки
git for-each-ref --format="delete %(refname) %(objectname)" refs/original/ | git update-ref --stdin
```

*Примеры:*

Удаление какого-то файла во всех ветках.

```
git filter-branch --tree-filter "rm -f <file-name>" -- --all
```

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

Такое же удаление файла, только через index-filter.

```
git filter-branch --index-filter "git rm --cached --ignore-unmatch <file-name>" -- --all
```

Удаление коммитов одного человека.

```
git filter-branch --commit-filter '
    if [ "$GIT_AUTHOR_NAME" = "KotDimos" ];
    then
        skip_commit "$@";
    else
        git commit-tree "$@";
    fi' -- --all
```

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

```
git filter-branch --env-filter '
    GIT_AUTHOR_NAME=KotDimos
    GIT_AUTHOR_EMAIL=KotDimos@gmail.com
    GIT_COMMITTER_NAME=KotDimos
    GIT_COMMITTER_EMAIL=KotDimos@gmail.com
    ' b63eabe..feature
```

Где b63eabe более ранний коммит ветки feature.

Возращение master на старую версию.

```
# Возращение на прошлую ветку
git update-ref refs/original/refs/heads/master master

# Удаление бекап ветки
git update-ref -d refs/original/refs/heads/master
```

### Целостность объектов

[Наверх](#содержание)

`git fsck` - Проверяет подключение и действительность объектов в базе данных.

Опции:

* `--unreachable` - показать объекты, которые существуют, но недоступны ни для одного из эталонных узлов.
* `--name-objects` - отображается имя, описывающее, как они доступны.
* `--dangling` - показать объекты, которые существуют, но никогда не используются напрямую.
* `--verbose` - более подробная история объектов.
* `--lost-found` - записывает висячие объекты в `.git/lost-found/commit/` или `.git/lost-found/other/`, в зависимости от типа.

### Запуск сервера

[Наверх](#содержание)

`git instaweb` - Программа для запуска gitweb сервера.

Опции.

* `-l / --local` - привязать сервер только к локальному IP адресу.
* `--start` - запустить сервер для текущего репозитория.
* `--restart` - перезапустить сервер.
* `--stop` - остановить сервер.
* `-d / --httpd` - использовать указанный httpd демон. Поддерживаются apache2, lighttpd, mongoose, plackup, python и webrick. По умолчанию lighttpd.
* `-p / --port` - порт для привязки httpd. По умолчанию 1234.
* `-b / --browser` - браузер для просмотра веб страницы.

*Примеры:*

Запустить сервер.

```bash
git instaweb --start
```

Перезапустить запущенный сервер.

```bash
git instaweb --restart
```

Остановить запущенный сервер.

```bash
git instaweb --stop
```

Запустить сервер на порте 4321.

```bash
git instaweb --start --port 4321
```

Открыть веб страницу в google chrome.

```bash
git instaweb --browser chrome
```

## Внутреннее строение Git

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

Объекты бывают четырёх типов - blob, tree, commit, tag.

### Blob

[Наверх](#содержание)

`Blob` (Binary Large Object) - это основа репозитория. Содержимое репозитория, без имени. Если добавить в git файл с содержимым, то результирующий blob-объект будет содержать только содержимое, без данных об имени файла.

Для просмотра того что хранит blob.

```
git cat-file -p <hash-blob>
```

### Tree

[Наверх](#содержание)

`Tree` (дерево) - Объект этого типа решает проблему хранения имен файлов, а также позволяет хранить группы файлов.

Простмотр данных дерева.

```
git cat-file -p 94937a87cb0903787b7b517ea5f97f53b5492655
```

*Примеры:*

Дерево содержит в себе несколько blob объектов.

```
100644 blob db092f7d9e4ac9b4ae7ae5006e5dfa977c429d72	file1.txt
100644 blob 31c23dbefbf2605a0bf89022901da02b41e2af2e	file2.txt
100644 blob 5f5cc9d7ec9d0a46ede7f4f6de95cf317b8b19de	file3.txt
```

Так же дерево может содержать в себе другие деревья.

```
040000 tree aa7a4e54e8a9f75ced1cf720defda5067bd916fe	src
040000 tree bf59e83160c15371efcc209c5ebdbc7981c735ca    docs
```

И смешанно.

```
040000 tree aa7a4e54e8a9f75ced1cf720defda5067bd916fe	src
100644 blob db092f7d9e4ac9b4ae7ae5006e5dfa977c429d72	file1.txt
```

Права в первом столбце.

* 1 цифра - 1 для файлов и символических файлов, 0 для директорий.
* 2 цифра - 0 для файлов, 2 для символических ссылок, 4 для директорий.
* 3 цифра - не используется, всегда 0.
* 4-6 цифры - POSIX права.

### Commit

[Наверх](#содержание)

`Commit` - фиксация изменения одного дерева, коммит также хранит информацию о "предках" этого дерева, то есть ссылки на родительские коммиты. Можно считать, что коммит указывает на деревья из каких произошло текущее дерево, а также автор коммита и сообщение коммита.

У самого первого коммита в репозитории не может быть предков. Он считается начальным коммитом, и считается что до него ничего не было.

Бывает, что несколько коммитов происходят от одного предка. В этом месте в истории появляется "развилка", история начинает делиться на ветви. Это происходит когда от одного коммита идёт 2 ветки.

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

Вывод типа объекта.

```
$ git cat-file -p 475eeda53bc3f0291e7ea4a970247d3916c033d2

commit
```

Вывод самого объекта.

```
$ git cat-file -p 475eeda53bc3f0291e7ea4a970247d3916c033d2

tree a43f53d26a405f95caa823d7ae458a17cb4015f1
parent 83618fc75a2ff53d73ea0abe3ab6ca4e994d6776
author KotDimos <KotDimos@gmail.com> 1662372846 +0300
committer KotDimos <KotDimos@gmail.com> 1662372846 +0300

commit message
```

### Tag

[Наверх](#содержание)

Теги бывают двух типов.

* Аннотированные (Тяжеловесные).
* Легковестные.

Аннотированный тег это специальный объект, который содержит в себе все данные которые требуются тегу.

Просмотр тега.

```
$ git cat-file -p annotaded-tag

object 51f0522b99cf3590675e2f3bf76b58c7914e3efc
type commit
tag annoteded-tag
tagger KotDimos <KotDimos@gmail.com> 1665228654 +0300

tag message
```

Легковестный тег же это просто указатель на коммит, и при просмотре его он покажет коммит.

### References

[Наверх](#содержание)

Символьные имена объектов - ссылка (references), хранятся в каталоге `.git/refs`.

Типы:

1. Теги (tag) - статическая ссылка на коммит. Расположены в `.git/refs/tags/`
2. Ветка (head, branch) - динамическая ссылка на коммит, которая меняется при добавлении нового коммита в цепочку. Расположены в `.git/refs/heads/`
3. Удаленные ветки (remote) - ветка специального вида, которая предназначена для слежения за ветками (heads) в других репозиториях. Лежат в `.git/refs/remotes/`

Кроме этого, есть несколько специальных ссылок, которые по историческим соображениям лежат вне каталога `.git/refs`. Наиболее важными являются `HEAD`, `ORIG_HEAD` и `MERGE_HEAD`.

`HEAD` - это особая ссылка, она показывает на коммит, который соответствует рабочей копии. Если быть точным, это не просто ссылка на коммит, это ссылка на текущую ветку.

## Низкоуровневые команды

### Просмотр объекта

Команда `git cat-file` нужна для просмотра содержимого или информации о типе и размере для объектов репозитория.

Опции.

* `-t` - показать тип объекта.
* `-s` - показать размер объекта.
* `-e` - если объект существует, выходит с нулевым статусом, иначе выдаётся ошибка.
* `-p` - показать содержимое объекта.
* `--allow-unknown-type` - Разрешить `-s` или `-t` запрашивать сломанные/поврежденные объекты неизвестного типа.

*Примеры:*

Просмотреть тип объекта.

```
git cat-file -t 5713fb3
```

Вывести содержимое объекта.

```
git cat-file -p 5713fb3
```

Вывести размер объекта.

```
git cat-file -s 5713fb3
```
