README.md

Помощь

Помощь по любой команде можно получить используя

git help <command> или
git <command> -h

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

Файл настроек хранится в .git/config

Вызов помощи:

git config -h

Установка имени и email:

git config user.name "Mike"
git config user.email "my@email.org"
  • –system,

  • –global (per user),

  • –local (default)

Посмотреть параметры конфигурации:

git config --list

Алиасы

Для создания алиаса можно воспользоваться командой

git config --global alias.<your-cmd-name> <git-command-with-flags>
git config --global alias.<your-cmd-name> '!<bash-command-with-flags>'

Пример:

git config alias.ls '!ls -la'

Отмена интерактивности

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

Пример: git -P <далее команда git>

Работа с репозиторием

Структура работы с git

Рабочий каталог - текущие состояние проекта в локальном каталоге разработчика
Индекс - специальная область внутри .git каталога, в которой подготовлены (проиндексированы) файлы для их помещения в репозиторий при последующем коммите
Репозиторий - файлы в специальной области .git каталога, находящиеся под версионным контролем, которые попадают туда при операции commit.
HEAD - указатель, где мы сейчас находимся: на какой ветке, на каком коммите.

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

Инициализация репозитория: git init.
В результате чего, создается папка .git

Добавление в индекс

По-умолчанию git ничего не помещает в индекс, пока мы ему не скажем об этом.
Для добавления файлов в индекс нужна команда:

git add <имя файла или каталога>

Для добавления текущего каталога, можно использовать:

git add .

Для добавления всех файлов текущего каталога, за исключением тех, которые начинаются с точки (dot), можно использовать:

git add *

Для добавления файла или каталога, который исключён в .gitignore нужно добавить ключи -f или --force:

git add -f <file-or-folder>

Для файла или каталога добавленного таким способом все изменения будут отслеживаться независимо от .gitignore.

Пустые каталоги

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

Выборочное добавление изменений в индекс

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

git add -p <file-or-folder>

Добавление в репозиторий (commit)

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

git commit

При этом будет запущен текстовый редактор для редактирования коммит-сообщения.

При написании коммит сообщения в редакторе при коммите, можно просмотреть разницу diff внизу редактора. Это бывает удобно, т.к. позволяет убедиться, что мы коммитим то, что нужно. Сам этот diff не войдет в коммит. Для этого нужно указать ключ -v.

git commit -v

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

git commit -m 'Commit message'

Можно переопределить автора и дату:

git commit -m "commit_message" --author='John Smith <john@email.com>' --date='...'

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

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

git commit -am 'Commit message'

или более длинно, но с интерактивным коммит-сообщением:

git commit --all

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

git commit -m "commit_message" <file-or-folder-name>

Права файлов

При добавлении файлов в репозиторий, git отслеживает права и записывает их в репозиторий: 644 - для обычных файлов и 755 – для исполняемых.

Изменение атрибутов файлов: исполняемый или нет, является существенным для git

Права файлов можно изменить в обход локальной системы, отредактировав их в индексе с помощью команды:

git update-index --chmod=+x <filename>

Если файла нет в индексе и мы его только добавляем, то для редактирования прав можно воспользоваться командой:

git add --chmod=+x <filename>

Просмотр содержимого коммитов (git show)

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

git show --pretty=fuller
# или
git show --pretty=medium

Просмотр разницы изменений в индексе и репозитории (git diff)

Для просмотра разницы между тем, что в индексе и HEAD, можно воспользоваться командой:

git diff --staged

Просмотр разницы в индексе и рабочем каталоге (git diff)

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

git diff

Просмотр разницы между HEAD и рабочим каталогом

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

git diff HEAD

Просмотр разницы между двумя коммитами (git diff)

Для просмотра разницы между двумя коммитами:

# Сравнение текущей ветки и другого коммита, м.б. в другой ветке
git diff HEAD <sha-коммита>

# Сравнение произвольной ветки и другого коммита, м.б. в другой ветке
git diff <имя-ветки> <sha-коммита>

# Найти разницу между файлами на момент первого и второго коммита
git diff <sha-коммита-1> <sha-коммита-2>

Просмотр изменений сделанных в тематической ветке (git diff)

Чтобы посмотреть, какие изменения были сделаны в тематической ветке с момента отхождения её от родителя:

# что изменилось в тематической ветке с момента отхождения её от родителя
git diff <имя-ветки-родителя>...<имя-тематической-ветки>

# что изменилось в `feature-1` с момента её отпочкования от `develop`
git diff develop...feature-1

Просмотр изменений сделанных в родительской ветке (git diff)

Чтобы посмотреть, а что уже произошло в родительской ветке, с момента её расхождения (с общего коммита) с текущей веткой:

git diff <имя-ветки>...<имя-ветки-родителя>

git diff <sha-ветки>...<sha-ветки-родителя>

# Что изменилось в `develop` с момента отпочкования от неё ветки `feature`
git diff feature...develop

Просмотр изменений в конкретном файле (git diff)

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

# Какие изменения были сделаны в файле по сравнению с индексом
git diff <имя-файла>

Для сравнения файла в HEAD и рабочей директорией можно воспользоваться командой:

# Какие изменения были сделаны в файле по сравнению с версией файла в HEAD
git diff HEAD <имя-файла>

Для сравнения файла в разных ветках можно воспользоваться командой:

# Какие изменения были сделаны в файле между разными ветками
git diff <имя-ветки-1> <имя-ветки-2> <имя-файла>

# Какие изменения были сделаны для перечисленных файлов
# между разными ветками.
# Т.е сравнить содержимое файлов по одному и тому же пути в разных ветках
git diff <имя-ветки-1> <имя-ветки-2> <имя-файла1> <имя-файла2> <имя-файла3>

# сравнить два файла в двух разных коммитах
# имя ветки и путь к файлу могут быть какие угодно
git diff <имя-ветки1>:<путь-к-файлу1> <имя-ветки2>:<путь-к-файлу2>

Найти изменившиеся файлы в разных ветках (git diff)

Для того, чтобы найти, какие файлы изменились в двух разных ветках:

#Перечислить имена отличающихся файлов между двумя ветками
git diff --name-only <ветка1> <ветка2>

# Найти, какие файлы изменились между веткой `develop` и `feature-1`
git diff --name-only develop feature-1

Статус репозитория

Для просмотра состояния проекта, под управлением git необходимо ввести команду

git status

Откат изменений в индексе (git reset/restore/checkout)

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

# сброс в индексе ошибочно проиндексированных файлов,
# с сохранением изменений в рабочем каталоге
git reset HEAD <file-or-folder>

# или короче
git reset <file-or-folder>

# очистка индекса для всего проекта, с сохранением изменений в рабочем каталоге
git reset HEAD

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

git restore --staged <file-or-folder>

Изменения останутся только в рабочем каталоге.

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

git checkout <sha-коммита> <file-or-folder>

Произойдет помещение в индекс и в рабочую директорию содержимого файла из указанного коммита в прошлом. Ветка (HEAD) при этом не двигается.

Откат изменений в рабочем каталоге (git reset/restore/checkout)

Для того, чтобы отменить изменения в рабочем каталоге, в конкретном файле можно вызвать команду:

git restore <файл>

# или

# данная команда приведет файл в рабочем каталоге как в HEAD (в репозитории)
git checkout HEAD <имя-файла>

# данная команда приведет файл в рабочем каталоге как в индексе
git checkout <имя-файла>

# Поместить содержимое файла из прошлого в индекс и в рабочую директорию.
# Ветка (HEAD) при этом не двигается.
git checkout <sha-коммита-из-прошлого> <file-or-folder>

Для интерактивного отката изменений в файле или каталоге можно вызвать команду:

git restore -p <файл-или-каталог>

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

# Откат всех отслеживаемых файлов
git reset --hard

# Удаление всех временных файлов и каталогов
git clean -fxd

Откат всех изменений в ветке (git reset/checkout)

Жесткий откат (hard)

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

git checkout -f HEAD
# если не указать HEAD, то он будет использован по умолчанию
git checkout -f

Т.к. HEAD указывает на текущую ветку, то произойдет откат всех изменений в текущей ветке: перемещение HEAD (если нужно), удаление индекса и изменений в рабочей директории.

Можно еще откатить все изменения в текущей ветке, в рабочей директории и в индексе через команду:

# откат на HEAD, с удалением изменений в индексе и рабочем каталоге
git reset --hard HEAD
# Если HEAD не указать, то он используется по умолчанию
git reset --hard

Бывает так, что ошибка закралась несколько коммитов назад. И нам необходимо вернуться, отменить все изменения, сделанные в ветке N коммитов назад, то есть “удалить” неверные коммиты без сохранения их данных. Для этого нужно:

# откат к произвольному коммиту  назад с "удалением" коммитов в ветке
git reset --hard <sha-коммита-куда-откатываемся>

# откат на 1 коммит назад с удалением последнего "неверного" коммита в ветке
git reset --hard  @~1

После команды git reset --hard, HEAD и текущая ветка будут указывать на один и тот же коммит в прошлом. При этом все незакоммиченные изменения пропадут. А коммиты, которые были после коммита на котором мы стоим, станут недостижимыми и будут со временем автоматически удалены (30 дней).

Если после отката изменений к предыдущим коммитам мы передумали и хотим вернуть как было (хотим опять уйти в “будущее”) и если не было других коммитов, то воспользуемся командой:

git reset --hard ORIG_HEAD

Ветка вернется в состояние, какой была до предыдущего отката.

Мягкий откат (soft)

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

# откат к произвольному коммиту  назад без удаления индекса и рабочей директории
git reset --soft <sha-коммита-куда-откатываемся>

# откат на 1 коммит назад без удаления индекса и рабочей директориим
git reset --soft  @~1
# или то же самое
git reset --soft  @~

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

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

После внесения исправлений можно создать новый коммит, но со старым описанием, с помощью команды:

# взять описание с коммита, который был до отката (если не было других коммитов)
# -c открыть редактор, -C взять описание коммита как есть,
# --reset-author обновить автора, а не брать его из ORIG_HEAD
git commit -c ORIG_HEAD

Замена последнего локального коммита или описания к нему

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

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

git commit --ammend

Эта команда делает два действия: reset --soft на один коммит назад, а затем добавляет новый коммит с исправлениями + описание из заменяемого коммита.

Перезапись текущего и удаленного репозитория

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

remote-overwrite.sh

# удалить каталог .git в локальном репозитории
rm -rf .git

# инициализация нового git-репозитория
git init

# добавление нужных файлов
git add .

# создание начального коммита
 git commit -m 'Initial commit'

# установка удаленного репозитория
git remote add origin <url-репозитория>

# перезапись удаленного репозитория, например для ветки master
git push --force --set-upstream origin master

Смешанный откат (mixed)

Смешанный откат нечто среднее между hard и soft откатами, описанными выше. Он перемещает ветку (HEAD), обновляет индекс, но при этом не трогает рабочую директорию, т.е. оставляет содержимое рабочей директории в состоянии коммита, с которого ушли.

# откат к произвольному коммиту, с оставлением рабочей директории как есть.
git reset --mixed <sha-коммита-куда-откатываемся>

# в reset ключ --mixed используется по умолчанию, и его можно не указывать
# откат HEAD на один коммит назад, при этом
# файлы в рабочей директории остались как были,
# то есть содержат не проиндексированные изменения.
git reset @~1

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

Если файл был физически удален в рабочем каталоге, то для того, чтобы сказать об этом git нужно добавить это изменение в индекс:

git add <удаленный-файл-или-каталог>

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

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

git rm <удаляемый-файл>

или для каталога:

git rm -r <удаляемый-каталог>

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

git rm --cached <удаляемый-из-индекса-файл>

Удаление неотслеживаемых и временных файлов

По умолчанию, git ничего не делает с не отслеживаемыми файлами. Для очистки рабочего каталога от временных и лишних файлов:

# Команда предназначена только для неотслеживаемых и временных файлов
# -d удаление директорий
# -x удаление тех, которые игнорируются через .gitignore
# -f принудительное удаление
git clean -f

# Удаление всех временных файлов и не нужных каталогов
git clean -fxd

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

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

git add .

Для того, чтобы переимновать файл и добавить в индекс в git есть специальная команда:

git mv <старое-имя-файла> <новое-имя-файла>

Ветвление

Наиболее часто используемые типы веток: тематические и релизные.
Тематические ветки посвящены разработке какой-либо функциональности.
Релизные ветки предназначены для ведения кодовой базы конкретной версии продукта.
Например, v1 это ветка, в которой нет развития и устраняются ошибки для 1 версии продукта. При этом master продолжает развитие и в какой-то момент от него будет ответвление v2, развитие которой тоже будет заморожено.

Ветка - это специальная ссылка на коммит. Физически информация о ветке хранится по пути .git/refs/heads/<название-ветки>. Внутри файла лежит хеш коммита.

Есть еще один важный файл: .git/HEAD, который хранит информацию о ветке, где мы сейчас находимся. HEAD буквально это указатель на вершину текущей ветки. Внутри файла .git/HEAD хранится ссылка на файл, хранящий ветку, в которой мы сейчас находимся .git/refs/heads/<название-ветки>.

Любой коммит содержит информацию о том, какой коммит является родительским.
Последний коммит в ветке называется вершиной.

Просмотр локальных веток

Для просмотра всех локальных веток можно использовать команду:

git branch -v

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

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

git branch <имя-новой-ветки>

При этом не произойдет автоматического перехода на новую ветку.

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

git checkout -b <имя-новой-ветки>

Переключение на другую ветку

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

git checkout <имя-существующей-ветки>

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

# Команда переключения на предыдущую ветку
git checkout -

Важно помнить, что checkout работает на чистом статусе. Если есть незакоммиченные изменения в отслеживаемых файлах, то checkout не сработает.

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

Тут есть два выхода:

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

  2. Сделать команду stash (положить изменения на “полку” без коммита) и после переключиться на другую ветку.

Для первого варианта можно выполнить команду:

git checkout -f <имя-существующей-ветки>

В этом случае checkout просто перезапишет все изменения данными из переключаемой ветки.

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

git stash
# или более длинный и предпочтительный вариант, с указанием сообщения
git stash push -m "custom message about work"
# для помещения в стэш неотслеживаемых файлов нужно добавить ключ -u

git checkout <имя-существующей-ветки>

Для возврата нужно выполнить команды:

git checkout <имя-предыдущей-ветки>

#  для просмотре списка стэшей
git stash list

# для выбора какой стэш накатить.  Например надо накатить stash@{0}
git stash pop stash@{0}

# если стэш единственный, то можно его сразу накатить без параметров и удалить стэш
git stash pop
# для восстановления из стэш неотслеживаемых файлов нужно добавить ключ -u

#если стэш нужно оставить, после того как его накатили, то можно использовать
git stash apply

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

Перенос незакоммиченных изменений

Бывает так, что случайно в ветке master или develop мы произвели изменения и вдруг это увидели. Изменения надо было делать в тематической ветке, а не в этих ветках.
Для переноса незакоммиченных изменений можно использовать команду:

git checkout -b <имя-новой-ветки>

В этом случае, все созданные изменения автоматически попадут в новую ветку.

Перенос закоммиченных изменений в другую ветку

Бывает так, что случайно в ветке master мы произвели несколько коммитов и вдруг это увидели. Коммиты надо было делать в тематической ветке, а не в этой ветке.

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

# создание новой ветки на текущем коммите
git branch <имя-новой-ветки>

# переходим на новую ветку
git checkout <имя-новой-ветки>

# передвигаем `master` на несколько коммитов назад, где он и должен быть
git branch -f master <sha-коммита-где-должна-быть-master>
# готово!

еще один вариант

# создание новой ветки на текущем коммите
git branch <имя-новой-ветки>

# переходим на новую ветку
git checkout <имя-новой-ветки>

# передвигаем `master` на несколько коммитов назад, где он и должен быть:
# автоматическое создание или перезапись существующей ветки + автоматическое переключение на `master` (ключ -B)
git checkout -B master <sha-коммита-где-должна-быть-master>
# готово!

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

Таким же образом, master можно передвинуть на любое место

# также `master` можно передвинуть и на реальную ветку
git branch -f master <имя-существующей-ветки>
# после этой команды `master` и существующая ветка будут указывать на один коммит

Копирование коммитов (cherry-pick)

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

# мы должны стоять в той ветке, куда хотим скопировать коммит из соседней ветки
git cherry-pick <sha-коммита-в-соседней-ветке>

# можно скопировать сразу несколько коммитов
git cherry-pick <sha-коммита1> <sha-коммита2>

# можно скопировать все коммиты из ветки feature которых нет в ветке develop
git cherry-pick develop..feature

# если возникли конфликты, то
# отменить, всё как было до cherry-pick
git cherry-pick --abort

# продолжить cherry-pick после разрешения конфликта
git cherry-pick --continue


# если хотим оставить те коммиты, которые удачно скопировались
# и не хотим копировать коммиты, которые привели к конфликту
git cherry-pick --quit

# если хотим перенести изменения, но не делать коммит, а подредактировать изменения, то нужно выполнить
git cherry-pick --no-commit
# эта команда добавит изменения в рабочую директорию и в индекс. После чего можно редактировать скопированные изменения.
# Таким образом cherry-pick позволяет не только копировать коммиты из других веток, но и просто взять из них изменения и применить к текущему состоянию проекта.

Переход HEAD на произвольный коммит

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

# двигаем `HEAD` на произвольный коммит в прошлое
git checkout <sha-коммита>
# после этой команды возникнет состояние "отделенный HEAD", который не будет указывать ни на одну из существующих веток

Это особое состояние, т.к. теперь HEAD не указывает ни на одну из существующих веток. И любые коммиты после этого будут расти в сторону ото всех веток, что на практике неудобно. Это будут недостижимые коммиты, которые git со временем удаляет (30 дней по умолчанию).

Для того, чтобы создать ветку, от текущего состояния нужно:

# создаем новую ветку для `HEAD`
git branch <имя-новой-ветки> <sha-текущего-коммита>

Чтобы поместить случайно созданные коммиты в состоянии отделенный HEAD в реальную ветку нужно:

# переходим на нужную ветку
git checkout <имя-существующей-ветки>

# переносим случайно созданный коммит в текущую ветку, используя его sha
git cherry-pick <sha-коммита-в-отделенном-HEAD>

Восстановление предыдущих версий файлов

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

# Восстанавливаем файл
git checkout <sha-коммита-где-файл-нормальный> <путь-к-файлу>
# после этой команды checkout не будет ничего делать с веткой, а только восстановит указанный файл
# при этом файл будет добавлен в индекс

# Восстанавливаем файл в текущей ветке в состояние которое было минус 2 коммита назад
git checkout @~2 <путь-к-файлу>

# Коммит запишет изменения из индекса в репозиторий (запишет восстановленный файл в репозиторий)
git commit

Просмотр истории коммитов и содержимого файлов в истории

Просмотр истории коммитов (git log)

Для просмотра истории коммитов:

# Подробная история коммитов
git log

# Краткая история коммитов: усеченный sha + заголовок коммит сообщения
git log --oneline

# то же самое с отменой интерактивности (удобная на мой взгляд)
git -P log --oneline

# Просмотр истории коммитов в ветке `master`
git -P log master --oneline

# Кастомное декорирование: sha | дата автора | строка заголовка | автор
git log --pretty=format:'%C(yellow)%h %C(auto)| %aI | %s | [%an]'
git log --pretty=format:'%C(yellow)%h %C(reset)| %cd | %s | [%an]' --date=local

# Просмотр истории вместе с diff
git log -p

# Просмотр истории коммитов где есть имя функции или другая
git log -p  -S<имя-функции-или-искомая-строка>
git log -p  -Sget-user-profile

# Просмотр достижимых коммитов из master и из develop
git log master develop

# Просмотр графа
git log --graph --oneline

Настройка красивых git log видов

Настройка git log в файле ~/.gitconfig

[alias]
lg1 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all

lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all

lg = !"git lg1"

Просмотр истории коммитов в ветке с момента её отхождения от родителя

# посмотреть коммиты в ветке fix с момента отхождения от ветки develop
git log fix ^develop

# или тоже самое по другому
git log develop..fix

# или тоже самое, если HEAD стоит на develop
git log HEAD..fix

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

# посмотреть коммиты в ветке develop, которых нет в fix
git log fix..develop

# или тоже самое, если HEAD стоит на fix
git log HEAD..develop

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

# посмотреть историю коммитов, где менялся файл
git log <имя-файла>

# посмотреть историю коммитов, где менялся файл и их содержимое
git log -p <имя-файла>

# то же самое + отследить если файл переименовывался
git log -p --follow <имя-файла>

Можно смотреть коммиты в тематической ветке, в которых менялся конкретный файл, с момента разделения от родительской ветки.

# посмотреть историю коммитов в ветке feature, где менялся файл и их содержимое
# с момента отпочкования её от develop
git log -p develop..feature <имя-файла>

Просмотр содержимого коммита (git show)

Отличие show от diff в том, что show предназначен для отображения одного конкретного коммита. А diff показывает разницу между двумя коммитами.

Для просмотра содержимого файлов в истории коммитов:

# Просмотр содержимого последнего коммита из HEAD
git show

# Просмотр содержимого файла из конкретного коммита
git show <sha-коммита>:<имя-файла>

# Просмотр содержимого последнего коммита из HEAD: чуть длиннее без интерактивности
git -P show HEAD

# Просмотр содержимого последнего коммита в конкретной ветке, например в `master`
git show master

# Просмотр содержимого конкретного коммита в истории
git show 36ff58b

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

# Просмотр содержимого предпоследнего коммита из HEAD
git show HEAD~
git show HEAD~1

# Можно добавлять тильду для ветки или sha-коммита, чтобы смотреть содержимое на минус N шагов
git show master~2
git show 36ff58b~2

Для просмотра файла целиком, как он выглядел на какой-то момент времени назад, нужно через двоеточие указать путь:

# Просмотр содержимого файла целиком из предпоследнего коммита из HEAD
git show HEAD~:<имя-файла>
git show HEAD~1:developer/git.adoc

# Можно добавлять тильду для ветки или sha-коммита, чтобы смотреть содержимое на минус N шагов
git show 36ff58b~2:developer/git.adoc

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

# Просмотр содержимого файла целиком из предпоследнего коммита из HEAD
git show :developer/git.adoc

Для просмотра содержимого самого свежего коммита, где встречается в описании конкретное слово (при этом ветка не важна):

# Просмотр содержимого коммита, где встречается слово `Удаление` в описании коммита (case sensistive поиск)
git show :/Удаление

Слияние веток “перемоткой” (fast-forward) merge

Для слияния изменений из ветки fix в ветку develop нужно:

# Переключиться на ту ветку, куда вливаем изменения (в `develop`)
git checkout develop

# Слить изменения из ветки fix в текущую ветку
git merge fix

Фактически указатель develop передвигается вперед (fast-forward) на тот же коммит, куда указывала вершина ветки fix. При этом, команда git записала в файл .git/ORIG_HEAD коммит, куда раньше указывала ветка develop до слияния. Эта информация может быть полезна, если мы вдруг передумали сливать develop и хотим откатить назад ветку develop, до merge.

Для отката изменений, сделанных merge:

# Вернуть ветку `develop` в состояние, как она выглядела до merge
git branch -f develop ORIG_HEAD

Истинное слияние веток merge и разрешение конфликтов

В отличие от быстрой перемотки (fast-forward) истинное слияние происходит тогда, когда ветка develop имеет уже свои собственные коммиты (ушла вперед), которых нет у fix. В этом случае будет произведено истинное слияние.

Для слияния изменений из ветки fix в ветку develop нужно:

# Переключиться на ту ветку, куда вливаем изменения (в `develop`)
git checkout develop

# Слить изменения из ветки fix в текущую ветку
git merge fix

# при конфликтах, для просмотра содержимого файла в сливаемых ветках
git show <имя-сливаемой-ветки>:<имя-файла>

# запустить gui для слияния
git mergetool

# найти общий комммит для двух сливаемых веток develop и fix
git merge-base develop fix

# найти изменения в develop и общим коммитом
git diff -U0 <sha-общего-коммита> develop <имя-файла>

# найти изменения в fix и общим коммитом
git diff -U0  <sha-общего-коммита> fix <имя-файла>

# при конфликтах, если хотим посмотреть как было до разделения веток
git checkout --conflict=diff3 --merge <имя-файла>

# При разрешении конфликтов, если хотим взять нашу (develop) версию, то
git checkout --ours <имя-файла>
# При разрешении конфликтов, если хотим взять их (fix) версию, то
git checkout --theirs <имя-файла>

# продолжить слияние файла
git checkout --merge <имя-файла>

# завершить слияние
git merge --continue

# Отмена merge с конфликтами и откат как было
git reset --hard

Отмена слияния

Если слияние произошло и мы хотим откатить слияние (вернуться на 1 коммит назад), то нужно выполнить команду:

# Для отмены слияния нужно вернуться на 1 коммит назад.
# Гит помнит, кто был родителем, на которого надо откатиться
git reset --hard @~1

Семантические конфликты при слиянии и их разрешение

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

Для разрешения такого конфликта можно воспользоваться следующим алгоритмом:

# Сначала нужно отменить слияние
git reset --hard @~1

# Далее нужно произвести заново слияние, но без коммита
git merge <имя-ветки> --no-commit
# мы оказываемся в состоянии прерванного слияния

# Далее исправляем семантический конфликт руками в редакторе или IDE.
# Например, выставляем правильное количество переменных в функции

# добавляем изменения в индекс
git add <имя-файла-с-конфликтом>

# далее делаем коммит слияние
git merge --continue
# или что тоже самое
git commit --no-edit

Сохранение веток при слиянии (merge –no-ff)

Ситуация. Ветка develop не менялась, а в ветке feature сделана новая функциональность. При слиянии (merge) git сделает быструю перемотку, т.е. перенесет все коммиты из feature в master и будет ровная история коммитов. При таком слиянии нет возможности увидеть, что раньше было в ветке develop до слияния? Если будет найдена ошибка в слитом коде, то будет не понятно, куда откатываться? Где был был develop до слияния, чтобы сделать туда откат?

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

git merge --no-ff --no-edit <имя-сливаемой-ветки>

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

# запрещаем перемотку
git config merhe.ff false

# или можно запретить перемотку для конкретной ветки
git config branch.<имя-ветки>.mergeoptions '--no-ff'

# после этого merge будет всегда делать коммит-слияние
git merge <имя-ветки>

Создание коммита из ветки (merge –squash)

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

Для этого нужно применить флаг --squash. Этот флаг сворачивает всю историю коммитов в один коммит.

git merge --squash <имя-сливаемой-ветки>

При использовании флага --squash файла .git/MERGE_HEAD не создается. Это значит, что команда git merge --abort для отмены слияния, или команда git merge --continue для продолжения слияния, не сработают.

Нужно руками поправить конфликт и добавить отредактированный файл в индекс.

Сворачивание многих коммитов в один в тематической ветке

Для того, чтобы свернуть несколько коммитов в текущей ветке:

# Свернуть последние 4 коммита в текущей ветке в один
git rebase -i HEAD~4

Для сворачивания всех коммитов в текущей ветке:

# Свернуть все коммиты в текущей ветке в один, с момента ответвления от develop
git rebase -i `git merge-base HEAD develop`

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

Для удаления ненужной ветки fix после слияния с develop:

# Общий синтаксис команды удаления
git branch -d <имя-удаляемой-ветки>

# Переключиться ветку в которой изменения слиты с удаляемой веткой,  например в `develop` и удалить `fix`
git checkout develop
git branch -d fix

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

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

# Общий синтаксис команды безусловного удаления ветки
git branch -D <имя-удаляемой-ветки>

Если нужно восстановить только что удаленную ветку:

# Общий синтаксис команды восстановления удаленной ветки
git branch <имя-новой-ветки> <sha-вершины-коммита-куда-указывала-удаленная-ветка>

Удаление всей истории коммитов (перезапись origin)

Пример: для ветки develop, когда хочется текущее состояние закомитить как начальное.

10039  git checkout --orphan latest_branch
10040  git add -A
10041  git status
10042  git commit -am "init commit"
10043  git branch -D develop
10044  git branch -m develop
10045  git push -f origin develop

История переключений веток: лог ссылок reflog

При каждом переключении ветки, при каждом коммите (движении HEAD) команда git ведет reflog, журнал переключений. Например, для HEAD, журнал reflog хранится в файле .git/logs/HEAD. Журнал reflog бывает полезен, например, при поиске недостижимых коммитов.

# Вывести reflog (историю всех движений) для ветки
git reflog <имя-ветки>

# Вывести reflog для HEAD
git reflog

# Вывести reflog с указанием даты, чтобы удобнее было искать
git reflog --date=iso

# Вывести историю переключений последних 4 раз, чтобы увидеть sha-коммитов
git reflog -4

Для восстановления ветки из reflog можно воспользоваться командой:

# Создать новую ветку, на основании записи в истории`reflog` HEAD@{12}
git branch <имя-новой-ветки> HEAD@{12}

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

Для перехода на предыдущую ветку, из которой мы совершили checkout на текущую ветку, можно воспользоваться reflog.

# Перейти на предыдущую ветку с которой мы пришли в текущую.
git checkout @{-1}
# можно использовать -2, -3 checkout'ов назад


# Команда переключения на предыдущую ветку
git checkout -

Этот фокус не сработает, если предыдущая ветка была удалена.

Создание патча коммита

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

git format-patch -1 <sha-коммита>

# сделать единый патч для последних трех коммитов
git format-patch -3 --stdout > multi_commit.patch

# сделать единый патч, содержащий все коммиты из текущей ветки
# которых нет в ветке master
git format-patch --signoff master --stdout > multi_commit.patch


# создать патчи в папке ~/output/directory/ содержащий все коммиты из текущей ветки
# которых нет в ветке master
git format-patch --signoff master -o ~/output/directory/

Что такое хороший коммит

  1. Атомарный. Посвящен только одной задаче или цели.

  2. Консистентный. Переводит проект из одного рабочего состояния в другое рабочее состояние. Код должен продолжать работать. Тесты должны проходить.

  3. Имеет хорошее описание. На первой строке указывается, что было сделано в виде заголовка, не более 50 символов. Далее, через одну пустую строку, указывается контекст: почему это было сделано? Зачем это нужно? То есть, указывается то, чего не видно из кода и изменений.

  4. Коммит нужно делать только тогда, когда окончена работа, а не когда закончился рабочий день.

  5. Коммитить лучше чаще.

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

Описание

Рецепты git

Конвейеры
0 успешных
0 с ошибкой